feat: enhance dashboard with uptime, quick actions, mini activity feed
This commit is contained in:
parent
88b5b55a01
commit
bfe552cb47
2 changed files with 147 additions and 10 deletions
|
|
@ -123,13 +123,27 @@ defmodule ToweropsWeb.DashboardLive do
|
|||
[]
|
||||
end
|
||||
|
||||
device_up = Map.get(status_counts, :up, 0)
|
||||
|
||||
uptime_percentage =
|
||||
if device_count > 0,
|
||||
do: Float.round(device_up / device_count * 100, 1),
|
||||
else: 100.0
|
||||
|
||||
recent_activity =
|
||||
try do
|
||||
ActivityFeed.list_org_activity(organization_id, limit: 5)
|
||||
rescue
|
||||
_ -> []
|
||||
end
|
||||
|
||||
socket
|
||||
|> assign(:summary, summary)
|
||||
|> assign(:active_alerts, Enum.take(active_alerts, 20))
|
||||
|> assign(:total_alert_count, length(active_alerts))
|
||||
|> assign(:sites_count, sites_count)
|
||||
|> assign(:device_count, device_count)
|
||||
|> assign(:device_up, Map.get(status_counts, :up, 0))
|
||||
|> assign(:device_up, device_up)
|
||||
|> assign(:device_down, Map.get(status_counts, :down, 0))
|
||||
|> assign(:device_unknown, Map.get(status_counts, :unknown, 0))
|
||||
|> assign(:has_subscribers, has_subscribers)
|
||||
|
|
@ -137,6 +151,9 @@ defmodule ToweropsWeb.DashboardLive do
|
|||
|> assign(:impact_summary, impact_summary)
|
||||
|> assign(:active_incidents, active_incidents)
|
||||
|> assign(:site_impact_summaries, site_impact_summaries)
|
||||
|> assign(:uptime_percentage, uptime_percentage)
|
||||
|> assign(:recent_activity, recent_activity)
|
||||
|> assign(:last_updated, DateTime.utc_now())
|
||||
|> load_insights(organization_id)
|
||||
|> load_recent_config_changes(organization_id)
|
||||
end
|
||||
|
|
@ -258,6 +275,19 @@ defmodule ToweropsWeb.DashboardLive do
|
|||
defp mrr_at_risk_positive?(n) when is_number(n), do: n > 0
|
||||
defp mrr_at_risk_positive?(_), do: false
|
||||
|
||||
defp uptime_color(pct) when pct >= 99.0, do: "text-green-600 dark:text-green-400"
|
||||
defp uptime_color(pct) when pct >= 95.0, do: "text-yellow-600 dark:text-yellow-400"
|
||||
defp uptime_color(_pct), do: "text-red-600 dark:text-red-400"
|
||||
|
||||
defp uptime_bg(pct) when pct >= 99.0,
|
||||
do: "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800"
|
||||
|
||||
defp uptime_bg(pct) when pct >= 95.0,
|
||||
do: "bg-yellow-50 border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800"
|
||||
|
||||
defp uptime_bg(_pct),
|
||||
do: "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800"
|
||||
|
||||
defp format_qoe(nil), do: "—"
|
||||
defp format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,24 @@
|
|||
<:subtitle>Welcome to {@current_scope.organization.name}</:subtitle>
|
||||
</.header>
|
||||
|
||||
<%!-- Last Updated + Refresh --%>
|
||||
<%= if assigns[:last_updated] do %>
|
||||
<div class="mt-4 flex items-center justify-end gap-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span class="flex items-center gap-1">
|
||||
<.icon name="hero-clock" class="h-3.5 w-3.5" />
|
||||
Last updated: {Calendar.strftime(@last_updated, "%b %d, %H:%M:%S UTC")}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="refresh_dashboard"
|
||||
class="btn btn-ghost btn-xs gap-1"
|
||||
>
|
||||
<.icon name="hero-arrow-path" class="h-3.5 w-3.5" />
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= if @device_count == 0 do %>
|
||||
<div class="mt-8 rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-12 text-center dark:border-white/20 dark:bg-gray-800/50">
|
||||
<.icon
|
||||
|
|
@ -99,6 +117,20 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<%!-- Network Uptime --%>
|
||||
<div class={[
|
||||
"rounded-lg border p-4 shadow-sm",
|
||||
uptime_bg(@uptime_percentage)
|
||||
]}>
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Network Uptime</h3>
|
||||
<p class={["mt-1 text-3xl font-bold", uptime_color(@uptime_percentage)]}>
|
||||
{if @uptime_percentage == 100.0, do: "100", else: :erlang.float_to_binary(@uptime_percentage, decimals: 1)}%
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{@device_up}/{@device_count} devices up
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<%!-- Active Alerts --%>
|
||||
<.link
|
||||
navigate={~p"/alerts"}
|
||||
|
|
@ -212,13 +244,30 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Quick Actions --%>
|
||||
<div class="mt-6 flex flex-wrap items-center gap-3">
|
||||
<.link navigate={~p"/devices/new"} class="btn btn-outline btn-sm gap-1.5">
|
||||
<.icon name="hero-plus-circle" class="h-4 w-4" /> Add Device
|
||||
</.link>
|
||||
<.link navigate={~p"/discovery"} class="btn btn-outline btn-sm gap-1.5">
|
||||
<.icon name="hero-magnifying-glass-circle" class="h-4 w-4" /> Run Discovery
|
||||
</.link>
|
||||
<.link navigate={~p"/alerts"} class="btn btn-outline btn-sm gap-1.5">
|
||||
<.icon name="hero-bell-alert" class="h-4 w-4" /> View Alerts
|
||||
</.link>
|
||||
<.link navigate={~p"/subscribers/trace"} class="btn btn-outline btn-sm gap-1.5">
|
||||
<.icon name="hero-signal" class="h-4 w-4" /> Subscriber Trace
|
||||
</.link>
|
||||
</div>
|
||||
|
||||
<%!-- Active Incidents (sorted by revenue impact) --%>
|
||||
<%= if @active_incidents != [] do %>
|
||||
<div class="mt-8">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<.icon name="hero-fire" class="h-5 w-5 text-red-500" />
|
||||
Active Incidents
|
||||
<span class="ml-2 inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
||||
<span class="ml-1 inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
||||
{length(@active_incidents)}
|
||||
</span>
|
||||
</h2>
|
||||
|
|
@ -284,7 +333,10 @@
|
|||
<%!-- Left: Alert Feed (3 cols) --%>
|
||||
<div class="lg:col-span-3">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Active Alerts</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<.icon name="hero-bell-alert" class="h-5 w-5 text-red-500" />
|
||||
Active Alerts
|
||||
</h2>
|
||||
<.link
|
||||
navigate={~p"/alerts"}
|
||||
class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
|
||||
|
|
@ -394,10 +446,18 @@
|
|||
<%= if Enum.any?(@recent_config_changes) do %>
|
||||
<div class="lg:col-span-5">
|
||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5 mb-3">
|
||||
<.icon name="hero-clock" class="h-4 w-4 text-orange-500" />
|
||||
Recent Config Changes
|
||||
</h3>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
||||
<.icon name="hero-document-text" class="h-4 w-4 text-orange-500" />
|
||||
Recent Config Changes
|
||||
</h3>
|
||||
<.link
|
||||
navigate={~p"/config-changes"}
|
||||
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
|
||||
>
|
||||
View All →
|
||||
</.link>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
<%= for change <- @recent_config_changes do %>
|
||||
<.link
|
||||
|
|
@ -420,10 +480,54 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%!-- Recent Activity Mini-Feed --%>
|
||||
<%= if assigns[:recent_activity] && @recent_activity != [] do %>
|
||||
<div class="lg:col-span-3">
|
||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
||||
<.icon name="hero-clock" class="h-4 w-4 text-blue-500" />
|
||||
Recent Activity
|
||||
</h3>
|
||||
<.link
|
||||
navigate={~p"/activity"}
|
||||
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
|
||||
>
|
||||
View All →
|
||||
</.link>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
:for={activity <- @recent_activity}
|
||||
class="flex items-start gap-3 text-sm"
|
||||
>
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<span class="block h-2 w-2 rounded-full bg-blue-400 dark:bg-blue-500"></span>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-gray-900 dark:text-white text-sm">
|
||||
{activity.description}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{Calendar.strftime(activity.inserted_at, "%b %d %H:%M")}
|
||||
<%= if Map.get(activity, :actor_name) do %>
|
||||
<span class="ml-1">by {activity.actor_name}</span>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%!-- Right: Insights Feed (2 cols) --%>
|
||||
<div class="lg:col-span-2">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Insights</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<.icon name="hero-light-bulb" class="h-5 w-5 text-yellow-500" />
|
||||
Insights
|
||||
</h2>
|
||||
<.link
|
||||
navigate={~p"/insights"}
|
||||
class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
|
||||
|
|
@ -527,7 +631,10 @@
|
|||
<%= if @current_scope.organization.use_sites && @site_impact_summaries != [] do %>
|
||||
<div class="mt-8">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Site Health</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<.icon name="hero-map-pin" class="h-5 w-5 text-blue-500" />
|
||||
Site Health
|
||||
</h2>
|
||||
<.link
|
||||
navigate={~p"/sites"}
|
||||
class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue