feat(web): keep the four info cards, enrich them, drop the status-header tiles
The dashboard used to open with six status-header tiles *and* four compact cards restating them (runner state lived in three places at once). The tiles are gone; each card is now the single owner of its subject, enriched with what only the tiles had (CPU%/RAM/disk with gauges) plus what neither had (hostname, kernel, full CPU model, the vmware-host.service row). All four cards share the same <dl> grammar and font scale now -- VMware and Runners were a bordered <table> at a different size/case, which read as a visibly different style sitting next to Host and Cargo cache's plain label/value lists. operations/host.py also gains delete_template()/TemplateDeleteResult, used by the template-delete route in the next commit.
This commit is contained in:
@@ -40,6 +40,19 @@ _MEMINFO = (
|
||||
_UPTIME = "123456.78 987654.32\n"
|
||||
_LOADAVG = "0.50 0.75 1.00 3/456 7890\n"
|
||||
|
||||
# Two cores' worth of /proc/cpuinfo -- the parser takes the FIRST "model name".
|
||||
_CPUINFO = (
|
||||
"processor\t: 0\n"
|
||||
"vendor_id\t: GenuineIntel\n"
|
||||
"model name\t: Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz\n"
|
||||
"cache size\t: 36864 KB\n"
|
||||
"\n"
|
||||
"processor\t: 1\n"
|
||||
"model name\t: Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz\n"
|
||||
)
|
||||
_OSRELEASE = "7.0.0-28-generic\n"
|
||||
_HOSTNAME = "ci-host\n"
|
||||
|
||||
|
||||
def _fake_reader(calls: dict[str, int]) -> Any:
|
||||
def _read(path: str) -> str:
|
||||
@@ -52,6 +65,12 @@ def _fake_reader(calls: dict[str, int]) -> Any:
|
||||
return _UPTIME
|
||||
if path == "/proc/loadavg":
|
||||
return _LOADAVG
|
||||
if path == "/proc/cpuinfo":
|
||||
return _CPUINFO
|
||||
if path == "/proc/sys/kernel/osrelease":
|
||||
return _OSRELEASE
|
||||
if path == "/proc/sys/kernel/hostname":
|
||||
return _HOSTNAME
|
||||
raise AssertionError(f"unexpected proc path: {path}")
|
||||
|
||||
return _read
|
||||
@@ -113,6 +132,61 @@ def test_host_resources_uptime_and_loadavg(monkeypatch: pytest.MonkeyPatch) -> N
|
||||
assert result.load15 == pytest.approx(1.00)
|
||||
|
||||
|
||||
# ── host_resources: static identity fields (CPU model, kernel, hostname) ─────
|
||||
|
||||
|
||||
def test_host_resources_reports_cpu_model_kernel_and_hostname(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
_install_fake_proc(monkeypatch)
|
||||
|
||||
result = host_resources()
|
||||
|
||||
assert result.cpu_model == "Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz"
|
||||
assert result.kernel == "7.0.0-28-generic"
|
||||
assert result.hostname == "ci-host"
|
||||
|
||||
|
||||
def test_host_resources_cpu_model_is_empty_when_cpuinfo_has_no_model_name(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Not every architecture prints ``model name`` (e.g. arm64) -- degrade, don't crash."""
|
||||
calls: dict[str, int] = {}
|
||||
inner = _fake_reader(calls)
|
||||
|
||||
def _read(path: str) -> str:
|
||||
if path == "/proc/cpuinfo":
|
||||
return "processor\t: 0\nBogoMIPS\t: 50.00\n"
|
||||
return str(inner(path))
|
||||
|
||||
monkeypatch.setattr(host_module, "_read_proc", _read)
|
||||
monkeypatch.setattr(host_module.time, "sleep", lambda _s: None)
|
||||
|
||||
assert host_resources().cpu_model == ""
|
||||
|
||||
|
||||
def test_host_resources_unreadable_static_fields_do_not_zero_live_metrics(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""A missing ``/proc/cpuinfo`` must not cost us CPU%/RAM/load as well."""
|
||||
calls: dict[str, int] = {}
|
||||
inner = _fake_reader(calls)
|
||||
|
||||
def _read(path: str) -> str:
|
||||
if path in ("/proc/cpuinfo", "/proc/sys/kernel/osrelease", "/proc/sys/kernel/hostname"):
|
||||
raise OSError("gone")
|
||||
return str(inner(path))
|
||||
|
||||
monkeypatch.setattr(host_module, "_read_proc", _read)
|
||||
monkeypatch.setattr(host_module.time, "sleep", lambda _s: None)
|
||||
|
||||
result = host_resources()
|
||||
|
||||
assert result.cpu_pct == pytest.approx(50.0)
|
||||
assert result.load1 == pytest.approx(0.50)
|
||||
assert (result.cpu_model, result.kernel, result.hostname) == ("", "", "")
|
||||
|
||||
|
||||
# ── host_resources: degrade to zeros on OSError, never raise ────────────────
|
||||
|
||||
|
||||
@@ -134,6 +208,9 @@ def test_host_resources_degrades_to_zeros_on_oserror(monkeypatch: pytest.MonkeyP
|
||||
load1=0.0,
|
||||
load5=0.0,
|
||||
load15=0.0,
|
||||
cpu_model="",
|
||||
kernel="",
|
||||
hostname="",
|
||||
)
|
||||
|
||||
|
||||
@@ -144,6 +221,8 @@ def test_host_resources_degrades_to_zeros_when_second_stat_sample_fails(
|
||||
calls = {"n": 0}
|
||||
|
||||
def _flaky(path: str) -> str:
|
||||
if path.startswith("/proc/sys/kernel/") or path == "/proc/cpuinfo":
|
||||
return "" # static identity reads happen before the live-metric block
|
||||
if path != "/proc/stat":
|
||||
raise AssertionError("should not reach meminfo after stat blows up")
|
||||
calls["n"] += 1
|
||||
|
||||
Reference in New Issue
Block a user