feat(dashboard): Monitors widget carries uptime; Uptime / SLA retired (#5250)

The Monitors widget opens with a verdict ("1 of 5 monitors down."), then one
row per monitor under a single header: latency and uptime over 24h, 7d and
30d, down monitors first with why and since when, the name linking to the
monitor. Counts follow the type filter. Problems always show; the limit
trims only the healthy rest ("+N more up").

Uptime / SLA (uptime_summary) repeated those numbers per host; migration
0033 moves dashboards onto Monitors. Shared widget styles (.w-verdict,
.w-table, .w-sub, .w-more) carry the widget-shape ruling for the widgets
that follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 13:12:58 -04:00
co-authored by Claude Opus 5.5
parent 4dcf958022
commit b5fcccba5f
10 changed files with 149 additions and 126 deletions
+7 -5
View File
@@ -221,14 +221,16 @@ async def widget():
async with current_app.db_sessionmaker() as db:
data, counts, _ = await monitor_svc.monitor_rows(
db, DEFAULT_RANGE, current_app.config.get("THRESHOLDS", {}))
up, down, pending = counts["up"], counts["down"], counts["pending"]
if type_filter in MONITOR_TYPE_VALUES:
data = [d for d in data if d["monitor"].type == type_filter]
if show_down_only:
data = [d for d in data if not (d["latest"] and d["latest"].is_up)]
# Counted after the type filter, so the verdict speaks for what is listed.
counts = {k: sum(d["state"] == k for d in data) for k in ("up", "degraded", "down", "pending", "paused")}
problems = [d for d in data if d["state"] in ("down", "degraded", "pending")]
rest = [] if show_down_only else [d for d in data if d["state"] not in ("down", "degraded", "pending")]
shown = max(0, limit - len(problems))
return await render_template(
"monitors/widget.html",
monitor_data=data[:limit], up=up, down=down, pending=pending,
show_down_only=show_down_only, widget_id=widget_id,
problems=problems, rest=rest[:shown], more=len(rest) - min(len(rest), shown),
counts=counts, total=len(data), show_down_only=show_down_only, widget_id=widget_id,
)
+1
View File
@@ -204,6 +204,7 @@ async def monitor_rows(db, range_key: str, thresholds: dict):
"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, {}),
"tls_days": tls_days,
"down_since": None,
"down_reason": down_reason(latest) if latest is not None and not latest.is_up else None,