feat: Disk-Space Banner im Diagnostic mit Cleanup-Command zum Kopieren
Server:
- checkDiskSpace() prueft alle 30s 'df -B1 /shared' (zeigt Host-Disk
da /shared ein Volume auf dem Docker-FS ist)
- 4 Stufen: ok (<70%), info (70%), warn (85%), critical (95%)
- Broadcastet disk_status nur bei Aenderung (Level oder Prozent)
- currentDiskStatus wird gecached → neu verbundene Clients bekommen
den aktuellen Stand sofort beim 'init'
UI:
- Sticky Banner ganz oben, versteckt wenn Disk ok
- Farbe nach Level: gelb (info), orange (warn), rot (critical)
- Zeigt Prozent, Used/Total/Avail in GB, konkrete Situation
- Cleanup-Command als monospace Code mit Copy-Button ('docker system
prune -a --volumes -f') — Click auf Code oder Button kopiert ins
Clipboard, Fallback auf Range-Selektion
- 'Schliessen' Button fuer temporaeres Ausblenden (kommt aber wieder
bei naechster Aenderung)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
402bddc18a
commit
f15b3f583f
|
|
@ -127,6 +127,18 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Disk-Space Warnung (dynamisch gesetzt) -->
|
||||||
|
<div id="disk-banner" style="display:none;position:sticky;top:0;z-index:500;padding:10px 14px;border-radius:0;margin:-16px -16px 12px -16px;font-size:13px;">
|
||||||
|
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
||||||
|
<span id="disk-banner-icon" style="font-size:18px;">⚠️</span>
|
||||||
|
<span id="disk-banner-text" style="flex:1;min-width:200px;font-weight:600;"></span>
|
||||||
|
<code id="disk-banner-cmd" onclick="copyDiskCmd()" style="font-family:monospace;font-size:12px;padding:4px 8px;background:rgba(0,0,0,0.3);border-radius:4px;cursor:pointer;white-space:nowrap;">
|
||||||
|
docker system prune -a --volumes -f
|
||||||
|
</code>
|
||||||
|
<button onclick="copyDiskCmd()" class="btn secondary" style="padding:4px 10px;font-size:11px;">Kopieren</button>
|
||||||
|
<button onclick="document.getElementById('disk-banner').style.display='none'" class="btn secondary" style="padding:4px 10px;font-size:11px;">Schliessen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<h1>ARIA Diagnostic</h1>
|
<h1>ARIA Diagnostic</h1>
|
||||||
|
|
||||||
<!-- Haupt-Navigation -->
|
<!-- Haupt-Navigation -->
|
||||||
|
|
@ -753,6 +765,11 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.type === 'disk_status') {
|
||||||
|
updateDiskBanner(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.type === 'mode' && msg.payload) {
|
if (msg.type === 'mode' && msg.payload) {
|
||||||
// Bridge hat den Modus geaendert (evtl. von anderer App/Diagnostic) — UI syncen
|
// Bridge hat den Modus geaendert (evtl. von anderer App/Diagnostic) — UI syncen
|
||||||
const mode = (msg.payload.mode || '').toLowerCase();
|
const mode = (msg.payload.mode || '').toLowerCase();
|
||||||
|
|
@ -2155,6 +2172,55 @@
|
||||||
const ttsToggleEl = document.getElementById('tts-debug-toggle');
|
const ttsToggleEl = document.getElementById('tts-debug-toggle');
|
||||||
if (ttsToggleEl) ttsToggleEl.checked = showTtsDebug;
|
if (ttsToggleEl) ttsToggleEl.checked = showTtsDebug;
|
||||||
|
|
||||||
|
// Disk-Space Banner aktualisieren (wird vom Server via disk_status gepusht)
|
||||||
|
function updateDiskBanner(status) {
|
||||||
|
const banner = document.getElementById('disk-banner');
|
||||||
|
const icon = document.getElementById('disk-banner-icon');
|
||||||
|
const text = document.getElementById('disk-banner-text');
|
||||||
|
if (!banner) return;
|
||||||
|
if (!status || status.level === 'ok') {
|
||||||
|
banner.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gb = (n) => (n / 1024 / 1024 / 1024).toFixed(1);
|
||||||
|
const pct = status.percent;
|
||||||
|
const used = gb(status.usedBytes);
|
||||||
|
const total = gb(status.totalBytes);
|
||||||
|
const avail = gb(status.availBytes);
|
||||||
|
let bg, col, msg;
|
||||||
|
if (status.level === 'critical') {
|
||||||
|
bg = '#5C1A1A'; col = '#FF6B6B'; icon.innerHTML = '🚨'; // 🚨
|
||||||
|
msg = `KRITISCH: Platte ${pct}% voll (${used}GB von ${total}GB, nur noch ${avail}GB frei). aria-core kann bald nicht mehr schreiben — sofort aufraeumen!`;
|
||||||
|
} else if (status.level === 'warn') {
|
||||||
|
bg = '#5C3A1A'; col = '#FFAA55'; icon.innerHTML = '⚠️'; // ⚠️
|
||||||
|
msg = `Warnung: Platte ${pct}% voll (${avail}GB frei). Bald aufraeumen.`;
|
||||||
|
} else {
|
||||||
|
bg = '#4A3A1A'; col = '#FFD60A'; icon.innerHTML = 'ℹ️'; // ℹ️
|
||||||
|
msg = `Hinweis: Platte ${pct}% voll (${avail}GB frei).`;
|
||||||
|
}
|
||||||
|
banner.style.background = bg;
|
||||||
|
banner.style.color = col;
|
||||||
|
banner.style.borderBottom = `2px solid ${col}`;
|
||||||
|
text.textContent = msg;
|
||||||
|
banner.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyDiskCmd() {
|
||||||
|
const cmd = document.getElementById('disk-banner-cmd').textContent.trim();
|
||||||
|
navigator.clipboard.writeText(cmd).then(() => {
|
||||||
|
const btn = event.target;
|
||||||
|
const old = btn.textContent;
|
||||||
|
btn.textContent = 'Kopiert!';
|
||||||
|
setTimeout(() => { btn.textContent = old; }, 1500);
|
||||||
|
}).catch(() => {
|
||||||
|
// Fallback: selektieren
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNode(document.getElementById('disk-banner-cmd'));
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
window.getSelection().addRange(range);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
connectWS();
|
connectWS();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1148,6 +1148,53 @@ function updateAgentActivity() {
|
||||||
watchdogWarned = false;
|
watchdogWarned = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Disk-Space Monitor ───────────────────────────────
|
||||||
|
// Prueft regelmaessig die Host-Disk (via gemountetem /shared) und
|
||||||
|
// broadcastet bei kritischen Schwellwerten ein disk_status Event.
|
||||||
|
let lastDiskStatus = null;
|
||||||
|
let currentDiskStatus = null; // Vollstaendig fuer neu verbundene Clients
|
||||||
|
function checkDiskSpace() {
|
||||||
|
const { exec } = require("child_process");
|
||||||
|
exec("df -B1 /shared", (err, stdout) => {
|
||||||
|
if (err) return;
|
||||||
|
const lines = stdout.trim().split("\n");
|
||||||
|
if (lines.length < 2) return;
|
||||||
|
const cols = lines[1].split(/\s+/);
|
||||||
|
// Filesystem Size Used Avail Use% MountedOn
|
||||||
|
const total = parseInt(cols[1], 10);
|
||||||
|
const used = parseInt(cols[2], 10);
|
||||||
|
const avail = parseInt(cols[3], 10);
|
||||||
|
if (!total) return;
|
||||||
|
const pct = Math.round((used / total) * 100);
|
||||||
|
let level = "ok";
|
||||||
|
if (pct >= 95) level = "critical";
|
||||||
|
else if (pct >= 85) level = "warn";
|
||||||
|
else if (pct >= 70) level = "info";
|
||||||
|
const status = {
|
||||||
|
type: "disk_status",
|
||||||
|
level,
|
||||||
|
percent: pct,
|
||||||
|
usedBytes: used,
|
||||||
|
totalBytes: total,
|
||||||
|
availBytes: avail,
|
||||||
|
};
|
||||||
|
currentDiskStatus = status;
|
||||||
|
// Nur broadcasten wenn sich was geaendert hat (oder alle 60s Refresh)
|
||||||
|
const key = `${level}-${pct}`;
|
||||||
|
if (lastDiskStatus !== key) {
|
||||||
|
lastDiskStatus = key;
|
||||||
|
broadcast(status);
|
||||||
|
if (level !== "ok") {
|
||||||
|
log(level === "critical" ? "error" : "warn", "server",
|
||||||
|
`Disk ${pct}% belegt (${(used/1024/1024/1024).toFixed(1)}GB von ${(total/1024/1024/1024).toFixed(1)}GB)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Beim Start + alle 30s
|
||||||
|
setTimeout(checkDiskSpace, 2000);
|
||||||
|
setInterval(checkDiskSpace, 30000);
|
||||||
|
|
||||||
// Watchdog prüft alle 30s ob ARIA nach einer gesendeten Nachricht reagiert
|
// Watchdog prüft alle 30s ob ARIA nach einer gesendeten Nachricht reagiert
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
if (pendingMessageTime === 0) return; // Keine Nachricht gesendet
|
if (pendingMessageTime === 0) return; // Keine Nachricht gesendet
|
||||||
|
|
@ -1281,6 +1328,8 @@ wss.on("connection", (ws) => {
|
||||||
browserClients.add(ws);
|
browserClients.add(ws);
|
||||||
// Initialen State + letzte Logs senden
|
// Initialen State + letzte Logs senden
|
||||||
ws.send(JSON.stringify({ type: "init", state, logs: logs.slice(-100) }));
|
ws.send(JSON.stringify({ type: "init", state, logs: logs.slice(-100) }));
|
||||||
|
// Letzten Disk-Status mitgeben damit der Client sofort weiss wie's um Platz steht
|
||||||
|
if (currentDiskStatus) ws.send(JSON.stringify(currentDiskStatus));
|
||||||
|
|
||||||
ws.on("message", (raw) => {
|
ws.on("message", (raw) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue