feat(widgets): Overview verdict, pending monitors' age, fullest filesystems first (#5810)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m23s
CI / unit (push) Successful in 1m33s
CI / publish (push) Successful in 1m16s

- Overview: a verdict line when something is wrong ('1 alert firing, 1
  monitor down.'); its picker description now says what it lists (alerts,
  monitors down, anomalies). Host figures have their home in Hosts.
  Comments no longer name the Status page that was folded into Monitors.
- Monitors widget: a monitor not checked yet says when it was added.
- Host page: filesystems fullest first, by the worse of space and inodes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 10:11:37 -04:00
co-authored by Claude Opus 5.5
parent 5c7a4cf9a0
commit b29b160baa
8 changed files with 45 additions and 6 deletions
+4 -4
View File
@@ -1,11 +1,11 @@
# steward/core/status.py
"""Unified monitor-status aggregation.
A single readable "is everything up?" surface (the Status page + dashboard
A single readable "is everything up?" surface (the dashboard Overview
widget) is assembled from heterogeneous monitors. The core unified Monitor
entity (ping/dns/http) contributes via `monitor_status_source`; plugins may
register their own sources from setup(). Each source is an async callable(db)
returning normalised StatusEntry objects so the Status page never imports
returning normalised StatusEntry objects so the Overview never imports
plugin tables directly.
"""
from __future__ import annotations
@@ -62,7 +62,7 @@ async def collect_status(db) -> list[StatusEntry]:
"""Gather entries from every registered source, down-first then by name.
A failing source is logged and skipped so one broken plugin can't blank
the whole Status page.
the whole Overview.
"""
entries: list[StatusEntry] = []
for src in list(_SOURCES):
@@ -210,7 +210,7 @@ def _heartbeat_title(mtype: str, r) -> str:
async def monitor_status_source(db) -> list[StatusEntry]:
"""Contribute every enabled Monitor (all types) to the Status page."""
"""Contribute every enabled Monitor (all types) to the Overview."""
from steward.models.monitors import Monitor
monitors = list((await db.execute(
+1 -1
View File
@@ -65,7 +65,7 @@ WIDGET_REGISTRY: dict[str, dict] = {
"status_overview": {
"key": "status_overview",
"label": "Overview",
"description": "What needs attention across Steward: hosts, monitors and firing alerts, problems listed with since-times",
"description": "What needs attention across Steward: firing alerts, monitors down and metric anomalies, each with since when",
"hx_url": "/status/widget",
"detail_url": "/monitors/",
"plugin": None,
+1 -1
View File
@@ -27,7 +27,7 @@
{% if session.user_id %}<a href="/monitors/{{ m.id }}">{{ m.name }}</a>{% else %}{{ m.name }}{% endif %}
{%- if d.state == 'down' %}<span class="w-sub tone-crit">went down {{ d.down_since|ago if d.down_since else '' }}{% if d.down_reason %} · {{ d.down_reason }}{% endif %}</span>
{%- elif d.state == 'degraded' %}<span class="w-sub tone-warn">{% if (d.latency or status_class(latest.response_ms, 'latency') if latest else none) == 'crit' %}slow replies{% else %}checks failing now and then{% endif %}</span>
{%- elif d.state == 'pending' %}<span class="w-sub">not checked yet</span>{% endif %}
{%- elif d.state == 'pending' %}<span class="w-sub">not checked yet{% if m.created_at %} · added {{ m.created_at|ago }}{% endif %}</span>{% endif %}
</td>
{% set lat = (d.latency or status_class(latest.response_ms, 'latency')) if latest and latest.is_up and latest.response_ms is not none else none %}
<td class="{{ 'tone-' ~ lat if lat in ('warn', 'crit') }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>
+7
View File
@@ -30,6 +30,13 @@
{% if entries %}✓ All {{ up }} monitors are up and no alert is firing.{% else %}No monitors configured, and no alert is firing.{% endif %}
</div>
{% else %}
{# The verdict when something is wrong (#5810): what, in counts, worst first. #}
{% set said = [] %}
{% if firing %}{% set _ = said.append(firing|length ~ " alert" ~ ('' if firing|length == 1 else 's') ~ " firing") %}{% endif %}
{% if down %}{% set _ = said.append(down ~ " monitor" ~ ('' if down == 1 else 's') ~ " down") %}{% endif %}
{% if anomalies %}{% set _ = said.append(anomalies|length ~ " metric" ~ ('' if anomalies|length == 1 else 's') ~ " unusual") %}{% endif %}
{% if pending %}{% set _ = said.append(pending ~ " monitor" ~ ('' if pending == 1 else 's') ~ " waiting for a first check") %}{% endif %}
{% if said %}<div class="w-verdict {{ 'tone-crit' if firing or down else 'tone-warn' }}">{{ said|join(', ')|capitalize }}.</div>{% endif %}
<div style="display:grid;gap:1px;">
{% for a in alerts[:6] %}
<div style="display:flex;align-items:center;gap:0.5rem;padding:0.2rem 0;font-size:0.82rem;">