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:
@@ -16,7 +16,6 @@ from steward.core.audit import audit_request
|
||||
from steward.models.users import UserRole
|
||||
from steward.models.monitors import Monitor, MonitorType, MONITOR_TYPE_VALUES
|
||||
from steward.core.status import _heartbeat_title, streak_start
|
||||
from steward.core.time_range import DEFAULT_RANGE
|
||||
from steward.models.hosts import Host
|
||||
from steward.monitors import service as monitor_svc
|
||||
|
||||
@@ -47,23 +46,15 @@ def _form_opts(form, mtype: str) -> dict:
|
||||
@require_role(UserRole.viewer)
|
||||
async def index():
|
||||
poll_interval = current_app.config.get("MONITORS_POLL_INTERVAL", 60)
|
||||
return await render_template(
|
||||
"monitors/index.html",
|
||||
poll_interval=poll_interval,
|
||||
current_range=request.args.get("range", DEFAULT_RANGE),
|
||||
)
|
||||
return await render_template("monitors/index.html", poll_interval=poll_interval)
|
||||
|
||||
|
||||
@monitors_bp.get("/rows")
|
||||
@require_role(UserRole.viewer)
|
||||
async def rows():
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
data, counts, range_key = await monitor_svc.monitor_rows(
|
||||
db, request.args.get("range", DEFAULT_RANGE), current_app.config.get("THRESHOLDS", {}))
|
||||
return await render_template(
|
||||
"monitors/rows.html",
|
||||
monitor_data=data, counts=counts, range_key=range_key,
|
||||
)
|
||||
data, counts = await monitor_svc.monitor_rows(db, current_app.config.get("THRESHOLDS", {}))
|
||||
return await render_template("monitors/rows.html", monitor_data=data, counts=counts)
|
||||
|
||||
|
||||
@monitors_bp.post("/add")
|
||||
@@ -219,8 +210,7 @@ async def widget():
|
||||
widget_id = request.args.get("wid", "0")
|
||||
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
data, counts, _ = await monitor_svc.monitor_rows(
|
||||
db, DEFAULT_RANGE, current_app.config.get("THRESHOLDS", {}))
|
||||
data, counts = await monitor_svc.monitor_rows(db, current_app.config.get("THRESHOLDS", {}))
|
||||
if type_filter in MONITOR_TYPE_VALUES:
|
||||
data = [d for d in data if d["monitor"].type == type_filter]
|
||||
# Counted after the type filter, so the verdict speaks for what is listed.
|
||||
|
||||
+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