From 18eb94e942a3d29d2cbad3da607b615ef9141ebb Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 8 Aug 2026 15:08:49 +0200 Subject: [PATCH] fix(diagnostic): 'Sicher aufraeumen' fuehrt wirklich auf statt nur zu kopieren MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Button rief copyDiskCmd() → kopierte nur den docker-Befehl in die Zwischenablage (ueber http scheitert navigator.clipboard oft still → 'keine Funktion'). Jetzt fuehrt runDiskCleanup() das Aufraeumen echt aus: Server-seitig ueber die Docker-Daemon-API (Socket ist gemountet) — /build/prune?all=true + /images/prune (dangling=false), ohne Volumes = keine Daten weg. 'Aggressiv' (zusaetzlich Container+Volumes) als eigener 'Jetzt ausfuehren'-Button mit Warnung. Rueckmeldung: wieviel frei wurde; Banner aktualisiert sich per periodischem disk_status. Co-Authored-By: Claude Opus 4.8 --- diagnostic/index.html | 29 ++++++++++++++++++- diagnostic/server.js | 66 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/diagnostic/index.html b/diagnostic/index.html index bf5464a..2cc4aa3 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -198,7 +198,7 @@
⚠️ -
@@ -1739,6 +1740,20 @@ return; } + if (msg.type === 'disk_cleanup') { + const btn = document.getElementById('disk-clean-btn'); + if (msg.status === 'running') { + if (btn) { btn.disabled = true; btn.textContent = 'Raeume auf...'; } + } else if (msg.status === 'done') { + if (btn) { btn.disabled = false; btn.textContent = 'Sicher aufraeumen'; } + alert('Aufgeraeumt: ' + (msg.freed || '0 MB') + ' frei\n(' + (msg.steps || []).join(', ') + ')'); + } else if (msg.status === 'error') { + if (btn) { btn.disabled = false; btn.textContent = 'Sicher aufraeumen'; } + alert('Aufraeumen fehlgeschlagen: ' + (msg.error || '')); + } + return; + } + if (msg.type === 'mode' && msg.payload) { // Bridge hat den Modus geaendert (evtl. von anderer App/Diagnostic) — UI syncen const mode = (msg.payload.mode || '').toLowerCase(); @@ -6734,6 +6749,18 @@ banner.style.display = 'block'; } + // Fuehrt das Aufraeumen WIRKLICH aus (Server-seitig via Docker-API), statt + // nur den Befehl zu kopieren. + function runDiskCleanup(variant) { + const aggressive = variant === 'aggressive'; + const q = aggressive + ? 'AGGRESSIV aufraeumen? Loescht zusaetzlich ungenutzte Volumes — nur wenn ALLE ARIA-Container laufen, sonst Datenverlust!' + : 'Sicher aufraeumen? Loescht Build-Cache + ungenutzte Images (keine Volumes, keine Daten gehen verloren).'; + if (!confirm(q)) return; + const btn = document.getElementById('disk-clean-btn'); + if (btn && !aggressive) { btn.disabled = true; btn.textContent = 'Raeume auf...'; } + send({ action: 'disk_cleanup', variant }); + } function copyDiskCmd(variant) { const cmd = variant === 'aggressive' ? 'docker system prune -a --volumes -f' diff --git a/diagnostic/server.js b/diagnostic/server.js index 74c9d52..7c32608 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -1559,6 +1559,70 @@ function dockerExec(containerName, cmd) { }); } +// POST gegen die Docker-Daemon-API (via gemountetem Socket). Fuer prune- +// Endpoints — die geben SpaceReclaimed (Bytes) zurueck. +function dockerApiPost(apiPath) { + return new Promise((resolve, reject) => { + const req = http.request({ + socketPath: "/var/run/docker.sock", + path: apiPath, + method: "POST", + headers: { "Content-Type": "application/json", "Content-Length": 0 }, + }, (res) => { + let data = ""; + res.on("data", (c) => data += c); + res.on("end", () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { resolve(JSON.parse(data || "{}")); } catch { resolve({}); } + } else { + reject(new Error(`Docker API ${apiPath}: HTTP ${res.statusCode} — ${String(data).slice(0, 200)}`)); + } + }); + }); + req.on("error", reject); + req.end(); + }); +} + +// "Sicher aufraeumen": Build-Cache + ungenutzte Images prunen — OHNE Volumes +// (keine Daten weg). "aggressive": zusaetzlich gestoppte Container + ungenutzte +// Volumes (kann Daten kosten → nur auf ausdrueckliche Wahl). Fuehrt es WIRKLICH +// aus (frueher kopierte der Button nur den Befehl in die Zwischenablage). +async function handleDiskCleanup(clientWs, variant) { + const aggressive = variant === "aggressive"; + const send = (o) => { try { clientWs.send(JSON.stringify(o)); } catch (_) {} }; + send({ type: "disk_cleanup", status: "running", variant }); + log("warn", "server", `Disk-Cleanup gestartet (${aggressive ? "aggressive" : "safe"})`); + try { + let reclaimed = 0; + const steps = []; + const bp = await dockerApiPost("/build/prune?all=true"); + reclaimed += (bp.SpaceReclaimed || 0); + steps.push("Build-Cache"); + // dangling=false → ALLE ungenutzten Images (nicht nur dangling). + // Docker-API-Filterformat: map[string][]string. + const imgFilter = encodeURIComponent(JSON.stringify({ dangling: ["false"] })); + const ip = await dockerApiPost("/images/prune?filters=" + imgFilter); + reclaimed += (ip.SpaceReclaimed || 0); + steps.push("ungenutzte Images"); + if (aggressive) { + const cp = await dockerApiPost("/containers/prune"); + reclaimed += (cp.SpaceReclaimed || 0); + steps.push("gestoppte Container"); + const vp = await dockerApiPost("/volumes/prune"); + reclaimed += (vp.SpaceReclaimed || 0); + steps.push("ungenutzte Volumes"); + } + const mb = (reclaimed / (1024 * 1024)); + const freed = mb >= 1024 ? (mb / 1024).toFixed(2) + " GB" : mb.toFixed(0) + " MB"; + log("info", "server", `Disk-Cleanup fertig: ${freed} frei (${steps.join(", ")})`); + send({ type: "disk_cleanup", status: "done", variant, reclaimedBytes: reclaimed, freed, steps }); + } catch (err) { + log("error", "server", `Disk-Cleanup fehlgeschlagen: ${err.message}`); + send({ type: "disk_cleanup", status: "error", variant, error: String(err && err.message || err) }); + } +} + // ── Hilfsfunktionen ───────────────────────────────────── function waitForMessage(ws, timeoutMs) { @@ -2463,6 +2527,8 @@ wss.on("connection", (ws) => { sendToRVS_raw({ type: "interject", payload: { projectId: msg.projectId || "", text: t }, timestamp: Date.now() }); log("info", "server", "Zwischenruf an RVS (project=" + (msg.projectId || "(main)") + "): " + t.slice(0, 60)); } + } else if (msg.action === "disk_cleanup") { + handleDiskCleanup(ws, msg.variant === "aggressive" ? "aggressive" : "safe"); } else if (msg.action === "reconnect_gateway") { connectGateway(); } else if (msg.action === "reconnect_rvs") {