feat(monitors): Monitors absorbs the Status page; Status is removed (#5270)
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:
+12
-16
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user