# System Monitor API (`/api/system-monitor/*`) Everything the `/system-monitor` web screen renders, for building the same in the app. ## Auth & access - Send the JWT as **`Authorization: Bearer `**, or as **`?token=`** in the query string (required for the SSE endpoints — `EventSource` can't set headers). - **Owner-only.** These routes are gated to the platform owner. Non-owner accounts (e.g. music-app users) are confined to `/api/auth` + `/api/music` and will get `403` here. The full **officer-mobile** client (which authenticates as the owner from an owner-allowed origin) has access; the music app does not. - All responses are `application/json` except the two `/logs` endpoints, which are `text/event-stream`. --- ## `GET /api/system-monitor/stats` One full snapshot. Poll it on a steady interval (the web client uses **2 s**) — a few fields are rates computed from the delta since your *previous* call (see notes), so a steady cadence matters. ```jsonc { "hostname": "alpha", "platform": "Linux 6.8.0-136-generic", "uptimeSec": 614031, "loadavg": [0.59, 0.60, 0.81], // 1 / 5 / 15 min "cpu": { "model": "AMD Ryzen 9 7940HS w/ Radeon 780M Graphics", "cores": 16, "usagePct": 2.6, // overall, 0–100 "perCore": [3.1, 0.0, 12.4, ...] // length === cores }, "mem": { // null if unreadable "totalBytes": 65100000000, "usedBytes": 26800000000, "freeBytes": 38300000000, "usedPct": 41.2, "swapTotalBytes": 0, "swapUsedBytes": 0 }, "disks": [ // real mounts (tmpfs/overlay excluded) { "mount": "/", "fsType": "ext4", "totalBytes": 0, "usedBytes": 0, "usedPct": 32 } ], "processes": [ // top 20 by CPU { "pid": 1234, "user": "pastilhas", "cpuPct": 21.8, "memPct": 1.2, "command": "radicle-node" } ], "temp": { // null if no hwmon "cpuC": 56.0, // chosen CPU sensor, °C (null if none matched) "cpuLabel": "k10temp · Tctl", "sensors": [ { "name": "amdgpu", "label": "edge", "celsius": 55.0 }, ... ] // every hwmon temp }, "gpu": { // null if no /sys/class/drm gpu_busy_percent "busyPct": 0, "vramUsedBytes": 2092957696, "vramTotalBytes": 2147483648 }, "net": { // null if /proc/net/dev unreadable "rxBytesPerSec": 0, "txBytesPerSec": 0, // aggregate (excludes lo) "interfaces": [ { "name": "eth0", "rxBytesPerSec": 0, "txBytesPerSec": 0 } ] // active only, busiest first }, "power": { // null if unreadable "cpuWatts": null, // RAPL is root-only by default → usually null "gpuWatts": 38.1 // amdgpu hwmon }, "timestamp": 1785150000000 } ``` **Notes** - `net.*BytesPerSec` and `power.cpuWatts` are **deltas since the previous `/stats` call**. The **first** call returns `0`/`null` for these; steady-interval polling gives stable numbers. - `cpuWatts` is usually `null` — RAPL `energy_uj` is root-only unless a udev rule opens it. `gpuWatts` works. - Any section can be `null` on hardware that doesn't expose it — render defensively. --- ## `GET /api/system-monitor/pm2` ```jsonc { "processes": [ { "id": 0, // pm2 id (pm_id) — use this for the logs endpoint "name": "officer", "status": "online", // online | stopped | errored | … "pid": 3339851, // OS pid, or null "cpuPct": 0, "memBytes": 10354688, "restarts": 44, "uptimeMs": 420000 } // 0 unless status === "online" ], "error": "…" // present only if pm2 couldn't be read } ``` ## `GET /api/system-monitor/docker` ```jsonc { "containers": [ { "id": "abc123def456", // short id (12 chars) — use for the logs endpoint "name": "jellyfin", "image": "jellyfin/jellyfin", "state": "running", // running | exited | … "status": "Up 3 hours", "ports": "0.0.0.0:9301->8096/tcp" } ], "error": "…" } ``` --- ## Live logs (SSE) Both stream one **`data: `** frame per line, plus `: hb` heartbeat comments every 15 s. The server kills the underlying tail when the connection closes. Open with `EventSource` using `?token=`. ### `GET /api/system-monitor/pm2/logs?id=&lines=` - `id` — **numeric** pm2 id from `/pm2` (required). - `lines` — initial backlog, default `100`, max `1000`. - Source: `pm2 logs --raw` (combined stdout+stderr, follows live). The first frames include a short pm2 `[TAILING] …` header. ### `GET /api/system-monitor/docker/logs?id=&lines=` - `id` — container id or name from `/docker` (charset-validated). - `lines` — initial backlog (`--tail`), default `100`, max `1000`. - Source: `docker logs -f --tail ` (combined stdout+stderr). ```js const es = new EventSource(`/api/system-monitor/pm2/logs?id=0&lines=150&token=${token}`); es.onmessage = (e) => appendLine(e.data); // close es to stop the tail ```