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") {