feat(dashboard): a curated default layout, merged duplicate widgets, a labelled Overview (#5213)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 50s
CI / unit (push) Failing after 1m16s
CI / publish (push) Skipped

- New installs start from widgets.RECOMMENDED, packed row by row for the
  enabled plugins; the edit drawer can reset a dashboard to it.
- Host Agent — Resources folds into Hosts — Overview (which gains load 1m),
  UniFi — Devices into UniFi — Overview; migration 0032 moves existing
  dashboards onto the survivors.
- The Overview labels what it counts (hosts, monitors up/down/pending,
  alerts firing) and lists firing alerts and down monitors with since-times.
- Uptime polls every 5 minutes (poll_seconds) instead of every cycle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 00:42:04 -04:00
co-authored by Claude Opus 5.5
parent 5ec930bf76
commit 5492336f1e
18 changed files with 381 additions and 309 deletions
+49 -36
View File
@@ -9,6 +9,9 @@
# type - "select" (only type currently supported)
# default - default value (int or str determines how it's parsed from the form)
# options - list of {value, label} dicts
#
# "poll" refreshes the cell every monitor poll interval; "poll_seconds"
# overrides that cadence for a widget whose data moves slowly.
from __future__ import annotations
WIDGET_REGISTRY: dict[str, dict] = {
@@ -60,7 +63,7 @@ WIDGET_REGISTRY: dict[str, dict] = {
"status_overview": {
"key": "status_overview",
"label": "Overview",
"description": "At-a-glance summary — host count, monitor up/down/pending, and alerts",
"description": "What needs attention across Steward: hosts, monitors and firing alerts, problems listed with since-times",
"hx_url": "/status/widget",
"detail_url": "/status/",
"plugin": None,
@@ -70,7 +73,7 @@ WIDGET_REGISTRY: dict[str, dict] = {
"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",
"description": "One row per host: monitor status, uptime, and agent CPU, memory, disk and load, linking to the host page",
"hx_url": "/hosts/overview/widget",
"detail_url": "/hosts/",
"plugin": None,
@@ -84,7 +87,9 @@ WIDGET_REGISTRY: dict[str, dict] = {
"hx_url": "/hosts/uptime/widget",
"detail_url": "/hosts/uptime",
"plugin": None,
"poll": False,
"poll": True,
# Uptime moves slowly and reads 30 days of checks: every 5 minutes, not every poll.
"poll_seconds": 300,
"params": [],
},
"traefik_routers": {
@@ -184,29 +189,6 @@ WIDGET_REGISTRY: dict[str, dict] = {
},
],
},
"unifi_devices": {
"key": "unifi_devices",
"label": "UniFi — Devices",
"description": "Network infrastructure devices with online/offline state",
"hx_url": "/plugins/unifi/widget/devices",
"detail_url": "/plugins/unifi/",
"plugin": "unifi",
"poll": True,
"params": [
{
"key": "type_filter",
"label": "Device type",
"type": "select",
"default": "all",
"options": [
{"value": "all", "label": "All devices"},
{"value": "wireless", "label": "Wireless APs"},
{"value": "wired", "label": "Wired only"},
{"value": "offline", "label": "Offline devices"},
],
},
],
},
"docker_containers": {
"key": "docker_containers",
"label": "Docker — Services & failures",
@@ -260,16 +242,6 @@ WIDGET_REGISTRY: dict[str, dict] = {
"poll": True,
"params": [],
},
"host_resources": {
"key": "host_resources",
"label": "Host Agent — Resources",
"description": "Fleet view: CPU, memory, disk, and load per host — each with a trend sparkline",
"hx_url": "/plugins/host_agent/widget",
"detail_url": "/hosts/",
"plugin": "host_agent",
"poll": True,
"params": [],
},
"host_resource_history": {
"key": "host_resource_history",
"label": "Host Agent — History Graph",
@@ -301,6 +273,47 @@ WIDGET_REGISTRY: dict[str, dict] = {
},
}
# Widgets that were merged into another (#5213): migration 0032 moved every
# placement of the old key onto the new one, so nothing here renders them.
RETIRED = {
"host_resources": "hosts_overview", # Hosts — Overview carries cpu/mem/disk/load per host
"unifi_devices": "unifi_overview", # UniFi — Overview lists the offline devices
}
# The recommended dashboard (#5213): what needs attention first, then the
# fleet, then each integration. (key, width, height, config) on the 12-column
# grid, one cell 70px tall; a widget whose plugin is off is skipped and the
# rest close up. Seeded into the first dashboard and offered by
# "Reset to the recommended layout" in edit mode.
RECOMMENDED: list[tuple[str, int, int, dict]] = [
("status_overview", 12, 3, {}),
("hosts_overview", 6, 7, {}),
("monitors", 6, 7, {"show_down_only": "no", "limit": 10}),
("docker_containers", 6, 6, {}),
("docker_resources", 6, 6, {"limit": 10}),
("uptime_summary", 6, 5, {}),
("traefik_routers", 6, 5, {}),
("unifi_overview", 6, 4, {}),
("snmp_devices", 6, 4, {}),
("traefik_request_chart", 12, 4, {}),
]
def recommended_layout(plugins_cfg: dict) -> list[dict]:
"""RECOMMENDED placed on the grid for what is enabled: left to right,
a new row when the next one does not fit, each row as tall as its
tallest widget. [{key, x, y, w, h, config}]."""
available = {w["key"] for w in get_available_widgets(plugins_cfg)}
out, x, y, row_h = [], 0, 0, 0
for key, w, h, config in RECOMMENDED:
if key not in available:
continue
if x + w > 12:
x, y, row_h = 0, y + row_h, 0
out.append({"key": key, "x": x, "y": y, "w": w, "h": h, "config": config})
x, row_h = x + w, max(row_h, h)
return out
# Group each widget for the picker (Core monitors / Monitoring capabilities /
# Integrations), mirroring the Settings → Plugins taxonomy. Capability plugins