<%!-- Header --%>
{@current_scope.organization.name}
Network overview
<%= if assigns[:last_updated] do %>
Updated {ToweropsWeb.TimeHelpers.format_datetime(@last_updated, @timezone, @time_format)}
<.icon name="hero-arrow-path" class="h-3.5 w-3.5" /> Refresh
<% end %>
<%= if !assigns[:summary] do %>
<.skeleton_dashboard />
<% end %>
<%!-- Empty State --%>
<%= if assigns[:summary] && @device_count == 0 do %>
<.icon
name="hero-light-bulb"
class="mx-auto h-14 w-14 text-blue-500 dark:text-blue-400"
/>
{t("Let's get started!")}
{t("Set up your network monitoring in a few steps.")}
<%= if @current_scope.organization.use_sites do %>
1
{t("Create a Site")}
{t("Sites represent your physical locations")}
2
{t("Add Devices")}
{t("Add routers, switches, and servers to your sites")}
<% else %>
1
{t("Add Devices")}
{t("Add network devices to monitor")}
<% end %>
{if @current_scope.organization.use_sites, do: "3", else: "2"}
{t("Monitor & Receive Alerts")}
{t("TowerOps automatically monitors your devices and alerts you of issues")}
<%= if @current_scope.organization.use_sites do %>
<.button navigate={~p"/sites/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Create Your First Site
<% else %>
<.button navigate={~p"/devices/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Add Your First Device
<% end %>
<% end %>
<%= if assigns[:summary] && @device_count > 0 do %>
<%!-- ═══════════════════════════════════════════════
STAT CARDS — compact row
═══════════════════════════════════════════════ --%>
<%!-- Health Score --%>
{t("Health")}
80 ->
"bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400"
@summary.health_score > 50 ->
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-400"
true ->
"bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400"
end
]}>
{cond do
@summary.health_score > 80 -> "Good"
@summary.health_score > 50 -> "Degraded"
true -> "Critical"
end}
{@summary.health_score}
<%!-- Uptime --%>
{t("Uptime")}
{if @uptime_percentage == 100.0,
do: "100",
else: :erlang.float_to_binary(@uptime_percentage, decimals: 1)}%
{@device_up}/{@device_count} up
<%!-- Devices --%>
<.link
navigate={~p"/devices"}
class="rounded-lg border border-gray-200 bg-white px-4 py-3 transition hover:shadow-sm dark:border-white/10 dark:bg-gray-800/50"
>
{t("Devices")}
{@device_count}
{@device_up} up
<%= if @device_down > 0 do %>
{@device_down} down
<% end %>
<%!-- Alerts --%>
<.link
navigate={~p"/alerts"}
class="rounded-lg border border-gray-200 bg-white px-4 py-3 transition hover:shadow-sm dark:border-white/10 dark:bg-gray-800/50"
>
{t("Alerts")}
0,
do: "text-red-600 dark:text-red-400",
else: "text-green-600 dark:text-green-400"
)
]}>
{if @total_alert_count == 0, do: "✓", else: @total_alert_count}
{if @total_alert_count == 0, do: "All clear", else: "View details →"}
<%!-- Secondary stats (only show if data exists) --%>
<%= if @has_subscribers || (@current_scope.organization.use_sites && @sites_count > 0) do %>
<%= if @has_subscribers do %>
{t("Subscribers")}
{format_number(@summary.subscribers.total)}
{format_mrr(@summary.subscribers.total_mrr)}/mo
{t("MRR at Risk")}
{format_mrr(@impact_summary.mrr_at_risk)}
0}
class="text-xs text-red-600 dark:text-red-400"
>
{format_number(@impact_summary.subscribers_affected)} affected
<% end %>
<%= if @current_scope.organization.use_sites && @sites_count > 0 do %>
<.link
navigate={~p"/sites"}
class="rounded-lg border border-gray-200 bg-white px-4 py-3 transition hover:shadow-sm dark:border-white/10 dark:bg-gray-800/50"
>
{t("Sites")}
{@sites_count}
<% end %>
<.link
navigate={~p"/insights"}
class="rounded-lg border border-gray-200 bg-white px-4 py-3 transition hover:shadow-sm dark:border-white/10 dark:bg-gray-800/50"
>
{t("Insights")}
{@summary.insights.critical + @summary.insights.warning + @summary.insights.info}
0}
class="inline-flex items-center rounded-full bg-red-100 px-1.5 py-0.5 text-[10px] font-medium text-red-700 dark:bg-red-900/30 dark:text-red-400"
>
{@summary.insights.critical} critical
0}
class="inline-flex items-center rounded-full bg-yellow-100 px-1.5 py-0.5 text-[10px] font-medium text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
>
{@summary.insights.warning} warning
<% end %>
<%!-- ═══════════════════════════════════════════════
{t("ACTIVE INCIDENTS (only when there ARE incidents)")}
═══════════════════════════════════════════════ --%>
<%= if @active_incidents != [] do %>
{t("Active Incidents")}
{length(@active_incidents)}
<.link
navigate={~p"/devices/#{incident.device_id}"}
class="text-sm font-semibold text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
>
{incident.device_name}
at {incident.site_name}
{format_duration(incident.duration_seconds)}
<%= if incident.subscribers_affected > 0 do %>
{format_number(incident.subscribers_affected)} subs
<% end %>
<%= if mrr_at_risk_positive?(incident.mrr_at_risk) do %>
{format_mrr(incident.mrr_at_risk)}/mo
<% end %>
<% end %>
<%!-- ═══════════════════════════════════════════════
{t("MAIN CONTENT — full width stacked layout")}
═══════════════════════════════════════════════ --%>
<%!-- Active Alerts (only show section when there ARE alerts) --%>
<%= if @active_alerts != [] do %>
{t("Active Alerts")}
<.link
navigate={~p"/alerts"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all {@total_alert_count} →
{t("Device")}
{t("Message")}
{t("Impact")}
{t("When")}
<.link
navigate={~p"/devices/#{alert.device.id}"}
class="text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
>
{alert.device.name}
{alert.device.site.name}
{alert.message}
<%= if alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] && alert.gaiia_impact["total_subscribers"] > 0 do %>
{alert.gaiia_impact["total_subscribers"]} subs
<% end %>
<.timestamp datetime={alert.triggered_at} timezone={@timezone} />
<%= if is_nil(alert.acknowledged_at) && is_nil(alert.resolved_at) do %>
{t("Ack")}
<% else %>
Ack'd
<% end %>
<% end %>
<%!-- Insights (compact grouped view) --%>
<%= if @insights != [] do %>
{t("Insights")}
<.link
patch={~p"/dashboard"}
class={[
"rounded-full px-2 py-0.5 text-[10px] font-medium transition",
if(is_nil(@insight_source),
do: "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
else:
"text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
)
]}
>
{t("All")}
<.link
:for={source <- ~w(preseem snmp gaiia system)}
patch={~p"/dashboard?insight_source=#{source}"}
class={[
"rounded-full px-2 py-0.5 text-[10px] font-medium transition",
if(@insight_source == source,
do: "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
else:
"text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
)
]}
>
{source}
<.link
navigate={~p"/insights"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all →
{insight.urgency}
{insight.source}
<.link
navigate={~p"/insights?source=#{insight.source}"}
class="hover:text-blue-600 dark:hover:text-blue-400"
>
{insight.title}
{insight.description}
<.icon name="hero-x-mark" class="h-3.5 w-3.5" />
<% end %>
<%!-- Recent Config Changes --%>
<%= if Enum.any?(@recent_config_changes) do %>
Config Changes (7d)
<.link
navigate={~p"/insights"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all →
<%= for change <- @recent_config_changes do %>
<.link
navigate={~p"/devices/#{change.device_id}/config-timeline"}
class="flex items-center gap-3 px-4 py-2.5 bg-white hover:bg-gray-50 dark:bg-gray-800/50 dark:hover:bg-white/5 transition-colors"
>
{if change.device, do: change.device.name, else: "Unknown"}
{change.change_size} lines · {Enum.join(
Enum.take(change.sections_changed, 2),
", "
)}
{format_short_datetime(change.changed_at, @timezone)}
<% end %>
<% end %>
<%!-- Site Health Grid --%>
<%= if @current_scope.organization.use_sites && @site_impact_summaries != [] do %>
{t("Site Health")}
<.link
navigate={~p"/sites"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all →
{t("Site")}
{t("Devices")}
{t("Down")}
{t("QoE")}
{t("Subscribers")}
<.link
navigate={~p"/sites/#{site.site_id}"}
class="flex items-center gap-2 text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
>
{site.name}
{site.device_count}
<%= if site.devices_down > 0 do %>
{site.devices_down}
<% else %>
0
<% end %>
<%= if site.avg_qoe do %>
"text-red-600 dark:text-red-400"
site.avg_qoe < 4.0 -> "text-yellow-600 dark:text-yellow-400"
true -> "text-green-600 dark:text-green-400"
end
}>
{format_qoe(site.avg_qoe)}
<% else %>
—
<% end %>
<%= if site.subscribers do %>
{site.subscribers}
<%= if site.subscribers_affected > 0 do %>
({format_number(site.subscribers_affected)} affected)
<% end %>
<% else %>
—
<% end %>
<% end %>
<%!-- Recent Activity (compact, only show meaningful entries) --%>
<%= if assigns[:recent_activity] && @recent_activity != [] do %>
{t("Recent Activity")}
<.link
navigate={~p"/activity"}
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
>
View all →
{activity.summary}
{format_short_datetime(activity.timestamp, @timezone)}
<% end %>
<%!-- Quick Actions --%>
<.link navigate={~p"/devices/new"} class="btn btn-outline btn-sm btn-xs gap-1">
<.icon name="hero-plus-circle" class="h-3.5 w-3.5" /> Add Device
<.link navigate={~p"/alerts"} class="btn btn-outline btn-sm btn-xs gap-1">
<.icon name="hero-bell-alert" class="h-3.5 w-3.5" /> Alerts
<.link navigate={~p"/trace"} class="btn btn-outline btn-sm btn-xs gap-1">
<.icon name="hero-signal" class="h-3.5 w-3.5" /> Trace
<.link navigate={~p"/network-map"} class="btn btn-outline btn-sm btn-xs gap-1">
<.icon name="hero-globe-alt" class="h-3.5 w-3.5" /> Network Map
<% end %>