feat(dashboard): Hosts widget worst first, load against cores, one home per fact (#5251)
The Hosts — Overview widget opens with a verdict ("VDNT-Util01: CPU high."
or "All 3 hosts healthy."), then one row per host under one header: CPU,
memory, disk / and load. Rows sort by host_glance: a down monitor, a
reading past critical, a quiet agent, a warning, then the healthy. Load
reads "16.8 / 8 cores", graded per core as Settings → Thresholds intends
(cores counted from the per-core CPU sub-resources, as on the host page).
A quiet agent's last figures are shown uncoloured, with when it last reported.
Ping and 24h uptime are gone from this widget: they belong to Monitors. So
are the unlabelled CPU sparkline and the 30-day uptime query it no longer
needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,13 +84,59 @@ async def test_a_quiet_overview_says_so(app):
|
||||
assert ">host<" in page and 'href="/alerts/"' not in page # the share view has no links
|
||||
|
||||
|
||||
async def test_the_hosts_widget_shows_load(app):
|
||||
host = SimpleNamespace(id="h1", name="web-1")
|
||||
agent = {"web-1": {"cpu_pct": 12.0, "mem_used_pct": 40.0, "load_1m": 0.734, "fresh": True}}
|
||||
def _host(hid, name):
|
||||
return SimpleNamespace(id=hid, name=name)
|
||||
|
||||
|
||||
def test_hosts_sort_worst_first():
|
||||
"""#5251: a down monitor, then a reading past critical, then a quiet agent,
|
||||
then a warning, then the healthy, by name; a host with nothing last."""
|
||||
from steward.core.settings import to_thresholds_cfg
|
||||
from steward.hosts.service import host_glance
|
||||
th = to_thresholds_cfg({})
|
||||
hosts = [_host("a", "alpha"), _host("b", "busy"), _host("d", "dead"), _host("q", "quiet"),
|
||||
_host("w", "warm"), _host("n", "nothing")]
|
||||
status = {"d": {"state": "down"}, "a": {"state": "up"}}
|
||||
agent = {"alpha": {"cpu_pct": 10.0, "fresh": True}, "busy": {"cpu_pct": 99.0, "fresh": True},
|
||||
"quiet": {"cpu_pct": 99.0, "fresh": False},
|
||||
"warm": {"cpu_pct": 10.0, "load_per_core": 85, "fresh": True}}
|
||||
rows = host_glance(hosts, status, agent, th)
|
||||
assert [(r["host"].name, r["state"]) for r in rows] == [
|
||||
("dead", "down"), ("busy", "crit"), ("quiet", "stale"), ("warm", "warn"),
|
||||
("alpha", "up"), ("nothing", "unknown")]
|
||||
assert rows[1]["worst"][0] == "CPU" and rows[3]["worst"][0] == "Load"
|
||||
assert rows[2]["worst"] is None # a quiet agent's old figures aren't graded
|
||||
|
||||
|
||||
async def test_the_hosts_widget_gives_a_verdict_and_load_against_cores(app):
|
||||
from datetime import datetime, timezone
|
||||
rows = [
|
||||
{"host": _host("h2", "VDNT-Util01"), "state": "crit", "worst": ("CPU", 99.0, "crit"),
|
||||
"agent": {"cpu_pct": 99.0, "mem_used_pct": 28.0, "disk_root": 52.0, "load_1m": 16.77,
|
||||
"cores": 8, "load_per_core": 210, "fresh": True}},
|
||||
{"host": _host("h1", "VDNT-Docker02"), "state": "up", "worst": None,
|
||||
"agent": {"cpu_pct": 1.0, "mem_used_pct": 4.0, "disk_root": 44.0, "load_1m": 0.92,
|
||||
"cores": 4, "load_per_core": 23, "fresh": True}},
|
||||
{"host": _host("h3", "nas"), "state": "stale", "worst": None,
|
||||
"agent": {"cpu_pct": 5.0, "fresh": False, "last_at": datetime(2026, 10, 7, tzinfo=timezone.utc)}},
|
||||
]
|
||||
async with app.test_request_context("/hosts/overview/widget"):
|
||||
page = await render_template("hosts/overview_widget.html", hosts=[host], host_status={},
|
||||
uptime={}, agent=agent, cpu_sparks={})
|
||||
assert ">load</span> 0.73" in page
|
||||
session["user_id"] = "u1"
|
||||
page = await render_template("hosts/overview_widget.html", rows=rows)
|
||||
assert "2 of 3 hosts need a look." in page
|
||||
assert page.index("VDNT-Util01") < page.index("VDNT-Docker02")
|
||||
assert "16.8 / 8 cores" in page and "0.9 / 4 cores" in page
|
||||
assert "agent last reported" in page
|
||||
assert page.count("<th>") == 5 and ">ping<" not in page and "24h" not in page
|
||||
assert not hex_colours(page), hex_colours(page)
|
||||
|
||||
|
||||
async def test_one_busy_host_is_named_in_the_verdict(app):
|
||||
rows = [{"host": _host("h2", "web-1"), "state": "crit", "worst": ("CPU", 99.0, "crit"),
|
||||
"agent": {"cpu_pct": 99.0, "fresh": True}}]
|
||||
async with app.test_request_context("/hosts/overview/widget"):
|
||||
page = await render_template("hosts/overview_widget.html", rows=rows)
|
||||
assert "web-1:" in page and "CPU high." in page and "tone-crit" in page
|
||||
|
||||
|
||||
async def test_a_widget_polls_at_its_own_pace(app):
|
||||
|
||||
Reference in New Issue
Block a user