# Functionele specificatie — Ops Dashboard ## Doel Eén web-UI waarmee de eigenaar van een single-host server-stack (Docker + systemd + Git-checkouts + Caddy + Postgres) dezelfde operaties kan uitvoeren die anders in een SSH-terminal gebeuren — met audit-log, herhaalbare flows en minder typefouten. **Schaal:** één host, één admin-gebruiker. Multi-host/team is buiten scope. ## Gebruikers en rollen | Rol | Beschrijving | Hoeveelheid | |---|---|---| | **admin** | Volle toegang tot alle modules en flows. Single account, geseed via env. | 1 | Geen RBAC, geen tenant-isolatie, geen "view-only"-modus. Wie inlogt kan alles. ## Functionele scope per module ### Dashboard (`/`) 5 live status-widgets met auto-refresh ~5s: | Widget | Data-bron | Indicator | |---|---|---| | Docker | `docker ps --format json` | Count van running containers, lijst (naam + status) | | Git | `git status --short --branch` per pad in `REPO_PATHS` | Branch + dirty-vlag | | systemd | `systemctl is-active ` per item in `SYSTEMD_UNITS` | Active / Inactive / Failed | | Caddy | `caddy admin-cmd certificates` (of equiv. shell-output parse) | Aantal certs + dichtstbijzijnde expiry | | Audit | DB-query op `FlowRun` desc | Laatste run + status | **Acceptatie:** - Widget laadt < 1 s na page-load - Auto-refresh werkt in achtergrond zonder volledig herrenderen - Bij fout (agent down, command faalt): widget toont rood errorblok, niet de hele page ### Auth (`/login`) - Email + wachtwoord (single user) - 5 failed attempts in 1 minuut → 429 rate-limit per IP - Succesvolle login → session-cookie 24u, HttpOnly, SameSite=strict, Secure (production) - `/api/auth/logout` invalideert sessie en wist cookie ### Docker (`/docker`) - Lijst running containers (CONTAINER ID, IMAGE, COMMAND, CREATED, STATUS, PORTS, NAMES) - Auto-refresh elke 5s - `/docker/[name]` → detail-page met logs (laatste 200 regels), image-info, environment - **Geen** start/stop/restart vanuit UI — alleen via flows ### Git (`/git`) - Per pad in `REPO_PATHS`: huidige branch, ahead/behind count, modified-files-count, laatste 3 commits - `/git/[repo]` → diff-viewer voor uncommitted changes + commit-historie laatste 20 ### systemd (`/systemd`) - Per unit in `SYSTEMD_UNITS`: active/inactive/failed, last-changed-timestamp - `/systemd/[unit]` → laatste 100 journal-regels van die unit, met level-filter - **Restart-actie**: alleen voor units die expliciet in `sudoers.d/ops-agent` met NOPASSWD staan ### Caddy (`/caddy`) - Toon huidige `/srv/scrum4me/caddy/Caddyfile` met syntax-highlighting - Toon alle uitgegeven certs (subject, issuer, expiry, dichtstbijzijnde eerst) - Geel-warning bij expiry < 30 dagen, rood bij < 7 dagen - `/caddy/edit` → editor met save-knop; save valideert via `caddy validate` voor commit en restart van caddy-container ### Flows (`/flows`) Twee voor-gedefinieerde flows in YAML in `ops-agent/flows.example/`: | Flow | Stappen | |---|---| | `update_scrum4me_web` | git pull → npm run build → docker compose up -d --build → smoke-test op homepage | | `update_caddy_config` | write nieuw Caddyfile → caddy validate → docker compose restart caddy → check cert renewal | Per flow: - Dry-run default (toont alleen wat het zou doen) - "Run"-knop voert echt uit; toont live SSE-stream van stdout/stderr per stap - Bij stap-fail: stop, markeer FlowRun als `failed`, latere stappen niet uitgevoerd - Bij success: FlowRun = `success`, totaalduur opgeslagen ### Audit (`/audit`) - Lijst van alle `FlowRun` records, default 50 laatste, sort desc op `started_at` - Filter op status, datumrange, flow-name - `/audit/[flow_run_id]` → volledige output per stap, scrollable ### Settings/Backups (`/settings/backups`) - Lijst van `.sql.gz` bestanden in `/srv/scrum4me/backups`, met size + mtime - "Backup now"-knop → maakt nieuwe dump met `pg_dumpall` voor alle databases - Restore: **handmatig vanuit terminal** — UI toont alleen de stappen als runbook ## State-machine flows ``` ┌────────┐ │ pending │ └────┬────┘ │ (start request) ┌────▼────┐ │ running │ └────┬────┘ ┌────┼────┬────────┐ ▼ ▼ ▼ ▼ success failed cancelled timeout (>30min) ``` `pending` → `running`: bij ontvangst start-request `running` → `success`: alle stappen exit-code 0 `running` → `failed`: een stap exit-code ≠ 0 `running` → `cancelled`: user klikt cancel `running` → `timeout`: na 30 min nog steeds running (cleanup-job) ## Hard limits - Max 1 actieve flow tegelijk (lock-file in `/var/run/agent/`); 2e start-request → 409 Conflict - Stdout/stderr per stap geknipt op 64 KB om audit-log niet te laten exploderen - Session-TTL hard 24 uur, geen "remember me" - Auto-refresh max 1 keer per 5 seconden om agent niet te overbelasten ## Niet-functionele eisen | Eis | Doel | |---|---| | First load < 1s | Single-user, lokale Postgres, geen onnodige round-trips | | Module-page TTI < 2s | Server-side render met direct agent-call, geen client-fetch waterval | | Audit-trail volledig | Elke flow-start logt user + tijdstempel + args; elke command-execution logt exit + duration | | Geen geheime data in URLs | Tokens in headers, secrets nooit in query-params | | CSP strict | `script-src 'self' 'unsafe-inline'`; geen externe CDNs | | HTTPS-only in productie | Caddy auto-ACME; cookies Secure-flag in prod-mode | ## Buiten scope - Meerdere admins / RBAC - Meerdere hosts / cluster-management - Custom container starten (alleen restart bestaande) - Real-time alerts (geen pager, geen email) - Externe monitoring-integratie (Grafana/Prometheus/Sentry) - Wachtwoord-reset-flow / SSO ## Verwante documenten - [Technische specificatie](./technical.md) — hoe het werkt - [Handleiding](../handleiding.md) — hoe je het gebruikt - [Post-install runbook](../runbooks/post-install.md) — eerste deploy