feat(ui): alerts, monitors, hosts, schedules and maintenance lists put problems first, with since-times (#5210)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m8s
CI / unit (push) Successful in 1m25s
CI / publish (push) Successful in 1m17s

#5210 "Problems first, with since-time, across every list" (milestone 477),
first batch.

- Alerts: the Active list reads firing, no data, acknowledged, pending, the
  longest-running first, and re-polls itself. An acknowledged alert says how
  long it has been breaching (from the alert's events; acknowledging moves
  last_transition_at) and, apart from that, who acknowledged it and when.
  Rules show what each is doing now; disabled rules sort last. The current
  value reads the latest-value table (#5208) instead of plugin_metrics.
- Monitors: a down monitor says "Down 12m · connection refused" in the list.
- Hosts: sorted down, agent gone quiet, pending, healthy. The agent column
  shows "—" for a missing reading (was 0%) and a Stale badge with its age.
  agent_metrics_by_host is gone; the list reads agent_overview_by_host.
- Schedules: failing first (failed or interrupted run, or a firing that could
  not start one), then running, then the rest, paused last; status badge
  from the shared states.
- Maintenance windows: active (ending soonest), then upcoming (starting
  soonest), with expired folded away; each says when it ends/starts/ended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 23:58:56 -04:00
co-authored by Claude Opus 5.5
parent a39633626b
commit 5a14e7c693
16 changed files with 335 additions and 103 deletions
+20
View File
@@ -152,6 +152,20 @@ def row_state(monitor: Monitor, latest, uptime_pct, thresholds: dict) -> str:
return "up"
def down_reason(latest) -> str | None:
"""Why a failed check failed, in a few words: the probe's error, else the
HTTP status, else a content mismatch; None when the result says nothing."""
if latest is None:
return None
if latest.error_msg:
return latest.error_msg
if latest.status_code:
return f"HTTP {latest.status_code}"
if latest.content_matched is False:
return "expected content not found"
return None
def problems_first(rows: list[dict]) -> list[dict]:
"""Down, then degraded, then pending, up and paused; by name within each."""
return sorted(rows, key=lambda d: (STATE_ORDER[d["state"]], d["monitor"].name.lower()))
@@ -191,7 +205,13 @@ async def monitor_rows(db, range_key: str, thresholds: dict):
"title": _heartbeat_title(m.type, r)} for r in rows],
"uptime_pct": uptime_pct,
"tls_days": tls_days,
"down_since": None,
"down_reason": down_reason(latest) if latest is not None and not latest.is_up else None,
})
# When each down monitor went down (a query per down monitor: few, and indexed).
for d in data:
if d["state"] == "down":
d["down_since"] = await down_since(db, d["monitor"].id)
counts = {k: sum(d["state"] == k for d in data) for k in STATE_ORDER}
counts["up"] += counts["degraded"]
return problems_first(data), counts, range_key