Admin Ops-Console — Readiness Panel + Task-Oriented Navigation
Date: 2026-08-11 Status: Design approved, pending implementation plan Scope: Make the Django admin friendlier as an operations console — extend the existing MonitoringAdminSite dashboard with a configuration-readiness panel, and regroup the scattered models into operator-facing sections. No new admin package/dependency; builds on what already exists.
Problem
The admin already has a custom dashboard (config/admin.py → MonitoringAdminSite, config/dashboard.py, templates/admin/dashboard.html) showing runtime activity: active incidents, 24h pipeline health, recent check runs, failed pipelines, 7-day trends, LLM token usage.
Two gaps make it feel confusing / “loosely coupled”:
- No configuration readiness. You cannot tell at a glance whether the system is actually wired up to do its job — are notification channels active (else alerts go nowhere)? is an LLM provider live (else analysis falls back to “no AI”)? did the last preflight pass? is the inbox draining? These live in separate admin sections and must be hunted down.
- Model navigation is Django-app-shaped, not operator-shaped. The 13 models are grouped by the five Django apps (Alerts / Checkers / Intelligence / Notify / Orchestration), which doesn’t match how an operator thinks about the system.
Non-goals
- No Django Unfold or other third-party admin UI.
- No new endpoints or models.
- The self-node creation condition (
ensure_self()no-ops whenINSTANCE_IDis unset, so the hub self-node never appears in production) is a separate, later step — explicitly out of scope here. The readiness “Nodes” card still works for agent nodes regardless.
Component 1 — Readiness panel (dashboard)
Extend get_dashboard_context() with a readiness block (a list of {label, status, detail, url} entries), rendered as status cards (green / amber / red + a deep link) above the existing activity cards.
| Signal | Source | Green / Amber / Red |
|---|---|---|
| Notification channels | NotificationChannel.is_active | ≥1 active / some inactive but ≥1 active / none active → alerts go nowhere |
| LLM provider | IntelligenceProvider.is_active (one active max) | 1 active / — / none active → analysis falls back to “no AI” |
| Preflight | latest PreflightRun.overall_status | ok / warn / error (neutral when never run) |
| Inbox | InboxItem (PENDING/PROCESSING) + stuck | empty / backlog present / stuck runs present |
| Nodes | Node last-seen recency | seen recently / stale / none seen |
statusis one ofok/warn/error/neutral, mapped to a CSS class.- Each card links to the relevant changelist (channels, providers, preflight history, inbox, nodes).
- Pure read-aggregation (counts + one “latest” per signal), same pattern as the existing context builder. No writes, cheap queries.
Component 2 — Task-oriented navigation
Override MonitoringAdminSite.get_app_list() to re-bucket all registered models into three operator-facing sections, replacing the default per-app grouping on both the index and the sidebar. It reuses Django’s already permission-filtered model dicts (from super().get_app_list() / _build_app_dict), so no model a user lacks permission for is exposed.
| Section | Models (explicit order, most-used first) |
|---|---|
| Operations (daily triage) | Incident, Alert, Inbox, Pipeline Runs, Nodes |
| Configuration (wiring) | Notification Channels, LLM Providers, Pipeline Definitions, API Keys |
| History & Audit (records) | Check Runs, Preflight Runs, Analysis Runs, Alert History, Stage Executions |
- A section with zero visible models (after permission filtering) is hidden.
- A model not listed in any section falls back to a trailing “Other” section (so a future model is never silently dropped from the nav).
- The readiness cards deep-link into the Configuration/Operations sections.
Component 3 — Rendering, styling
- Readiness cards added to
templates/admin/dashboard.htmlabove the current activity cards. A small helper inconfig/dashboard.pybuilds the readiness entries; all dynamic content escaped viaformat_html(consistent with the existingprettify_json). - Theme-aware using the same
var(--body-bg / --body-fg / --hairline-color)CSS variables already used on the dashboard — no external assets, no JS. - Status → color via a small class map (
ok/warn/error/neutral).
Error handling & edge cases
- Empty models (no channels / no provider / no preflight yet / no nodes) render as the specified neutral-or-red state; never raise.
get_app_listtolerates unregistered/missing models and empty sections; the “Other” fallback prevents silent model loss.- Readiness queries are bounded aggregates; no N+1 on the index page.
Testing
- Unit tests per readiness signal across permutations: channels (none/active/mixed), provider (none/active), preflight (none/ok/warn/error), inbox (empty/backlog/stuck), nodes (none/recent/stale).
get_app_listtests: correct bucketing + order, permission-filtered models excluded, empty section hidden, unlisted model lands in “Other”.- Dashboard render test: readiness cards present with the correct status class and deep links.
- 100% branch coverage on changed code (repo convention).
Acceptance criteria
- The dashboard shows a readiness panel with the five signals, correct green/amber/red status, and working deep links.
- The admin index + sidebar group models into Operations / Configuration / History & Audit (Nodes under Operations), permission-safe, empty sections hidden.
- No new dependency/endpoint/model; theme-aware, self-contained; CI green; 100% branch coverage on changed lines.