towerops/lib/towerops_web/live/admin/monitoring_live.ex
mayor bb9e6f0b0c
feat: create MonitoringLive base structure with PubSub subscription
Add LiveView for real-time job monitoring dashboard:
- Subscribe to job:lifecycle PubSub topic for live updates
- Display health metrics (completed, failed, avg duration, active jobs)
- Show active operations with real-time updates
- Display problems section (stuck/failed job counts)
- Add router entry under /admin/monitoring

Tests verify PubSub subscription and basic rendering.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 18:27:40 -06:00

73 lines
2.1 KiB
Elixir

defmodule ToweropsWeb.Admin.MonitoringLive do
@moduledoc """
Real-time job monitoring dashboard for Oban workers.
Displays:
- Active operations (currently executing jobs)
- Problems (stuck/failed jobs)
- Health metrics (throughput, errors, latency)
- Recent activity timeline
"""
use ToweropsWeb, :live_view
alias Towerops.JobMonitoring
alias Towerops.JobMonitoring.Metrics
@topic "job:lifecycle"
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(Towerops.PubSub, @topic)
end
socket =
socket
|> assign(:page_title, "Job Monitoring")
|> assign(:active_jobs, [])
|> assign(:stuck_jobs_count, 0)
|> assign(:failed_jobs_count, 0)
|> assign(:recent_jobs, [])
|> assign(:health_metrics, %{})
|> load_monitoring_data()
{:ok, socket}
end
@impl Phoenix.LiveView
def handle_info(%{event: :started} = event, socket) do
# Job started - add to active jobs list
active_jobs = [event | socket.assigns.active_jobs]
{:noreply, assign(socket, :active_jobs, active_jobs)}
end
def handle_info(%{event: event} = job_event, socket) when event in [:completed, :failed] do
# Job finished - remove from active jobs, reload data
active_jobs = Enum.reject(socket.assigns.active_jobs, fn job -> job.job_id == job_event.job_id end)
socket =
socket
|> assign(:active_jobs, active_jobs)
|> load_monitoring_data()
{:noreply, socket}
end
def handle_info(_event, socket) do
{:noreply, socket}
end
@spec load_monitoring_data(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
defp load_monitoring_data(socket) do
stuck_jobs = JobMonitoring.list_stuck_jobs()
failed_jobs = JobMonitoring.list_failed_jobs()
recent_jobs = JobMonitoring.list_recent_completions(20)
metrics = Metrics.calculate_all()
socket
|> assign(:stuck_jobs_count, length(stuck_jobs))
|> assign(:failed_jobs_count, length(failed_jobs))
|> assign(:recent_jobs, recent_jobs)
|> assign(:health_metrics, metrics)
end
end