Files
FabledSteward/steward/core/widgets.py
T
bvandeusen bca1b92cc6
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m24s
CI / publish (push) Successful in 58s
feat(host_agent): Netdata-style fleet overview + per-host detail view
Surfaces the metrics the agent now collects (task #868). Adds a viewer-facing
fleet page (/plugins/host_agent/) with per-host cards (CPU/mem/disk/load/temp,
stale flag) and a per-host detail page (/plugins/host_agent/<id>/) — the link
the fleet widget already pointed at but had no route for.

Detail page shows current gauges (CPU, memory incl. swap/cache, load, network
rx/tx, disk I/O, temp max, memory/cpu/io PSI), per-core CPU bars, per-mount
filesystem bars, and per-interface/disk/sensor breakdowns, plus history charts
(utilization %, throughput B/s, load & pressure) over a selectable range.
Charts use a linear epoch-ms x-axis so no Chart.js date adapter is needed.
Stale state uses the plugin's stale_after_seconds threshold. Repoints the
host_resources widget detail link from admin settings to the new fleet page.

Completes milestone #68 (task #867).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 10:27:57 -04:00

309 lines
10 KiB
Python

# steward/core/widgets.py
#
# Central widget registry. Each entry describes one widget type that can be
# placed on a dashboard. Plugin widgets declare which plugin must be enabled.
#
# "params" is a list of configurable parameters for the widget. Each param:
# key - form field name (stored in config_json)
# label - human-readable label
# 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
from __future__ import annotations
WIDGET_REGISTRY: dict[str, dict] = {
"ping": {
"key": "ping",
"label": "Ping",
"description": "Live ping status and latency history for all monitored hosts",
"hx_url": "/ping/rows",
"detail_url": "/ping/",
"plugin": None,
"poll": True,
"params": [],
},
"status_overview": {
"key": "status_overview",
"label": "Status — Overview",
"description": "Unified up/down/pending summary across ping, DNS, and HTTP monitors",
"hx_url": "/status/widget",
"detail_url": "/status",
"plugin": None,
"poll": True,
"params": [],
},
"uptime_summary": {
"key": "uptime_summary",
"label": "Uptime / SLA",
"description": "Per-host rolling uptime % for 24h, 7d, and 30d windows",
"hx_url": "/hosts/uptime/widget",
"detail_url": "/hosts/uptime",
"plugin": None,
"poll": False,
"params": [],
},
"dns": {
"key": "dns",
"label": "DNS",
"description": "DNS resolution status for all monitored hosts",
"hx_url": "/dns/rows",
"detail_url": "/dns/",
"plugin": None,
"poll": True,
"params": [],
},
"traefik_routers": {
"key": "traefik_routers",
"label": "Traefik — Routers",
"description": "Top routers by request rate with error highlights and expiring certs",
"hx_url": "/plugins/traefik/widget",
"detail_url": "/plugins/traefik/",
"plugin": "traefik",
"poll": True,
"params": [],
},
"traefik_access_log": {
"key": "traefik_access_log",
"label": "Traefik — Access Log",
"description": "Traffic origin summary — top IPs, countries, and request distribution",
"hx_url": "/plugins/traefik/widget/access_log",
"detail_url": "/plugins/traefik/",
"plugin": "traefik",
"poll": True,
"params": [
{
"key": "ip_filter",
"label": "IP Filter",
"type": "select",
"default": "all",
"options": [
{"value": "all", "label": "All traffic"},
{"value": "internal", "label": "Internal only"},
{"value": "external", "label": "External only"},
],
},
{
"key": "limit",
"label": "Top IPs shown",
"type": "select",
"default": 10,
"options": [
{"value": 10, "label": "10 entries"},
{"value": 20, "label": "20 entries"},
{"value": 50, "label": "50 entries"},
],
},
],
},
"traefik_request_chart": {
"key": "traefik_request_chart",
"label": "Traefik — Request Chart",
"description": "Request rate and error percentage over time (Chart.js)",
"hx_url": "/plugins/traefik/widget/request_chart",
"detail_url": "/plugins/traefik/",
"plugin": "traefik",
"poll": True,
"params": [
{
"key": "hours",
"label": "Time range",
"type": "select",
"default": 6,
"options": [
{"value": 1, "label": "Last 1 hour"},
{"value": 6, "label": "Last 6 hours"},
{"value": 24, "label": "Last 24 hours"},
],
},
],
},
"unifi_overview": {
"key": "unifi_overview",
"label": "UniFi — Overview",
"description": "WAN status, client counts, offline devices, and active alarms",
"hx_url": "/plugins/unifi/widget",
"detail_url": "/plugins/unifi/",
"plugin": "unifi",
"poll": True,
"params": [],
},
"unifi_clients": {
"key": "unifi_clients",
"label": "UniFi — Top Clients",
"description": "Active wireless and wired clients sorted by bandwidth",
"hx_url": "/plugins/unifi/widget/clients",
"detail_url": "/plugins/unifi/",
"plugin": "unifi",
"poll": True,
"params": [
{
"key": "limit",
"label": "Clients shown",
"type": "select",
"default": 5,
"options": [
{"value": 5, "label": "5 clients"},
{"value": 10, "label": "10 clients"},
{"value": 20, "label": "20 clients"},
],
},
],
},
"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 — Containers",
"description": "Container status overview — running/stopped counts and per-container CPU",
"hx_url": "/plugins/docker/widget",
"detail_url": "/plugins/docker/",
"plugin": "docker",
"poll": True,
"params": [
{
"key": "show_stopped",
"label": "Show stopped",
"type": "select",
"default": "no",
"options": [
{"value": "no", "label": "Running only"},
{"value": "yes", "label": "All containers"},
],
},
],
},
"http_monitors": {
"key": "http_monitors",
"label": "HTTP Monitors",
"description": "Synthetic endpoint checks — up/down status and response times",
"hx_url": "/plugins/http/widget",
"detail_url": "/plugins/http/",
"plugin": "http",
"poll": True,
"params": [
{
"key": "show_down_only",
"label": "Display filter",
"type": "select",
"default": "no",
"options": [
{"value": "no", "label": "All monitors"},
{"value": "yes", "label": "Failing only"},
],
},
{
"key": "limit",
"label": "Max shown",
"type": "select",
"default": 10,
"options": [
{"value": 5, "label": "5 monitors"},
{"value": 10, "label": "10 monitors"},
{"value": 20, "label": "20 monitors"},
],
},
],
},
"docker_resources": {
"key": "docker_resources",
"label": "Docker — Resources",
"description": "CPU and memory usage bars for running containers",
"hx_url": "/plugins/docker/widget/resources",
"detail_url": "/plugins/docker/",
"plugin": "docker",
"poll": True,
"params": [
{
"key": "limit",
"label": "Containers shown",
"type": "select",
"default": 10,
"options": [
{"value": 5, "label": "5 containers"},
{"value": 10, "label": "10 containers"},
{"value": 20, "label": "20 containers"},
],
},
],
},
"snmp_devices": {
"key": "snmp_devices",
"label": "SNMP — Devices",
"description": "Latest OID readings for all configured SNMP devices",
"hx_url": "/plugins/snmp/widget",
"detail_url": "/plugins/snmp/",
"plugin": "snmp",
"poll": True,
"params": [],
},
"host_resources": {
"key": "host_resources",
"label": "Host Agent — Resources",
"description": "Fleet view: CPU, memory, disk, and load per monitored host",
"hx_url": "/plugins/host_agent/widget",
"detail_url": "/plugins/host_agent/",
"plugin": "host_agent",
"poll": True,
"params": [],
},
"host_resource_history": {
"key": "host_resource_history",
"label": "Host Agent — History",
"description": "CPU/memory/disk history for one host",
"hx_url": "/plugins/host_agent/widget/history",
"detail_url": "/plugins/host_agent/settings/",
"plugin": "host_agent",
"poll": True,
"params": [
{
"key": "hours",
"label": "Time range",
"type": "select",
"default": 6,
"options": [
{"value": 1, "label": "Last 1 hour"},
{"value": 6, "label": "Last 6 hours"},
{"value": 24, "label": "Last 24 hours"},
],
},
],
},
}
def register_widget(definition: dict) -> None:
"""Register a widget at runtime (used by external plugins)."""
WIDGET_REGISTRY[definition["key"]] = definition
def get_available_widgets(plugins_cfg: dict) -> list[dict]:
"""Return widgets whose plugin dependency is satisfied."""
result = []
for w in WIDGET_REGISTRY.values():
if w["plugin"] is None:
result.append(w)
elif plugins_cfg.get(w["plugin"], {}).get("enabled", False):
result.append(w)
return result