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
+3 -47
View File
@@ -1,13 +1,11 @@
"""The Status page says what is wrong, and since when, before anything else
(#5120)."""
"""How long a monitor has been in its state (#5120). The Status page that
first showed it was folded into Monitors (#5270)."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
import pytest
from quart import render_template
from steward.core.status import StatusEntry, streak_start
from tests.test_style_tokens import hex_colours
from steward.core.status import streak_start
T0 = datetime(2026, 10, 6, 18, 0, tzinfo=timezone.utc)
@@ -26,45 +24,3 @@ def _checks(*ups):
def test_streak_start(ups, since, at_least):
checks = _checks(*ups)
assert streak_start(checks) == ((checks[since].checked_at if since is not None else None), at_least)
async def _rows(app, entries, alerts):
async with app.test_request_context("/status/rows"):
return await render_template(
"status/rows.html", entries=entries, alerts=alerts,
problems=[e for e in entries if e.status == "down"],
up=sum(e.status == "up" for e in entries), down=sum(e.status == "down" for e in entries), pending=0)
async def test_the_problems_come_first_and_say_since_when(app):
now = datetime.now(timezone.utc)
entries = [
StatusEntry(kind="http", key="1", name="shop-api", target="https://shop/x", status="down",
since=now - timedelta(minutes=12), uptime={"24h": 93.0}, tls_days=2.0),
StatusEntry(kind="icmp", key="2", name="nas", target="10.0.0.5", status="up",
since=now - timedelta(days=3), since_at_least=True, uptime={"24h": 100.0}),
]
state = SimpleNamespace(state=SimpleNamespace(value="firing"), no_data_since=None,
last_transition_at=now - timedelta(minutes=4))
rule = SimpleNamespace(id="r1", name="CPU high", resource_name="web01", metric_name="cpu_pct",
operator=SimpleNamespace(value=">"), threshold=90)
page = await _rows(app, entries, [(state, rule)])
attention = page.index("Needs attention")
assert attention < page.index("Recent checks") # before the full list
problem_card = page[attention:page.index("</section>")]
assert "shop-api" in problem_card and "nas" not in problem_card
assert "down since <time" in problem_card and ">12m ago</time>" in problem_card
assert "CPU high" in problem_card and "web01 CPU &gt; 90%" in problem_card and ">4m ago</time>" in problem_card
assert '<span class="badge badge-crit">Firing</span>' in problem_card
# Uptime and TLS coloured by Settings → Thresholds, not by literals.
assert '<span class="tone-crit">93.00%</span>' in page and '<span class="tone-ok">100.00%</span>' in page
assert 'class="tone-crit" title="TLS certificate expires in 2 days">2d' in page
assert not hex_colours(page), hex_colours(page)
async def test_a_quiet_board_has_no_problem_card(app):
entries = [StatusEntry(kind="icmp", key="2", name="nas", target="10.0.0.5", status="up")]
page = await _rows(app, entries, [])
assert "Needs attention" not in page and "nas" in page
assert "No monitors yet" in await _rows(app, [], [])