towerops/lib/towerops_web/live/capacity_live.ex
Graham McIntire 1c222f8ff5
feat: add capacity insight worker, UI, and reporting views
Add CapacityInsightWorker (every 15 min) that generates critical/warning
insights when backhaul utilization exceeds 90%/75% and auto-resolves
when it drops below 70%.

Add capacity and utilization columns to the device ports tab with
set/clear capacity controls. Add organization-level /capacity page
with summary cards and per-site capacity table. Add capacity summary
card to site show page. Add Capacity link to nav menu.
2026-03-07 14:42:40 -06:00

56 lines
2.4 KiB
Elixir

defmodule ToweropsWeb.CapacityLive do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Capacity
@impl true
def mount(_params, _session, socket) do
organization = socket.assigns.current_scope.organization
summaries = Capacity.get_organization_capacity_summary(organization.id)
total_capacity = Enum.reduce(summaries, 0, fn s, acc -> acc + s.total_capacity_bps end)
total_throughput = Enum.reduce(summaries, 0.0, fn s, acc -> acc + s.total_throughput_bps end)
overall_utilization =
if total_capacity > 0, do: Float.round(total_throughput / total_capacity * 100, 1), else: 0.0
sites_at_risk = Enum.count(summaries, fn s -> s.utilization_pct >= 75 end)
sites_with_headroom = Enum.count(summaries, fn s -> s.utilization_pct < 70 and s.total_capacity_bps > 0 end)
{:ok,
socket
|> assign(:page_title, "Capacity")
|> assign(:summaries, summaries)
|> assign(:total_capacity, total_capacity)
|> assign(:total_throughput, total_throughput)
|> assign(:overall_utilization, overall_utilization)
|> assign(:sites_at_risk, sites_at_risk)
|> assign(:sites_with_headroom, sites_with_headroom)}
end
defp format_capacity(bps) when is_number(bps) and bps >= 1_000_000_000,
do: "#{Float.round(bps / 1_000_000_000, 1)} Gbps"
defp format_capacity(bps) when is_number(bps) and bps >= 1_000_000, do: "#{Float.round(bps / 1_000_000, 1)} Mbps"
defp format_capacity(bps) when is_number(bps) and bps >= 1_000, do: "#{Float.round(bps / 1_000, 1)} Kbps"
defp format_capacity(bps) when is_number(bps), do: "#{bps} bps"
defp format_capacity(_), do: "-"
defp utilization_color(pct) when pct >= 90, do: "bg-red-500"
defp utilization_color(pct) when pct >= 70, do: "bg-yellow-500"
defp utilization_color(_pct), do: "bg-green-500"
defp utilization_text_color(pct) when pct >= 90, do: "text-red-600 dark:text-red-400"
defp utilization_text_color(pct) when pct >= 70, do: "text-yellow-600 dark:text-yellow-400"
defp utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
defp status_badge(pct) when pct >= 90, do: {"Critical", "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"}
defp status_badge(pct) when pct >= 70,
do: {"Warning", "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300"}
defp status_badge(_pct), do: {"Healthy", "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"}
end