diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
index c825b64..8f4dd24 100644
--- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
+++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py
@@ -44,6 +44,7 @@ for _p in (str(_BRIDGE), str(_DASH)):
sys.path.insert(0, _p)
import config # noqa: E402
+import infra_actions # noqa: E402
from limits import parse_cap # noqa: E402
# ── constante ───────────────────────────────────────────────────────────
@@ -847,6 +848,18 @@ class Handler(SimpleHTTPRequestHandler):
return self.send_json({"clients": maria_clients()})
if path == "/api/maria/fire":
return self.send_json({"fire": maria_fire()})
+ if path == "/api/infra/status":
+ fresh = qs.get("fresh", ["0"])[0] not in ("0", "", "false")
+ return self.send_json(infra_actions.status(fresh=fresh))
+ if path == "/api/infra/log":
+ action_id = qs.get("id", [""])[0]
+ if action_id not in infra_actions.ACTIONS:
+ return self.send_json({"error": "actiune necunoscuta"}, 400)
+ try:
+ n = min(max(int(qs.get("n", ["60"])[0]), 1), 2000)
+ except ValueError:
+ n = 60
+ return self.send_json(infra_actions.log_tail(action_id, n))
if path == "/api/maria/logs":
try:
n = min(max(int(qs.get("lines", ["200"])[0]), 1), 2000)
@@ -874,6 +887,10 @@ class Handler(SimpleHTTPRequestHandler):
return self.deny()
if path == "/api/service":
return self.handle_service()
+ if path == "/api/infra/run":
+ return self.handle_infra_run()
+ if path == "/api/infra/stop":
+ return self.handle_infra_stop()
if path == "/api/cleanup":
data = self.read_json()
return self.send_json(orphans_report(dry_run=bool(data.get("dry_run", True))))
@@ -959,6 +976,26 @@ class Handler(SimpleHTTPRequestHandler):
return self.send_json({"ok": bool(ok), "request_id": rid, "decision": decision},
200 if ok else 404)
+ def handle_infra_run(self):
+ data = self.read_json()
+ action_id = str(data.get("id") or "")
+ dry_run = bool(data.get("dry_run"))
+ who = tailnet_user(self.headers) or "local"
+ print(f"[actiune] infra {action_id} dry={dry_run}, cerut de {who}",
+ file=sys.stderr, flush=True)
+ res = infra_actions.run(action_id, dry_run)
+ return self.send_json(res, res["code"])
+
+ def handle_infra_stop(self):
+ data = self.read_json()
+ action_id = str(data.get("id") or "")
+ if action_id not in infra_actions.ACTIONS:
+ return self.send_json({"ok": False, "code": 400, "error": "actiune necunoscuta"}, 400)
+ who = tailnet_user(self.headers) or "local"
+ print(f"[actiune] infra stop {action_id}, cerut de {who}", file=sys.stderr, flush=True)
+ res = infra_actions.stop(action_id)
+ return self.send_json(res, res["code"])
+
def handle_maria_service(self):
"""start / stop / restart pe una din UNITATILE FIXE ale Mariei — acelasi
principiu ca handle_service: numele vine dintr-o cheie (bridge/rag), nu
diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
index f909bc6..eb32717 100644
--- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
+++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html
@@ -11,178 +11,358 @@
Punte Discord
+
—
+
-
+
+
-
-
-
Serviciu
-
-
-
-
-
-
-
-
-
+
+ ▶Serviciu
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
Fire active
-
-
-
Fir
Model
Director
Stare
USD
-
-
+
+ ▶Fire active
+
+
+
+
Fir
Model
Director
Stare
USD
+
+
+
-
+
-
-
-
Diagnostic
-
se încarcă…
-
+
+ ▶Diagnostic
+
+
se încarcă…
+
+
-
-
-
Confirmări în așteptare
-
niciuna
-
+
+ ▶Confirmări în așteptare
+
+
niciuna
+
+
-
-
-
Jurnal
+
+ ▶Jurnal
-
-
se încarcă…
-
+
+
+
se încarcă…
+
+
-
-
Maria — WhatsApp + RAG
+
+
+
+
-
-
Punte WhatsApp
-
-
-
-
-
+
+ ▶Punte WhatsApp
+
+
+
+
+
+
+
-
-
-
Consumer RAG
-
-
-
-
-
+
+
+ ▶Consumer RAG
+
+
+
+
+
+
+
-
+
-
-
Conectare WhatsApp
-
se încarcă…
-
-
-
- Nu poți scana codul QR? Cere un cod de asociere și
- introdu-l pe telefon în WhatsApp → Dispozitive conectate → Conectează un
- dispozitiv → Conectează cu număr de telefon.
+
+ ▶Conectare WhatsApp
+
+
se încarcă…
+
+
+
+ Nu poți scana codul QR? Cere un cod de asociere și
+ introdu-l pe telefon în WhatsApp → Dispozitive conectate → Conectează un
+ dispozitiv → Conectează cu număr de telefon.
+
Verificări: ping, ssh, cvorum, fără task-uri în curs
+
Salvează lista guest-urilor pornite (.cluster-state.txt)
+
Trimite instrucțiunile de pornire cât încă merge totul: mesaj în Discord + email
+
HA pe freeze; pauză la alerte, la testul DR și la fereastra de patch
+
Oprește guest-urile, Oracle 108 ultimul (shutdown immediate)
+
LXC 171 se oprește odată cu pvemini, la final: dashboard-ul și Discord nu mai răspund
+
pveelite → pve1 → pvemini; raportul final vine pe email
+
+
+
+
+
+
+ ▶Pornire clustertrezire de pe telefon, apoi de aici
+
+
+
+
+
+
Cu dashboard-ul oprit, instrucțiunile de pornire se găsesc în Discord (mesaj fixat la oprire) și pe email. Textul lor e mai jos.
+
—
+
+
+
+
+
+
+
+
+ ▶UPS
+
+
Test rapid baterie
ups-monthly-test.sh · ~2 min pe baterie
+
Simulează oprirea de urgență
ups-shutdown-cluster.sh --dry-run --force, ca nut
+
Istoric autonomie
ultimele rânduri din trendul de baterie
+
+
+
+
+
+
+
+ ▶DR · VM 109
+
+
Test DR acum
restaurează backup-ul RMAN pe VM 109, verifică, oprește · ~25 min
+
Fereastră de patch Windows
vm109-patch-window.sh --now · 30–60 min
+
VM 109 manual
pentru depanare; watchdog-ul intră pe pauză
+
+
+
+
+
+
+
+ ▶Failover și failbackdistructivcând un nod a căzut; deschide o operație pentru instrucțiuni
+
+
+
+ ▶Backup-urile Oracle trec pe pveminipveelite a căzut și nu revine curând
+
+
Situația
Serverul de producție (10.0.20.36) își copiază în fiecare noapte backup-urile RMAN pe pveelite. De acolo le folosește VM 109 la testul DR. Dacă pveelite e jos, copia din noaptea asta nu mai are unde ajunge.
+
Ce face
1. Verifică de două ori că pveelite chiar nu răspunde. 2. Pe pvemini, copia backup-urilor devine activă, cu scriere, partajată prin NFS. 3. Pe 10.0.20.36, transfer_backups.ps1 e redirecționat spre pvemini. 4. Oprește replicarea pveelite → pvemini.
+
Ce pierzi
Nimic din producție. Backup-urile ajunse pe pveelite după ultima replicare (maximum 15 min) rămân doar acolo.
+
Cum revii
Operația de mai jos, după ce pveelite a repornit.
+
failover-dr-to-pvemini.sh · pe pvemini · ~2 min
+
+
+
+
+ ▶Backup-urile Oracle revin pe pveelitedupă ce pveelite a repornit
+
+
Situația
Operația de mai sus a rulat. Între timp pvemini a primit backup-uri noi, iar pveelite e din nou pornit, dar are date vechi.
+
Ce face
1. Face un snapshot pe pvemini și îl trimite pe pveelite, suprascriind ce e acolo. 2. pvemini redevine replica read-only, fără NFS. 3. Pe 10.0.20.36, transferul e redirecționat înapoi spre pveelite. 4. Repornește replicarea.
+
Ce pierzi
Tot ce e pe pveelite și nu e pe pvemini. Nu rula dacă pveelite a mai primit backup-uri după căderea lui.
+
Cum revii
Nu există un pas invers; suprascrierea e definitivă.
+
failback-dr-to-pveelite.sh · pe pvemini · durează după volumul datelor
+
+
+
+
+ ▶VM 201 (roacentral) pe alt nodarhivat — vezi proxmox/cluster/failover/arhiva/învechit
+
+
De ce nu
Scripturile au fost scrise când VM 201 nu era în HA. Din 2026-04-25 e în HA (ha-prefer-pvemini): dacă pvemini cade, se mută singur pe pve1 și revine singur. Un failover manual ar intra în conflict cu HA.
+
În schimb
Nu apar ca butoane aici. Panoul „Stare cluster" arată doar unde rulează VM 201 și starea HA. Scripturile sunt arhivate în repo.