Nieuwe /worker-logs pagina: een tabel van de laatste N (10/25/50/100)
worker-runs met een inline detailpaneel dat de stream-json output van
Claude Code als leesbare timeline toont (system-init, assistant-tekst,
tool-calls/results, result-kaart).
- lib/parse-worker-log.ts: pure parser — summarizeRunLog (tabel) +
parseRunLog (timeline), discriminated-union events, server-side
truncatie van grote tool-results.
- lib/worker-logs.ts: server-only fs-toegang, leest uit WORKER_LOGS_DIR
(read-only bind mount), naam-regex + pad-confinement, .gz support.
- app/api/worker-logs[/[name]]: GET-routes, auth-guarded, force-dynamic.
- app/worker-logs: server page + client view (tabel, N-selector,
auto-refresh) + detail (timeline, auto-refresh tijdens in-progress run).
Vereist een read-only bind mount van /srv/scrum4me/worker-logs in de
ops-dashboard-container (docker-compose.yml + WORKER_LOGS_DIR in .env).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Legt de volledige stack-redeploy vast als één flow: scrum4me-web
(pull/migrate/build/restart) gevolgd door de MCP-worker.
Onderweg een echte bug gevonden en gefixt: update_mcp_worker.yml deed
`docker_compose_build worker-idea` zónder cache-bust. De worker-idea
Dockerfile clonet scrum4me-mcp van GitHub in een aparte laag; zolang
MCP_GIT_REF gelijk blijft ('main') hergebruikt Docker die laag, dus
nieuwe MCP-commits werden NIET opgepikt. Een schijnbaar geslaagde
rebuild draaide stilletjes op oude MCP-code.
Wijzigingen:
- commands.yml.example: nieuw command docker_compose_build_worker_fresh
dat via `sh -c` MCP_CACHE_BUST=$(date +%s) meegeeft — invalideert de
clone-laag zodat de laatste MCP-code wordt gepulld
- update_mcp_worker.yml: gebruikt nu de fresh-build; pullt ook
scrum4me-mcp lokaal (on_failure: continue, sync-only)
- redeploy_all.yml: nieuwe gecombineerde flow (16 stappen, web → worker)
- app/flows/redeploy-all/: UI-pagina + panel, zelfde patroon als de
bestaande flow-pagina's
- app/flows/page.tsx: Redeploy All bovenaan de flows-lijst
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the plain textarea on /caddy/edit with a CodeMirror 6 component
that provides live Caddyfile syntax highlighting (keywords, named matchers,
comments). The editor is dynamically imported (ssr: false) to prevent
hydration errors. The write/validate/save/reload state machine and content
flow remain unchanged.
Bundle impact: ~300 kB additional for the /caddy/edit route (CodeMirror 6
core + @uiw/react-codemirror).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace inline createHighlighter() call with a module-level singleton
so the Caddyfile grammar is parsed only once across requests. Add
type Highlighter import for proper TypeScript typing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds lib/grammars/caddyfile.json with scopes for directives, named-matchers
(@prefix), placeholders, strings, and comments. Updates /caddy page to use
createHighlighter with the local grammar instead of the nginx fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shiki 1.29 bundelt geen 'caddyfile' grammar — runtime error "Language
'caddyfile' is not included in this bundle". Nginx-grammar is syntactisch
het dichtst bij (directives + nested braces + reverse_proxy lijkt op
location-blocks), dus levert acceptabele kleuring zonder dependency
toe te voegen. Echte Caddyfile-grammar zou via een externe TextMate
JSON moeten worden geladen — later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SystemdWidget: groen als N=M healthy, oranje als 0<N<M, rood als N=0
- GitWidget: groen als 0 dirty repos, oranje als >0; toon K/M formaat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- relativeTime(date: Date) helper toegevoegd aan lib/utils.ts
- AuditWidget gebruikt nu gedeelde relativeTime in plaats van inline functie
- CaddyWidget toont rode badge als soonest cert-expiry <30 dagen
- app/page.tsx berekent expiringWarning voor CaddyInitial
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parallel server-side fetches via Promise.allSettled voor Docker, Caddy,
systemd, Git en Audit. Iedere widget toont geaggregeerde status en
refresht elke 30s client-side onafhankelijk van de andere widgets.
- lib/agent-fetch.ts: gedeelde client-side streaming helper
- app/api/audit/latest/route.ts: GET endpoint voor AuditWidget refresh
- app/_components/DockerWidget.tsx: running/total containers
- app/_components/CaddyWidget.tsx: soonest cert expiry in dagen
- app/_components/SystemdWidget.tsx: healthy/total units (of niet geconfigureerd)
- app/_components/GitWidget.tsx: dirty repo count (of niet geconfigureerd)
- app/_components/AuditWidget.tsx: laatste FlowRun status + relatief tijdstip
- app/page.tsx: vervangt SECTIONS-grid, doet parallel fetches, rendert widgets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Importeer AppNav in root-layout, render boven <main className="flex-1">.
Metadata bijgewerkt naar title "Ops Dashboard" en ops-beschrijving.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Beide routes hadden alleen sub-pages; /flows en /settings zelf gaven
404. Minimale index met kaartjes naar de bestaande sub-routes,
consistent met het home-dashboard. Onderdeel van IDEA-060 voor een
rijkere indexering later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SP-1 maakte 7 module-routes (docker/git/systemd/caddy/flows/
audit/settings) maar liet app/page.tsx de create-next-app starter
houden. Tijdelijke kaartjes-grid die auth-check doet en doorlinkt
naar elke module. IDEA-060 is gelogd voor een rijke dashboard met
live status per module.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server component fetches backup list via list_ops_backups agent command
and parses filename/size output. Client BackupsPanel component shows a
backup table and a Backup now button that triggers the backup_ops_db
flow with streaming terminal output and audit log link.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rate-limit /api/flows/start to 10 req/min per user (in-memory, matches login pattern)
- Add middleware.ts: validates x-csrf-token header against csrf_token cookie on all
API POST requests; issues the cookie on GET if missing; sets CSP, X-Frame-Options,
X-Content-Type-Options, and Referrer-Policy on all responses
- Add lib/csrf.ts: client-side apiFetch() wrapper that injects the CSRF header
- Update all client components (login, useFlowRun, docker, caddy, git, systemd)
to use apiFetch() for POST requests
- Cookie config in login route already correct (NODE_ENV check, httpOnly, sameSite=strict)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update ops-agent/flows.example/update_scrum4me_web.yml with full
deployment steps: git_status, git_fetch, git_log_ahead, git_pull,
npm_ci, prisma_migrate_deploy, npm_run_build, systemctl_restart,
and smoke test against thuis.jp-visser.nl/api/products
- Add npm_ci, prisma_migrate_deploy, npm_run_build, and
curl_smoke_scrum4me_thuis to commands.yml.example
- Add /flows/update-scrum4me-web UI page with Run and Dry Run buttons,
streaming terminal output, and link to audit log on completion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ops-agent/src/lib/flow-runner.ts: loads YAML flows, validates all steps
against the command whitelist, executes sequentially; supports dry_run
(emits WOULD RUN lines) and on_failure: abort|continue per step
- ops-agent/src/routes/flow.ts: POST /agent/v1/flow { flow_key, dry_run }
streams step_start/stdout/stderr/step_done/done SSE events
- ops-agent/src/index.ts: register flow route, add FLOWS_PATH env var
- ops-agent/flows.example/: three flow definitions — update_scrum4me_web,
update_mcp_worker, update_caddy_config; deploy to /etc/ops-agent/flows/
- ops-agent/commands.yml.example: add curl_smoke_scrum4me_web and
docker_compose_ps_worker smoke-test commands
- app/api/flows/run/route.ts: Next.js proxy — creates FlowRun/FlowStep
DB records per step, forwards SSE stream to browser
- hooks/useFlowRun.ts: add startFlow(flowKey, dryRun) method; handle
step_start events to display step headers in the terminal
- components/StreamingTerminal.tsx: add 'info' line type (sky-400) for
step headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Docker table: Restart and Stop buttons per container row (docker_compose_restart / docker_compose_stop)
- Git repos list: Fetch and Pull buttons per repo; Pull disabled when working tree is dirty
- systemd units list: Restart button per unit (systemctl_restart)
- Caddy: Edit link on /caddy page, new /caddy/edit page with textarea + 3-step Validate → Save+Reload flow
- All buttons open ConfirmDialog with exact agent-call preview, then stream output via StreamingTerminal
- Add docker_compose_stop to ops-agent/commands.yml.example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Truncate accumulated stdout/stderr to last 64KB before persisting FlowStep
to prevent unbounded DB growth on verbose commands
- Add @@index([user_id, started_at(sort: Desc)]) to FlowRun schema so audit
list queries (WHERE user_id = ? ORDER BY started_at DESC) use the index
- Add migration 20260513200000_flowrun_user_idx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add journalctl_recent command and scrum4me-web to whitelist in commands.yml.example
- Add SYSTEMD_UNITS env var to .env.example
- lib/parse-systemd.ts: parse activeState, subState, uptime, description
- /app/systemd: server page reading SYSTEMD_UNITS, client list with 10s polling and status badges
- /app/systemd/[unit]: server detail page, client component showing systemctl status + last 100 journal lines (polling 10s)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- lib/agent-client.ts: server-side execAgent() that calls the ops-agent
directly via OPS_AGENT_URL/OPS_AGENT_SECRET and streams SSE output
- lib/parse-docker.ts: pure parser for fixed-width docker ps table output
- app/docker/page.tsx: server component that fetches initial container list
and passes it to the client component
- app/docker/_components/docker-table.tsx: client component with 5s
auto-refresh via useEffect, status badge, and Link to /docker/[name]
- app/docker/[name]/page.tsx: placeholder detail page (logs in Story 3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ops-agent/src/auth.ts: constant-time compare via timingSafeEqual to prevent timing attacks; store secret as Buffer
- ops-agent/src/index.ts + ops-agent.service: bind on 127.0.0.1:3099 (was 4242, per plan)
- app/api/agent/[...path]/route.ts: Next.js proxy route that verifies ops_session cookie then forwards requests to agent with Authorization: Bearer <secret>
- .env.example + deploy/ops-dashboard.env.example: add OPS_AGENT_SECRET and OPS_AGENT_URL
- README.md: rotation procedure for the shared secret
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>