feat(monitors): Monitors absorbs the Status page; Status is removed (#5270)
CI / lint (push) Successful in 4s
CI / integration (push) Successful in 1m10s
CI / unit (push) Successful in 1m36s
CI / publish (push) Successful in 1m28s

Status repeated the Monitors page's counts, check strip and uptime. Monitors
now has one header row and every column Status had: the last 30 checks,
latest response with its trend, uptime over 24h / 7d / 30d (the range picker
goes) and TLS days on every row. /status and /status/rows and the nav link
are removed outright; the dashboard Overview (/status/widget) stays and its
Details link goes to Monitors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 15:36:38 -04:00
co-authored by Claude Opus 5.5
parent eaf9f1bcb8
commit 434127b3e1
16 changed files with 72 additions and 256 deletions
+12 -16
View File
@@ -14,7 +14,6 @@ from sqlalchemy import func, select
from steward.core.formatting import status_class
from steward.core.status import _heartbeat_title, _last_n_results, _uptime_by_monitor
from steward.core.time_range import parse_range
from steward.models.monitors import MONITOR_TYPE_VALUES, Monitor, MonitorResult, MonitorType
HTTP_METHODS = ("GET", "HEAD", "POST")
@@ -171,40 +170,37 @@ def problems_first(rows: list[dict]) -> list[dict]:
return sorted(rows, key=lambda d: (STATE_ORDER[d["state"]], d["monitor"].name.lower()))
async def monitor_rows(db, range_key: str, thresholds: dict):
"""Per-monitor state: latest result, heartbeat strip, uptime and TLS days,
problems first.
async def monitor_rows(db, thresholds: dict):
"""Per-monitor state: latest result, heartbeat strip, latency trend,
uptime over 24h / 7d / 30d and TLS days, problems first.
Returns (rows, counts, range_key). `counts` has up (degraded included),
degraded, down, pending and paused. `range_key` picks the uptime window
surfaced (24h/7d/30d; anything else falls back to 24h) and comes back
normalised.
Returns (rows, counts). `counts` has up (degraded included), degraded,
down, pending and paused. A monitor is degraded on its 24h uptime.
"""
_since, range_key = parse_range(range_key)
from steward.core.status import sparkline_svg
monitors = list((await db.execute(select(Monitor).order_by(Monitor.name))).scalars())
ids = [m.id for m in monitors]
recent = await _last_n_results(db, ids)
uptime = await _uptime_by_monitor(db, ids)
# Map the selected range to the uptime bucket we surface in the list.
bucket = {"24h": "24h", "7d": "7d", "30d": "30d"}.get(range_key, "24h")
now = datetime.now(timezone.utc)
data = []
for m in monitors:
rows = recent.get(m.id, [])
latest = rows[-1] if rows else None
uptime_pct = uptime.get(m.id, {}).get(bucket)
windows = uptime.get(m.id, {})
tls_days = None
if latest and latest.tls_expires_at:
tls_days = (latest.tls_expires_at - now).total_seconds() / 86400.0
times = [r.response_ms for r in rows if r.response_ms is not None]
data.append({
"monitor": m,
"latest": latest,
"state": row_state(m, latest, uptime_pct, thresholds),
"state": row_state(m, latest, windows.get("24h"), thresholds),
"heartbeat": [{"state": "up" if r.is_up else "down",
"title": _heartbeat_title(m.type, r)} for r in rows],
"uptime_pct": uptime_pct,
"uptime": uptime.get(m.id, {}),
"spark": sparkline_svg(times, width=56, height=16, stroke="currentColor") if len(times) > 1 else "",
"uptime": windows,
"tls_days": tls_days,
"down_since": None,
"down_reason": down_reason(latest) if latest is not None and not latest.is_up else None,
@@ -215,7 +211,7 @@ async def monitor_rows(db, range_key: str, thresholds: dict):
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
return problems_first(data), counts
async def down_since(db, monitor_id: str):