feat: system monitoring + push agent (merge plan M3)

systems + system_stats tables; agents push JSON every N seconds
to POST /api/systems/ingest with a per-system bearer token. The
server stores each sample, keeps a `latest` snapshot for cheap
list/widget reads, prunes stats after 7d, and marks systems down
when silent > max(3x interval, 30s) — firing system.up/down
webhook events on transitions.

cmd/dash-agent is a standalone Linux collector: /proc for
cpu/mem/net/load/uptime, /sys hwmon temps, statfs disk, and
container liveness via /var/run/docker.sock when present.
No imports from the server module — the sample shape is
duplicated deliberately so the agent stays a single static
binary.

Frontend: /systems card grid with usage bars, /systems/:id with
recharts cpu/mem/net/load, temps + containers tables, and the
agent command; `system` board widget; nav + palette + WidgetDialog
picker wired.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-21 19:02:58 +02:00
1 parent 2f3e8a1879
commit 74c987c18a
16 files changed
+1541 -10

No files matched your search

@@ -0,0 +1,31 @@
-- +goose Up
CREATE TABLE systems (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
host TEXT NOT NULL DEFAULT '',
os TEXT NOT NULL DEFAULT '',
arch TEXT NOT NULL DEFAULT '',
cpu_model TEXT NOT NULL DEFAULT '',
cores INTEGER NOT NULL DEFAULT 0,
interval_s INTEGER NOT NULL DEFAULT 10,
status TEXT NOT NULL DEFAULT 'pending',
last_seen_at TEXT,
latest TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
-- Whole agent payload per tick; no sub-field queries, so one JSON column
-- instead of a wide row that drifts with every agent version.
CREATE TABLE system_stats (
system_id TEXT NOT NULL REFERENCES systems(id) ON DELETE CASCADE,
ts TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
payload TEXT NOT NULL,
PRIMARY KEY (system_id, ts)
);
CREATE INDEX idx_system_stats_ts ON system_stats(ts);
-- +goose Down
DROP TABLE IF EXISTS system_stats;
DROP TABLE IF EXISTS systems;