// ui.js — kleine DOM-Helfer, Toast, Badge, Escaping + Inline-Icon-Set. // Bewusst kein Icon-Font / kein CDN: SVGs als Strings, faerben via currentColor. export const $ = (s, r = document) => r.querySelector(s); export const $$ = (s, r = document) => [...r.querySelectorAll(s)]; export function esc(s) { return String(s ?? "").replace(/[&<>]/g, c => ({ "&": "&", "<": "<", ">": ">" }[c])); } let _tt; export function toast(msg, err = false) { const t = $("#toast"); if (!t) return; t.textContent = msg; t.className = "toast show" + (err ? " err" : ""); clearTimeout(_tt); _tt = setTimeout(() => (t.className = "toast"), 3200); } // Modell-Status -> Badge-HTML export function badge(state) { if (state === "running" || state === "ready") return 'geladen'; if (state === "loading" || state === "starting") return 'lädt…'; return 'bereit'; } // relative Zeit aus Unix-Sekunden (z.B. "2m", "13h") export function ago(ts) { if (!ts) return ""; const s = Math.max(0, Math.floor(Date.now() / 1000 - ts)); if (s < 60) return s + "s"; if (s < 3600) return Math.floor(s / 60) + "m"; if (s < 86400) return Math.floor(s / 3600) + "h"; return Math.floor(s / 86400) + "d"; } const _svg = (p) => `${p}`; // Icon-Set (Stroke-Style, an die Referenz angelehnt) export const ICON = { logo: _svg(''), grid: _svg(''), cpu: _svg(''), pulse: _svg(''), server: _svg(''), book: _svg(''), help: _svg(''), settings: _svg(''), swap: _svg(''), monitor: _svg(''), layers: _svg(''), alert: _svg(''), gauge: _svg(''), search: _svg(''), compass: _svg(''), code: _svg(''), eye: _svg(''), refresh: _svg(''), file: _svg(''), database: _svg(''), thermo: _svg(''), shield: _svg(''), info: _svg(''), chevron: _svg(''), bolt: _svg(''), x: _svg(''), check: _svg(''), download: _svg(''), clock: _svg(''), }; 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. // 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 = ``; 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. // 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 = ``; 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(); }); }