+ <.icon
+ name="hero-light-bulb"
+ class="mx-auto h-16 w-16 text-blue-500 dark:text-blue-400"
+ />
+
+ Let's get started!
+
+
+ Follow these steps to set up your network monitoring:
+
+
+
+
+
+ 1
+
+
+
Create a Site
+
+ Sites represent your physical locations (offices, data centers, towers, etc.)
+
+
+
+
+
+ 2
+
+
+
Add Equipment
+
+ Add network devices (routers, switches, servers) to your sites
+
+
+
+
+
+ 3
+
+
+
+ Monitor & Receive Alerts
+
+
+ Towerops will automatically monitor your equipment and alert you of issues
+
+
+
-
-
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/alerts"}
- class="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900"
- >
-
Active Alerts
-
0 && "text-red-600 dark:text-red-500") ||
- "text-zinc-900 dark:text-zinc-100"
- ]}>
- {length(@active_alerts)}
-
-
Requires attention
-
-
-
-
System Status
-
- <%= if @equipment_down == 0 do %>
- <.icon name="hero-check-circle" class="h-5 w-5" />
-
All Systems Operational
- <% else %>
- <.icon name="hero-exclamation-triangle" class="h-5 w-5" />
-
{@equipment_down} Equipment Down
- <% end %>
+
+ <.button navigate={~p"/orgs/#{@current_organization.slug}/sites/new"} variant="primary">
+ <.icon name="hero-plus" class="h-5 w-5" /> Create Your First Site
+
-
+ <% else %>
+
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/sites"}
+ class="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900"
+ >
+
Sites
+
{@sites_count}
+
Total sites
+
+
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/equipment"}
+ class="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900"
+ >
+
Equipment
+
{@equipment_count}
+
+
+
+ {@equipment_up} Up
+
+
+
+ {@equipment_down} Down
+
+
+
+ {@equipment_unknown} Unknown
+
+
+
+
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/alerts"}
+ class="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900"
+ >
+
Active Alerts
+
0 && "text-red-600 dark:text-red-500") ||
+ "text-zinc-900 dark:text-zinc-100"
+ ]}>
+ {length(@active_alerts)}
+
+
Requires attention
+
+
+
+
System Status
+
+ <%= if @equipment_down == 0 do %>
+ <.icon name="hero-check-circle" class="h-5 w-5" />
+ All Systems Operational
+ <% else %>
+ <.icon name="hero-exclamation-triangle" class="h-5 w-5" />
+ {@equipment_down} Equipment Down
+ <% end %>
+
+
+
+
+ <%= if length(@active_alerts) > 0 do %>
+
+
+
Active Alerts
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/alerts"}
+ class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
+ >
+ View All Alerts →
+
+
+
+
+ <%= for alert <- Enum.take(@active_alerts, 5) do %>
+
+ <.icon
+ name="hero-exclamation-triangle"
+ class="h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-500"
+ />
+
+
+ <.link
+ navigate={
+ ~p"/orgs/#{@current_organization.slug}/equipment/#{alert.equipment.id}"
+ }
+ class="hover:text-blue-600 hover:underline dark:hover:text-blue-400"
+ >
+ {alert.equipment.name}
+
+
+
{alert.message}
+
+
+ {Calendar.strftime(alert.triggered_at, "%H:%M")}
+
+
+ <% end %>
+
+ <%= if length(@active_alerts) > 5 do %>
+
+ + {length(@active_alerts) - 5} more alerts
+
+ <% end %>
+
+
+ <% end %>
- <%= if length(@active_alerts) > 0 do %>
-
-
Active Alerts
+
Quick Actions
+
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/sites"}
+ class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:bg-blue-500 dark:hover:bg-blue-600"
+ >
+ <.icon name="hero-building-office" class="h-5 w-5" />
+ Manage Sites
+
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/equipment"}
+ class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:bg-blue-500 dark:hover:bg-blue-600"
+ >
+ <.icon name="hero-server" class="h-5 w-5" />
+ Manage Equipment
+
<.link
navigate={~p"/orgs/#{@current_organization.slug}/alerts"}
- class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
+ class="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2.5 text-sm font-semibold text-zinc-700 shadow-sm hover:bg-zinc-50 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700"
>
- View All Alerts →
+ <.icon name="hero-bell" class="h-5 w-5" />
+ View Alerts
-
-
- <%= for alert <- Enum.take(@active_alerts, 5) do %>
-
- <.icon
- name="hero-exclamation-triangle"
- class="h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-500"
- />
-
-
- <.link
- navigate={
- ~p"/orgs/#{@current_organization.slug}/equipment/#{alert.equipment.id}"
- }
- class="hover:text-blue-600 hover:underline dark:hover:text-blue-400"
- >
- {alert.equipment.name}
-
-
-
{alert.message}
-
-
- {Calendar.strftime(alert.triggered_at, "%H:%M")}
-
-
- <% end %>
-
- <%= if length(@active_alerts) > 5 do %>
-
- + {length(@active_alerts) - 5} more alerts
-
- <% end %>
-
<% end %>
-
-
-
Quick Actions
-
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/sites"}
- class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:bg-blue-500 dark:hover:bg-blue-600"
- >
- <.icon name="hero-building-office" class="h-5 w-5" />
- Manage Sites
-
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/equipment"}
- class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:bg-blue-500 dark:hover:bg-blue-600"
- >
- <.icon name="hero-server" class="h-5 w-5" />
- Manage Equipment
-
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/alerts"}
- class="inline-flex items-center gap-2 rounded-lg border border-zinc-300 bg-white px-4 py-2.5 text-sm font-semibold text-zinc-700 shadow-sm hover:bg-zinc-50 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-200 dark:hover:bg-zinc-700"
- >
- <.icon name="hero-bell" class="h-5 w-5" />
- View Alerts
-
-
-
diff --git a/lib/towerops_web/live/equipment_live/form.ex b/lib/towerops_web/live/equipment_live/form.ex
index f9d525e7..47df5c07 100644
--- a/lib/towerops_web/live/equipment_live/form.ex
+++ b/lib/towerops_web/live/equipment_live/form.ex
@@ -12,12 +12,20 @@ defmodule ToweropsWeb.EquipmentLive.Form do
organization = socket.assigns.current_organization
sites = Sites.list_organization_sites(organization.id)
- {:ok,
- socket
- |> assign(:organization, organization)
- |> assign(:available_sites, sites)
- |> assign(:preselected_site_id, params["site_id"])
- |> assign(:snmp_test_result, nil)}
+ # Redirect to sites page if no sites exist
+ if Enum.empty?(sites) do
+ {:ok,
+ socket
+ |> put_flash(:info, "Please create a site before adding equipment.")
+ |> push_navigate(to: ~p"/orgs/#{organization.slug}/sites/new")}
+ else
+ {:ok,
+ socket
+ |> assign(:organization, organization)
+ |> assign(:available_sites, sites)
+ |> assign(:preselected_site_id, params["site_id"])
+ |> assign(:snmp_test_result, nil)}
+ end
end
@impl true
diff --git a/lib/towerops_web/live/equipment_live/index.ex b/lib/towerops_web/live/equipment_live/index.ex
index 4ae77a85..c985b388 100644
--- a/lib/towerops_web/live/equipment_live/index.ex
+++ b/lib/towerops_web/live/equipment_live/index.ex
@@ -3,16 +3,19 @@ defmodule ToweropsWeb.EquipmentLive.Index do
use ToweropsWeb, :live_view
alias Towerops.Equipment
+ alias Towerops.Sites
@impl true
def mount(_params, _session, socket) do
organization = socket.assigns.current_organization
equipment = Equipment.list_organization_equipment(organization.id)
+ sites = Sites.list_organization_sites(organization.id)
{:ok,
socket
|> assign(:page_title, "Equipment")
- |> assign(:equipment, equipment)}
+ |> assign(:equipment, equipment)
+ |> assign(:has_sites, length(sites) > 0)}
end
@impl true
diff --git a/lib/towerops_web/live/equipment_live/index.html.heex b/lib/towerops_web/live/equipment_live/index.html.heex
index 62ed9e46..66c6e5bb 100644
--- a/lib/towerops_web/live/equipment_live/index.html.heex
+++ b/lib/towerops_web/live/equipment_live/index.html.heex
@@ -7,72 +7,100 @@
{@page_title}
<:subtitle>Monitor and manage all your network equipment
<:actions>
- <.button navigate={~p"/orgs/#{@current_organization.slug}/equipment/new"} variant="primary">
+ <.button
+ :if={@has_sites}
+ navigate={~p"/orgs/#{@current_organization.slug}/equipment/new"}
+ variant="primary"
+ >
<.icon name="hero-plus" class="h-5 w-5" /> New Equipment
- <%= if @equipment == [] do %>
+ <%= if !@has_sites do %>
- <.icon name="hero-server" class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-600" />
-
No equipment
+ <.icon
+ name="hero-building-office"
+ class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-600"
+ />
+
+ Create a site first
+
- Get started by adding your first equipment.
+ Before adding equipment, you need to create at least one site location.
+
+
+ Sites help you organize your equipment by physical location.
- <.button
- navigate={~p"/orgs/#{@current_organization.slug}/equipment/new"}
- variant="primary"
- >
- <.icon name="hero-plus" class="h-5 w-5" /> New Equipment
+ <.button navigate={~p"/orgs/#{@current_organization.slug}/sites/new"} variant="primary">
+ <.icon name="hero-plus" class="h-5 w-5" /> Create Your First Site
<% else %>
- <.table id="equipment" rows={@equipment}>
- <:col :let={eq} label="Name">
-
{eq.name}
-
- <:col :let={eq} label="IP Address">
-
{eq.ip_address}
-
- <:col :let={eq} label="Site">
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/sites/#{eq.site.id}"}
- class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- {eq.site.name}
-
-
- <:col :let={eq} label="Status">
-
- {eq.status |> to_string() |> String.upcase()}
-
-
- <:col :let={eq} label="Last Checked">
-
- <%= if eq.last_checked_at do %>
- {Calendar.strftime(eq.last_checked_at, "%Y-%m-%d %H:%M")}
- <% else %>
- Never
- <% end %>
-
-
- <:action :let={eq}>
- <.link
- navigate={~p"/orgs/#{@current_organization.slug}/equipment/#{eq.id}"}
- class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
- >
- View
-
-
-
+ <%= if @equipment == [] do %>
+
+ <.icon name="hero-server" class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-600" />
+
No equipment
+
+ Get started by adding your first equipment.
+
+
+ <.button
+ navigate={~p"/orgs/#{@current_organization.slug}/equipment/new"}
+ variant="primary"
+ >
+ <.icon name="hero-plus" class="h-5 w-5" /> New Equipment
+
+
+
+ <% else %>
+ <.table id="equipment" rows={@equipment}>
+ <:col :let={eq} label="Name">
+
{eq.name}
+
+ <:col :let={eq} label="IP Address">
+
{eq.ip_address}
+
+ <:col :let={eq} label="Site">
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/sites/#{eq.site.id}"}
+ class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
+ >
+ {eq.site.name}
+
+
+ <:col :let={eq} label="Status">
+
+ {eq.status |> to_string() |> String.upcase()}
+
+
+ <:col :let={eq} label="Last Checked">
+
+ <%= if eq.last_checked_at do %>
+ {Calendar.strftime(eq.last_checked_at, "%Y-%m-%d %H:%M")}
+ <% else %>
+ Never
+ <% end %>
+
+
+ <:action :let={eq}>
+ <.link
+ navigate={~p"/orgs/#{@current_organization.slug}/equipment/#{eq.id}"}
+ class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
+ >
+ View
+
+
+
+ <% end %>
<% end %>