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>
121 lines
5.2 KiB
Text
121 lines
5.2 KiB
Text
<Layouts.admin flash={@flash} timezone={@timezone}>
|
|
<div class="space-y-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
Job Monitoring Dashboard
|
|
</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">
|
|
Real-time monitoring of polling and discovery jobs
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Health Metrics Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
|
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Completed (1h)</h3>
|
|
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
|
{Map.get(@health_metrics, :completed_last_hour, 0)}
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">jobs completed</p>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
|
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Failed (1h)</h3>
|
|
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
|
{Map.get(@health_metrics, :failed_last_hour, 0)}
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">failures</p>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
|
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Avg Duration</h3>
|
|
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
|
<%= if Map.get(@health_metrics, :avg_execution_time_seconds) do %>
|
|
{Map.get(@health_metrics, :avg_execution_time_seconds) |> Float.round(1)}s
|
|
<% else %>
|
|
N/A
|
|
<% end %>
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">last hour</p>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
|
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Active Jobs</h3>
|
|
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
|
{length(@active_jobs)}
|
|
</p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">executing now</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Operations -->
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Active Operations</h2>
|
|
</div>
|
|
<div class="p-4">
|
|
<%= if @active_jobs == [] do %>
|
|
<p class="text-gray-500 dark:text-gray-400 text-center py-8">
|
|
No active jobs
|
|
</p>
|
|
<% else %>
|
|
<div class="space-y-2">
|
|
<%= for job <- @active_jobs do %>
|
|
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded">
|
|
<div>
|
|
<span class="font-mono text-sm text-gray-900 dark:text-white">
|
|
{job.worker |> String.split(".") |> List.last()}
|
|
</span>
|
|
<span class="text-sm text-gray-500 dark:text-gray-400 ml-2">
|
|
Device ID: {job.device_id}
|
|
</span>
|
|
</div>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
Started: {Calendar.strftime(job.timestamp, "%H:%M:%S")}
|
|
</span>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Problems -->
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 flex items-center justify-between">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Problems</h2>
|
|
<div class="flex gap-4">
|
|
<span class="text-sm text-orange-600 dark:text-orange-400">
|
|
{if @stuck_jobs_count > 0, do: "#{@stuck_jobs_count} stuck", else: ""}
|
|
</span>
|
|
<span class="text-sm text-red-600 dark:text-red-400">
|
|
{if @failed_jobs_count > 0, do: "#{@failed_jobs_count} failed", else: ""}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="p-4">
|
|
<%= if @stuck_jobs_count == 0 and @failed_jobs_count == 0 do %>
|
|
<p class="text-gray-500 dark:text-gray-400 text-center py-8">
|
|
No problems detected
|
|
</p>
|
|
<% else %>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
Problems section coming soon
|
|
</p>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Health Metrics -->
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Health Metrics</h2>
|
|
</div>
|
|
<div class="p-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
Detailed metrics coming soon
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Layouts.admin>
|