feat(hosts): one health header, graded by one set of thresholds (#5122)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 47s
CI / unit (push) Failing after 1m14s
CI / publish (push) Skipped

The host page's vitals strip becomes its health header: Live or Stale,
then one sentence — "Memory 93% is past its critical threshold", "Every
reading is within its thresholds", or, for a stale host, "No report since
20m ago; the readings below are from then" — and last seen as |ago. CPU,
memory, root disk and load per core follow as stat tiles graded by
Settings → Thresholds, with their sparklines.

The full-metrics page drew its bars and gauges at a hard-coded 70/90
(temperatures 70/85) while every other page used the configured cutoffs
(80/90 by default), so a 75 % CPU was amber here and fine everywhere else.
bar() now takes the metric kind and colours its .meter-fill through
status_class; gauges, temperatures and per-core bars do the same.

The host's monitor rows move onto the Monitors list's shapes: a status dot,
the name linking to the monitor's page, Ping/TCP/DNS/HTTP, pill-up/
pill-down, latency and uptime through status_class, labelled icon buttons.
HEX_CEILING drops to 54.

New formatting.worst_reading(readings, thresholds) picks the reading
furthest past its cutoff, the first of equals winning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 17:58:22 -04:00
co-authored by Claude Opus 5.5
parent 629c466fd9
commit 8c19f3c21f
8 changed files with 177 additions and 73 deletions
+7 -3
View File
@@ -14,6 +14,7 @@ from steward.models.users import UserRole
from sqlalchemy import select, func, or_, and_
from datetime import timedelta
from steward.core.formatting import worst_reading
from steward.core.settings import get_setting, public_base_url
from steward.core.time_range import parse_range, RANGE_OPTIONS
from steward.models.hosts import Host
@@ -723,11 +724,14 @@ async def host_vitals(host_id: str):
ls = reg.last_seen_at if reg else None
reporting = bool(reg) and ls is not None
stale = reporting and (datetime.now(timezone.utc) - ls).total_seconds() > _stale_after_seconds()
cpu, mem = hostlvl.get("cpu_pct"), hostlvl.get("mem_used_pct")
worst = worst_reading((("CPU", cpu, "cpu"), ("Memory", mem, "mem"), ("Disk /", disk_root, "disk"),
("Load per core", load_per_core, "load")),
current_app.config.get("THRESHOLDS", {}))
return await render_template(
"_host_vitals.html",
reg=reg, cpu=hostlvl.get("cpu_pct"), mem=hostlvl.get("mem_used_pct"),
disk_root=disk_root, load_per_core=load_per_core, sparks=sparks, psi=psi,
reporting=reporting, stale=stale,
reg=reg, cpu=cpu, mem=mem, disk_root=disk_root, load_per_core=load_per_core,
sparks=sparks, psi=psi, reporting=reporting, stale=stale, worst=worst,
)