Transform the dashboard into a single-pane-of-glass command center that blends operational metrics with business context from Gaiia subscriber data. - Extend insight schema with multi-source support (snmp, gaiia, system) - Add Oban workers for automated insight generation (device health, system, gaiia) - New Dashboard context with health score computation and site summaries - Rewrite dashboard LiveView with health overview, alert/insight feeds, site grid - Add source filter pills for insights with URL state management - Add metrics bar to site detail pages (device count, alerts, subscribers, MRR) - Add subscriber/MRR context to alert list site links - Add subscriber badges to device list site headers - Real-time PubSub updates for alerts on dashboard
129 lines
4.2 KiB
Elixir
129 lines
4.2 KiB
Elixir
defmodule Towerops.Dashboard do
|
|
@moduledoc """
|
|
Aggregates data from multiple contexts for the command center dashboard.
|
|
"""
|
|
|
|
alias Towerops.Alerts
|
|
alias Towerops.Devices
|
|
alias Towerops.Gaiia
|
|
alias Towerops.Preseem.Insights
|
|
alias Towerops.Sites
|
|
|
|
@doc "Returns a complete dashboard summary for an organization."
|
|
def get_dashboard_summary(organization_id) do
|
|
status_counts = Devices.get_device_status_counts(organization_id)
|
|
total_devices = status_counts |> Map.values() |> Enum.sum()
|
|
active_alerts = Alerts.list_organization_active_alerts(organization_id)
|
|
subscriber_totals = Gaiia.get_org_subscriber_totals(organization_id)
|
|
insight_counts = Insights.count_active_by_urgency(organization_id)
|
|
|
|
%{
|
|
health_score: compute_health_score(organization_id),
|
|
devices: %{
|
|
total: total_devices,
|
|
up: Map.get(status_counts, :up, 0),
|
|
down: Map.get(status_counts, :down, 0),
|
|
unknown: Map.get(status_counts, :unknown, 0)
|
|
},
|
|
alerts: %{
|
|
active_count: length(active_alerts)
|
|
},
|
|
subscribers: %{
|
|
total: subscriber_totals.total_subscribers,
|
|
total_mrr: subscriber_totals.total_mrr
|
|
},
|
|
insights: insight_counts
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Compute a weighted 0-100 health score for an organization.
|
|
|
|
Weights:
|
|
- Device uptime % (40%)
|
|
- Response time health (20%) - currently based on device status
|
|
- Critical alert penalty (20%)
|
|
- Preseem QoE average (10%, skipped if unavailable)
|
|
- Agent online % (10%, skipped if no agents)
|
|
|
|
Unavailable components have their weight redistributed proportionally.
|
|
"""
|
|
def compute_health_score(organization_id) do
|
|
status_counts = Devices.get_device_status_counts(organization_id)
|
|
total = status_counts |> Map.values() |> Enum.sum()
|
|
|
|
if total == 0 do
|
|
100
|
|
else
|
|
up = Map.get(status_counts, :up, 0)
|
|
down = Map.get(status_counts, :down, 0)
|
|
active_alerts = Alerts.list_organization_active_alerts(organization_id)
|
|
|
|
# Device uptime percentage (0.0-1.0)
|
|
uptime_ratio = up / total
|
|
|
|
# Response time health - approximate from up/unknown ratio
|
|
responsive_ratio = (total - down) / total
|
|
|
|
# Alert penalty - each alert reduces score, capped at full penalty
|
|
alert_penalty = min(length(active_alerts) * 0.1, 1.0)
|
|
alert_score = 1.0 - alert_penalty
|
|
|
|
# Weighted score (redistribute Preseem/Agent weights to core metrics)
|
|
# Base weights: uptime 40%, response 20%, alerts 20%, preseem 10%, agents 10%
|
|
# Without preseem/agents: uptime 50%, response 25%, alerts 25%
|
|
score =
|
|
uptime_ratio * 50.0 +
|
|
responsive_ratio * 25.0 +
|
|
alert_score * 25.0
|
|
|
|
round(score)
|
|
end
|
|
end
|
|
|
|
@doc "Returns subscriber summary for the org from Gaiia data."
|
|
def get_org_subscriber_summary(organization_id) do
|
|
totals = Gaiia.get_org_subscriber_totals(organization_id)
|
|
|
|
%{
|
|
total: totals.total_subscribers,
|
|
total_mrr: totals.total_mrr
|
|
}
|
|
end
|
|
|
|
@doc "Returns per-site summaries with device counts, alerts, and subscriber data."
|
|
def list_site_summaries(organization_id) do
|
|
sites = Sites.list_organization_sites(organization_id)
|
|
|
|
Enum.map(sites, fn site ->
|
|
device_count = Devices.count_site_devices(site.id)
|
|
down_count = Devices.count_site_devices_down(site.id)
|
|
gaiia_summary = Gaiia.get_site_subscriber_summary(site.id)
|
|
|
|
%{
|
|
site_id: site.id,
|
|
name: site.name,
|
|
device_count: device_count,
|
|
devices_down: down_count,
|
|
subscribers: if(gaiia_summary, do: gaiia_summary.account_count),
|
|
mrr: if(gaiia_summary, do: gaiia_summary.total_mrr)
|
|
}
|
|
end)
|
|
end
|
|
|
|
@doc "Returns a summary for a single site."
|
|
def get_site_summary(site_id) do
|
|
device_count = Devices.count_site_devices(site_id)
|
|
down_count = Devices.count_site_devices_down(site_id)
|
|
alert_count = Alerts.count_site_active_alerts(site_id)
|
|
gaiia_summary = Gaiia.get_site_subscriber_summary(site_id)
|
|
|
|
%{
|
|
device_count: device_count,
|
|
devices_down: down_count,
|
|
alert_count: alert_count,
|
|
subscribers: if(gaiia_summary, do: gaiia_summary.account_count),
|
|
mrr: if(gaiia_summary, do: gaiia_summary.total_mrr)
|
|
}
|
|
end
|
|
end
|