diff --git a/docs/plugins/host-agent-design.md b/docs/plugins/host-agent-design.md index e81e5bd..39199c6 100644 --- a/docs/plugins/host-agent-design.md +++ b/docs/plugins/host-agent-design.md @@ -157,7 +157,6 @@ plugins/host_agent/ │ └── host_agent_001_initial.py # creates host_agent_registrations table ├── templates/ │ ├── detail.html # per-host detail page -│ ├── widget_table.html # dashboard fleet table widget │ ├── widget_history.html # dashboard history chart widget │ ├── settings_list.html # plugin settings: list of registered hosts │ └── install.sh.j2 # install script template (Jinja) @@ -438,7 +437,7 @@ echo "Logs: journalctl -u steward-agent -f" ### Dashboard widgets (`steward/core/widgets.py`) -- **`host_resources`** — table widget. One row per monitored host: name, CPU %, mem %, disk % (worst mount), load 1m, "last seen Xs ago" with red/yellow/green coloring. Fleet glance. +- The fleet glance (CPU, mem, disk /, load 1m, stale mark) is the core **Hosts — Overview** widget (`hosts_overview`); the old `host_resources` table was folded into it (#5213). - **`host_resource_history`** — chart widget for one host. CPU / mem / disk over a selectable time range (1h, 6h, 24h, 7d). Same pattern as every other history widget — Chart.js. ### Per-host detail page diff --git a/plugins/host_agent/routes.py b/plugins/host_agent/routes.py index 20a90a7..4448dcc 100644 --- a/plugins/host_agent/routes.py +++ b/plugins/host_agent/routes.py @@ -17,7 +17,7 @@ from sqlalchemy import select, or_, and_ from datetime import timedelta from steward.core.formatting import worst_reading -from steward.core.metric_series import latest_by_resource, points as metric_points +from steward.core.metric_series import points as metric_points from steward.core.settings import get_setting, public_base_url from steward.core.time_range import parse_range, RANGE_OPTIONS from steward.models.hosts import Host @@ -477,108 +477,6 @@ def _stale_after_seconds() -> int: ) -async def _fleet_rows(session) -> list[dict]: - """One row per registered host with its latest host-level metrics + stale flag. - - Only bare host-level resources are used here (sub-resources carry a ':' and - are skipped) so the fleet glance stays one line per host. - """ - regs = (await session.execute(select(HostAgentRegistration))).scalars().all() - host_ids = [r.host_id for r in regs] - hosts = { - h.id: h for h in (await session.execute( - select(Host).where(Host.id.in_(host_ids)) - )).scalars().all() - } if host_ids else {} - - host_names = [h.name for h in hosts.values()] - latest_by = await latest_by_resource( - session, SOURCE_MODULE, host_names + [n + ":/" for n in host_names]) - latest: dict[str, dict[str, float]] = {} - root_disk: dict[str, float] = {} # host_name → root (/) disk % - for resource, metrics in latest_by.items(): - if resource.endswith(":/"): - if "disk_used_pct" in metrics: - root_disk[resource[:-2]] = metrics["disk_used_pct"].value - continue - latest[resource] = {m: r.value for m, r in metrics.items()} - - # Per-host recent series for the inline sparklines (cpu/mem/load host-level, - # disk from the root mount), so each metric shows its trend beside the value — - # the host-panel at-a-glance, on the fleet widget. 1h window keeps it cheap. - spark_series: dict[str, dict[str, list]] = { - n: {"cpu": [], "mem": [], "disk": [], "load": []} for n in host_names - } - if host_names: - since = datetime.now(timezone.utc) - timedelta(hours=1) - sp_rows = (await session.execute( - select(PluginMetric).where( - PluginMetric.source_module == SOURCE_MODULE, - PluginMetric.recorded_at >= since, - or_( - and_(PluginMetric.resource_name.in_(host_names), - PluginMetric.metric_name.in_(("cpu_pct", "mem_used_pct", "load_1m"))), - and_(PluginMetric.resource_name.in_([n + ":/" for n in host_names]), - PluginMetric.metric_name == "disk_used_pct"), - ), - ).order_by(PluginMetric.recorded_at) - )).scalars().all() - for r in sp_rows: - if r.resource_name.endswith(":/"): - name = r.resource_name[:-2] - if name in spark_series: - spark_series[name]["disk"].append(r.value) - else: - key = {"cpu_pct": "cpu", "mem_used_pct": "mem", "load_1m": "load"}.get(r.metric_name) - if key and r.resource_name in spark_series: - spark_series[r.resource_name][key].append(r.value) - - from steward.core.status import sparkline_svg - - stale_after = _stale_after_seconds() - now = datetime.now(timezone.utc) - rows = [] - for reg in regs: - host = hosts.get(reg.host_id) - if host is None: - continue - m = latest.get(host.name, {}) - ls = reg.last_seen_at - stale = ls is None or (now - ls).total_seconds() > stale_after - ser = spark_series.get(host.name, {}) - sparks = { - k: (sparkline_svg(v[-60:], width=72, height=16) if len(v) >= 2 else "") - for k, v in ser.items() - } - rows.append({ - "host": host, - "reg": reg, - "stale": stale, - "cpu_pct": m.get("cpu_pct"), - "mem_used_pct": m.get("mem_used_pct"), - "disk_worst": m.get("disk_used_pct_worst"), # health dot only - "disk_root": root_disk.get(host.name), # displayed at-a-glance - "load_1m": m.get("load_1m"), - "temp_max": m.get("temp_c_max"), - "net_rx_bps": m.get("net_rx_bps"), - "net_tx_bps": m.get("net_tx_bps"), - "disk_read_bps": m.get("disk_read_bps"), - "disk_write_bps": m.get("disk_write_bps"), - "sparks": sparks, - }) - rows.sort(key=lambda r: r["host"].name.lower()) - return rows - - -@host_agent_bp.get("/widget") -@require_role(UserRole.viewer) -async def widget_table(): - """Fleet-glance dashboard widget: one row per monitored host, latest metrics.""" - async with current_app.db_sessionmaker() as session: - rows = await _fleet_rows(session) - return await render_template("widget_table.html", rows=rows) - - @host_agent_bp.get("/widget/history") @require_role(UserRole.viewer) async def widget_history(): diff --git a/plugins/host_agent/templates/widget_table.html b/plugins/host_agent/templates/widget_table.html deleted file mode 100644 index 31c892e..0000000 --- a/plugins/host_agent/templates/widget_table.html +++ /dev/null @@ -1,69 +0,0 @@ -{# host_agent widget — fleet glance. One horizontal row per host, zebra-striped - so adjacent hosts read as distinct groupings: health dot + host name on the - left, metric cells (cpu/mem/disk/load) each with a trend sparkline laid out - horizontally on the right. The name wraps (never hard-truncated) but the row - stays horizontal. #} -{% if rows %} -{% macro metric_cell(label, value, fmt, svg, style="", title="") %} -
No hosts with agent data yet.
-{% endif %} diff --git a/plugins/unifi/routes.py b/plugins/unifi/routes.py index e5e00a6..55b48ea 100644 --- a/plugins/unifi/routes.py +++ b/plugins/unifi/routes.py @@ -324,33 +324,3 @@ async def widget_clients(): snapshot=snapshot, widget_id=widget_id, ) - - -@unifi_bp.get("/widget/devices") -@require_role(UserRole.viewer) -async def widget_devices(): - """HTMX dashboard widget: infrastructure devices with optional type filter.""" - type_filter = request.args.get("type_filter", "all") - widget_id = request.args.get("wid", "0") - - async with current_app.db_sessionmaker() as db: - result = await db.execute( - select(UnifiDevice).order_by(UnifiDevice.state.desc(), UnifiDevice.name) - ) - all_devices = result.scalars().all() - - if type_filter == "wireless": - devices = [d for d in all_devices if d.device_type == "uap"] - elif type_filter == "wired": - devices = [d for d in all_devices if d.device_type != "uap"] - elif type_filter == "offline": - devices = [d for d in all_devices if d.state != 1] - else: - devices = list(all_devices) - - return await render_template( - "unifi/widget_devices.html", - devices=devices, - type_filter=type_filter, - widget_id=widget_id, - ) diff --git a/plugins/unifi/templates/unifi/widget_devices.html b/plugins/unifi/templates/unifi/widget_devices.html deleted file mode 100644 index 3c04ae7..0000000 --- a/plugins/unifi/templates/unifi/widget_devices.html +++ /dev/null @@ -1,36 +0,0 @@ -{# unifi/widget_devices.html — dashboard widget: infrastructure devices #} -{% if not devices %} -No monitors configured.
-{% elif not problems %} -