Files
ROMFASTSQL/proxmox/lxc171-claude-agent/discord-bridge/dashboard
Claude Agent 7abefa2b46 feat(discord-bridge): dashboard de control si restart, dupa modelul agentului echo
Panou web pe 127.0.0.1:18790, unit systemd separat de al puntii. Server stdlib
(fara dependinte noi), tokenii de design si tiparul de endpoint-uri preluate din
/home/moltbot/echo-core/dashboard (handlers/eco.py) de pe LXC 110.

Arata: starea unitatii (uptime, PID, memoria cgroup, restarturi), firele din
state.json cu tur in zbor si cost, costul zilei fata de plafon, confirmarile
PreToolUse in asteptare (aprobabile direct din pagina), bot.log / infra.log si
opt verificari de diagnostic.

Face: start / stop / restart pe punte, cautarea si curatarea orfanilor prin
cleanup.py, repornirea propriului serviciu.

Garantii, cu teste:
- unitatea controlata e fixa in cod; un {"unit": "ssh.service"} in cerere nu
  schimba nimic, altfel panoul ar fi systemctl remote fara parola;
- stop/restart intorc 409 cu lista firelor active si cer force explicit, fiindca
  KillMode=control-group taie tururile in desfasurare;
- state.json se citeste fara lock: panoul nu are voie sa blocheze botul;
- diagnosticul pica daca reapare Bash(ssh:*) in deny (regresia de azi).

Uptime-ul se calculeaza din time.monotonic(), nu din /proc/uptime: in LXC acela
e virtualizat de lxcfs si da diferenta negativa fata de monotonic-ul systemd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:18:38 +00:00
..

Dashboard de control pentru puntea Discord (LXC 171)

Panou web pentru starea si repornirea puntii, modelat dupa dashboard-ul agentului echo de pe LXC 110 (/home/moltbot/echo-core/dashboard, port 8088): acelasi tip de server (stdlib http.server, zero dependinte), aceiasi tokeni de design, acelasi tipar de endpoint-uri ca in handlers/eco.py.

http://127.0.0.1:18790

Ce arata si ce poate face

Zona Continut
Serviciu stare active/running, uptime, PID, memoria cgroup-ului, numarul de restarturi, firele active, costul zilei fata de plafon
Butoane Pornește / Oprește / Repornește puntea, cauta si curata procese orfane (cleanup.py), reporneste dashboard-ul insusi
Fire active ce e in state.json: fir, model, cwd, daca procesul claude traieste, daca are tur in desfasurare, cost
Diagnostic 8 verificari (vezi mai jos), reimprospatate la 30s
Confirmari in asteptare cererile hook-ului PreToolUse — se pot aproba/refuza direct din pagina, nu doar din Discord
Jurnal ultimele 300 de linii din bot.log sau infra.log

Starea se reimprospateaza automat la 5 secunde.

Decizii de proiectare

O singura unitate controlata. /api/service actioneaza intotdeauna pe claude-discord.service; numele unitatii nu vine niciodata din cerere. Altfel panoul ar fi un systemctl remote fara parola pentru tot ce ruleaza sub claude. Exista un test care trimite {"unit": "ssh.service"} si verifica faptul ca tot puntea e repornita.

Bind pe 127.0.0.1. Butonul de restart opreste un agent care ruleaza cu bypassPermissions si are chei SSH catre tot clusterul. Se ajunge la el prin tunel SSH (mai jos), nu expus in LAN. DASHBOARD_BIND poate schimba asta, dar atunci tokenul ramane singura bariera.

Restart protejat de tururi in zbor. stop/restart intorc 409 cu lista firelor care au un tur in desfasurare; interfata intreaba si retrimite cu force: true doar dupa confirmare. KillMode=control-group din unitul puntii omoara tot cgroup-ul, deci un restart neatent taie raspunsuri pe jumatate scrise.

Dashboard-ul e un unit separat (claude-discord-dashboard.service), tocmai ca o repornire a puntii sa nu ia si panoul din care ai apasat butonul. Invers, /api/restart-self iese cu cod 0 si lasa Restart=always sa-l reporneasca.

Citire fara lock. state.json e citit direct, fara flock: panoul nu are voie sa blocheze botul. Un JSON prins la mijlocul unei scrieri se ignora si se reia la urmatorul poll (test: test_state_corupt_nu_arunca).

Verificarile din Diagnostic

  1. serviciul claude-discord (stare + numar de restarturi)
  2. state.json citibil, cate fire contine
  3. costul zilei fata de COST_CAP_USD_DAY
  4. spatiu liber pe disc (prag 10%)
  5. dimensiunea bot.log (prag 100 MB)
  6. regulile deny din bot-settings.json — pica daca reapare Bash(ssh:*) sau Bash(scp:*). Sunt regulile care pe 2026-08-30 au taiat complet accesul puntii la infrastructura: deny are precedenta peste bypassPermissions si opreste turul inainte de hook (vezi ../security/README.md).
  7. prezenta hook-ului de confirmare
  8. CLI-ul claude in PATH

Endpoint-uri

Toate cer cookie-ul de sesiune, obtinut cu POST /api/auth/login.

Metoda Ruta Ce face
GET /api/status serviciu + dashboard + fire + cost + numar de confirmari
GET /api/logs?lines=N&file=bot|infra ultimele N linii (plafon 2000)
GET /api/doctor verificarile de mai sus
GET /api/approvals cererile pending
GET/POST /api/cleanup GET = doar cauta; POST {"dry_run": false} = omoara orfanii
POST /api/service {"action": "start|stop|restart", "force": bool}
POST /api/approvals/decide {"request_id": "...", "decision": "allow|deny"}
POST /api/restart-self reporneste dashboard-ul
POST /api/auth/login / /api/auth/logout {"token": "..."} / sterge cookie-ul

Autentificarea e un token din ~/.claude-discord/env schimbat pe un cookie HttpOnly; SameSite=Strict valabil 30 de zile, comparat cu secrets.compare_digest. Fara DASHBOARD_TOKEN in env, procesul isi genereaza unul aleator si il scrie in logs/dashboard.log — nu ramane deschis.

Instalare

ops/install.sh face totul (leaga unitul, genereaza DASHBOARD_TOKEN daca lipseste, porneste serviciul). Manual:

ln -sfn /workspace/romfastsql/proxmox/lxc171-claude-agent/discord-bridge/dashboard/claude-discord-dashboard.service \
        ~/.config/systemd/user/claude-discord-dashboard.service
printf 'DASHBOARD_TOKEN=%s\n' "$(python3 -c 'import secrets;print(secrets.token_urlsafe(24))')" >> ~/.claude-discord/env
systemctl --user daemon-reload
systemctl --user enable --now claude-discord-dashboard

Setari optionale in ~/.claude-discord/env: DASHBOARD_BIND (implicit 127.0.0.1), DASHBOARD_PORT (implicit 18790).

Acces

Fiind legat de localhost, se ajunge la el prin tunel SSH — la fel ca la dashboard-ul lui echo:

# de pe statia de lucru (direct sau prin Tailscale: 100.95.55.51)
ssh -L 18790:127.0.0.1:18790 -N claude@10.0.20.171 &
# apoi http://localhost:18790

Tokenul se citeste cu grep DASHBOARD_TOKEN ~/.claude-discord/env.

Teste

cd proxmox/lxc171-claude-agent/discord-bridge
python3 -m pytest tests/test_dashboard.py -q

25 de teste, fara retea si fara systemctl real (dublura inregistreaza apelurile). Acopera autentificarea, faptul ca unitatea nu poate fi aleasa din cerere, blocajul pe tur in zbor si trecerea cu force, traversarea de cale in request_id, state.json corupt si verificarea de regresie pentru deny(ssh).