feat(dashboard): unified Hosts widget + grouped widget picker
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 8s
CI / integration (push) Successful in 2m15s
CI / publish (push) Successful in 56s

Bring the dashboard in line with the unified host IA + improved displays.

- New core "Hosts — Overview" widget (/hosts/overview/widget): one row per host
  combining monitor status (ping dot + latency, uptime 24h) with the agent
  glance (CPU / memory / disk root + stale flag), each row linking to the host
  hub. Reads agent data from the generic PluginMetric table via a core-safe
  _agent_overview_by_host helper (no host_agent import); freshness vs the
  plugin's stale window. The granular Ping/DNS/Uptime/Agent widgets stay.
- Group the add-widget picker into Core monitors / Monitoring capabilities /
  Integrations (a `group` field on every WIDGET_REGISTRY entry + section
  headings in _edit_panels.html), matching the Settings → Plugins taxonomy.
- Fix the agent fleet widget rows to link to the host hub (/hosts/<id>) instead
  of the old /plugins/host_agent/<id>/ page.

Scribe #903.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:00:12 -04:00
parent 42f7840c26
commit 609bd78af2
5 changed files with 172 additions and 2 deletions
+36
View File
@@ -32,6 +32,16 @@ WIDGET_REGISTRY: dict[str, dict] = {
"poll": True,
"params": [],
},
"hosts_overview": {
"key": "hosts_overview",
"label": "Hosts — Overview",
"description": "One row per host: monitor status + uptime and agent CPU/memory/disk, linking to the host hub",
"hx_url": "/hosts/overview/widget",
"detail_url": "/hosts/",
"plugin": None,
"poll": True,
"params": [],
},
"uptime_summary": {
"key": "uptime_summary",
"label": "Uptime / SLA",
@@ -292,8 +302,34 @@ WIDGET_REGISTRY: dict[str, dict] = {
}
# Group each widget for the picker (Core monitors / Monitoring capabilities /
# Integrations), mirroring the Settings → Plugins taxonomy. Capability plugins
# are host/monitoring facets; traefik/unifi are discrete vendor integrations.
_CORE_WIDGETS = {"ping", "dns", "status_overview", "uptime_summary", "hosts_overview"}
_INTEGRATION_PLUGINS = {"traefik", "unifi"}
GROUP_LABELS = {
"core": "Core monitors",
"capability": "Monitoring capabilities",
"integration": "Integrations",
}
GROUP_ORDER = ["core", "capability", "integration"]
def _group_for(w: dict) -> str:
if w["key"] in _CORE_WIDGETS:
return "core"
if w.get("plugin") in _INTEGRATION_PLUGINS:
return "integration"
return "capability"
for _w in WIDGET_REGISTRY.values():
_w["group"] = _group_for(_w)
def register_widget(definition: dict) -> None:
"""Register a widget at runtime (used by external plugins)."""
definition.setdefault("group", _group_for(definition))
WIDGET_REGISTRY[definition["key"]] = definition