v3 Phase A: Design-System-Fundament + Übersicht neu

- base.css/components.css: EINE Akzentfarbe (Teal), Metrik-Kacheln, Fit-Ampel,
  Modal, Quickstart-Reihen; beschriftete Sidebar; rueckwaertskompatibel
  (Legacy-Klassen + fehlende Vars --hi/--red/--red-dim definiert).
- index.html: beschriftete Navigation, Topbar mit Security-Chip, neue Overview-Mountpunkte.
- ui.js: Icon-Set erweitert + confirmModal/promptModal/fmtBytes/fmtPct (Beginner-UX-Helfer).
- overview.js: komplett neu (Klartext-Urteil, 4 Kacheln, System-Gesundheit-Balken,
  gefuehrter Schnellstart, "Dein Stack"). Inline-Styles raus.

Verifiziert: lokal 0 Konsolenfehler, Live-Metriken via WS, alle Views unbeschaedigt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-21 06:51:28 +02:00
parent 8f63c4969a
commit 52b0a3bff5
5 changed files with 418 additions and 346 deletions
+78
View File
@@ -58,6 +58,84 @@ export const ICON = {
compass: _svg('<circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/>'),
code: _svg('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'),
eye: _svg('<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>'),
refresh: _svg('<path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/>'),
file: _svg('<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M9 13h6M9 17h5"/>'),
database: _svg('<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/><path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/>'),
thermo: _svg('<path d="M14 14.76V5a2 2 0 0 0-4 0v9.76a4 4 0 1 0 4 0z"/>'),
shield: _svg('<path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7z"/><path d="m9 12 2 2 4-4"/>'),
info: _svg('<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>'),
chevron: _svg('<path d="m9 6 6 6-6 6"/>'),
bolt: _svg('<path d="M13 2 3 14h7l-1 8 10-12h-7z"/>'),
x: _svg('<path d="M18 6 6 18M6 6l12 12"/>'),
check: _svg('<path d="M20 6 9 17l-5-5"/>'),
download: _svg('<path d="M12 3v12M7 10l5 5 5-5"/><path d="M5 21h14"/>'),
clock: _svg('<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>'),
};
export function icon(name) { return ICON[name] || ""; }
// ---------------------------------------------------------------------------
// Format-Helfer (Klartext-Zahlen)
// ---------------------------------------------------------------------------
export function fmtBytes(b) {
if (!b && b !== 0) return "";
const gb = b / 1024 / 1024 / 1024;
if (gb >= 1) return gb.toFixed(1) + " GB";
return Math.round(b / 1024 / 1024) + " MB";
}
export function fmtPct(n) { return Math.round(n || 0) + " %"; }
// ---------------------------------------------------------------------------
// Bestätigungs-Dialog (ersetzt nacktes window.confirm) — gibt Promise<boolean>.
// Für heikle Aktionen mit Klartext-Konsequenz.
// ---------------------------------------------------------------------------
export function confirmModal({ title, body, confirmLabel = "Bestätigen", cancelLabel = "Abbrechen", danger = false }) {
return new Promise(resolve => {
const ov = document.createElement("div");
ov.className = "modal-overlay";
ov.innerHTML = `<div class="modal-card" role="dialog" aria-modal="true">
<h3>${esc(title)}</h3>
<p>${body}</p>
<div class="modal-actions">
<button data-x="0">${esc(cancelLabel)}</button>
<button class="${danger ? "danger" : "primary"}" data-x="1">${esc(confirmLabel)}</button>
</div></div>`;
const done = v => { ov.remove(); resolve(v); };
ov.addEventListener("click", e => {
if (e.target === ov) return done(false);
const b = e.target.closest("[data-x]");
if (b) done(b.getAttribute("data-x") === "1");
});
document.body.appendChild(ov);
ov.querySelector('[data-x="1"]').focus();
});
}
// ---------------------------------------------------------------------------
// Eingabe-Dialog (ersetzt window.prompt) — gibt Promise<string|null>.
// password:true blendet die Eingabe aus (für sudo-Passwort).
// ---------------------------------------------------------------------------
export function promptModal({ title, body = "", placeholder = "", password = false, confirmLabel = "Weiter", danger = false }) {
return new Promise(resolve => {
const ov = document.createElement("div");
ov.className = "modal-overlay";
ov.innerHTML = `<div class="modal-card" role="dialog" aria-modal="true">
<h3>${esc(title)}</h3>
${body ? `<p>${body}</p>` : ""}
<input type="${password ? "password" : "text"}" placeholder="${esc(placeholder)}" autocomplete="off" style="margin-bottom:18px">
<div class="modal-actions">
<button data-x="0">Abbrechen</button>
<button class="${danger ? "danger" : "primary"}" data-x="1">${esc(confirmLabel)}</button>
</div></div>`;
const inp = ov.querySelector("input");
const done = v => { ov.remove(); resolve(v); };
ov.addEventListener("click", e => {
if (e.target === ov) return done(null);
const b = e.target.closest("[data-x]");
if (b) done(b.getAttribute("data-x") === "1" ? (inp.value || "") : null);
});
inp.addEventListener("keydown", e => { if (e.key === "Enter") done(inp.value || ""); if (e.key === "Escape") done(null); });
document.body.appendChild(ov);
inp.focus();
});
}