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
5.6 KiB
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
- serviciul
claude-discord(stare + numar de restarturi) state.jsoncitibil, cate fire contine- costul zilei fata de
COST_CAP_USD_DAY - spatiu liber pe disc (prag 10%)
- dimensiunea
bot.log(prag 100 MB) - regulile
denydinbot-settings.json— pica daca reapareBash(ssh:*)sauBash(scp:*). Sunt regulile care pe 2026-08-30 au taiat complet accesul puntii la infrastructura:denyare precedenta pestebypassPermissionssi opreste turul inainte de hook (vezi../security/README.md). - prezenta hook-ului de confirmare
- CLI-ul
claudein 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).