feat(hosts): the live strip shows the hottest sensor's temperature, get_host marks sensors that don't count, and the Monitors card's button reads "Add" (#6803)
- The host page's live strip gains a Temperature tile: temp_c_max (real thermometers only), its last 6 h and the sensor setting it, graded by Settings → Thresholds and counted in the header's worst reading, in °C. - MCP get_host: each temperature carries `counted`; an unwired input is `counted: false` with `not_counted_because`, listed after the real ones. host_agent registers the judge (judge_sensors), so core imports no plugin. - The Monitors card's "Add monitor" wrapped into two lines beside the uptime; it reads "Add", titled "Add a monitor for this host". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1003,7 +1003,7 @@ def _new_token_pair() -> tuple[str, str]:
|
||||
@host_agent_bp.get("/vitals/<host_id>")
|
||||
@require_role(UserRole.viewer)
|
||||
async def host_vitals(host_id: str):
|
||||
"""Compact live vitals strip (CPU/MEM/DISK/LOAD + pressure) for the Hosts hub.
|
||||
"""Compact live vitals strip (CPU/MEM/DISK/LOAD/temperature + pressure) for the Hosts hub.
|
||||
|
||||
Self-contained fragment, polled by hosts/detail.html. Renders nothing until
|
||||
the agent reports — the Agent panel below then carries provisioning.
|
||||
@@ -1018,15 +1018,16 @@ async def host_vitals(host_id: str):
|
||||
latest = await _latest_metrics_for_host(db, host.name) if reg else {}
|
||||
norms = await host_svc.host_norms(db, host.name) if reg else {}
|
||||
|
||||
# The last 6 h for the at-a-glance sparklines (cpu/mem/load host-level,
|
||||
# The last 6 h for the at-a-glance sparklines (cpu/mem/load/temp host-level,
|
||||
# disk from the root mount), averaged into ~120 points in SQL, so the
|
||||
# line spans the whole window and only what is drawn is fetched.
|
||||
series_raw: dict[str, list[float]] = {"cpu": [], "mem": [], "disk": [], "load": []}
|
||||
series_raw: dict[str, list[float]] = {"cpu": [], "mem": [], "disk": [], "load": [], "temp": []}
|
||||
if reg:
|
||||
since = datetime.now(timezone.utc) - timedelta(hours=6)
|
||||
for key, resource, metric in (("cpu", host.name, "cpu_pct"),
|
||||
("mem", host.name, "mem_used_pct"),
|
||||
("load", host.name, "load_1m"),
|
||||
("temp", host.name, "temp_c_max"),
|
||||
("disk", host.name + ":/", "disk_used_pct")):
|
||||
series_raw[key] = [v for _t, v in await metric_points(
|
||||
db, SOURCE_MODULE, resource, metric, since, target=120)]
|
||||
@@ -1053,9 +1054,16 @@ async def host_vitals(host_id: str):
|
||||
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")
|
||||
# The hottest sensor that counts (temp_c_max leaves out unwired inputs),
|
||||
# named so the tile says what it is reading.
|
||||
temp = hostlvl.get("temp_c_max")
|
||||
prefix = host.name + ":temp:"
|
||||
hottest = hottest_sensor(host.name, {r[len(prefix):]: m["temp_c"] for r, m in latest.items()
|
||||
if r.startswith(prefix) and m.get("temp_c") is not None})
|
||||
thresholds = current_app.config.get("THRESHOLDS", {})
|
||||
worst = worst_reading((("CPU", cpu, "cpu"), ("Memory", mem, "mem"), ("Disk /", disk_root, "disk"),
|
||||
("Inodes on /", inodes_root, "disk"), ("Load per core", load_per_core, "load")),
|
||||
("Inodes on /", inodes_root, "disk"), ("Load per core", load_per_core, "load"),
|
||||
("Temperature", temp, "temp")),
|
||||
thresholds, host_svc.host_normals(norms, thresholds))
|
||||
# Each tile's grade; CPU, memory and load within the host's own normal
|
||||
# for the hour read healthy, and say what is usual (#5742).
|
||||
@@ -1068,7 +1076,7 @@ async def host_vitals(host_id: str):
|
||||
return await render_template(
|
||||
"_host_vitals.html",
|
||||
reg=reg, cpu=cpu, mem=mem, disk_root=disk_root, load_per_core=load_per_core,
|
||||
inodes_root=inodes_root,
|
||||
inodes_root=inodes_root, temp=temp, hottest=hottest[0].rsplit(" at ", 1)[0] if hottest else None,
|
||||
sparks=sparks, psi=psi, reporting=reporting, stale=stale, worst=worst,
|
||||
grades=grades, usual=usual,
|
||||
clock=clock_words(reg.clock_offset_s if reg else None, current_app.config.get("THRESHOLDS", {})),
|
||||
|
||||
Reference in New Issue
Block a user