v3 Phase B: alle Panels auf das Design-System + Beginner-UX
- cookbook.js: Fit-Ampel (gruen/gelb/rot) + Legende + Klartext-Urteile, sauberes Modal. - server.js: heikle Aktionen mit confirmModal/promptModal (Klartext-Konsequenz), Konsole im neuen Stil, Begriffe uebersetzt. - models.js: Tabelle re-skinnt (Capability-Tags statt Emoji, --blue raus), Entladen mit Bestaetigung, Konfig-Modal vereinheitlicht. - jobs.js (Aktivitaet): Metrik-Kacheln + Klartext-Verlaeufe. - guides.js: Kopf + Intro, Integrations-URL aus Browser-Host abgeleitet. - index.html: Mountpunkte fuer Modelle-/Aktivitaets-Kopf. - app.py: no-cache-Middleware fuer /static (UI-Aenderungen wirken sofort nach rsync, kein Stale-JS mehr). - base.css: Sidebar bei schmalem Viewport icon-only (Label-Ueberlappung gefixt). Verifiziert: alle 6 Panels mounten fehlerfrei (0 Konsolenfehler), Fit-Ampel rechnet live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+77
-92
@@ -1,120 +1,105 @@
|
||||
// server.js — Server & Wartung (v3): Dienste, OS-Updates, Reboot, Live-Konsole.
|
||||
// Heikle Aktionen mit Klartext-Bestätigung (confirmModal/promptModal).
|
||||
|
||||
import { api, getToken } from "../core/api.js";
|
||||
import { $, toast } from "../core/ui.js";
|
||||
import { $, toast, icon, confirmModal, promptModal } from "../core/ui.js";
|
||||
import { track } from "./jobs.js";
|
||||
|
||||
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
|
||||
|
||||
function mount() {
|
||||
$("#wartung").innerHTML = `
|
||||
<div class="card-h"><h3>Dienste & Applikation</h3></div>
|
||||
<div class="btn-row">
|
||||
<button id="w-restart-swap" title="Startet die LLM-Engine neu. Dauer ca. 5 Sekunden.">llama-swap neustarten</button>
|
||||
<button id="w-restart-mc" title="Startet Mission Control (das Dashboard) neu.">Mission Control neustarten</button>
|
||||
<button id="w-update" title="Zieht den neuesten Code via Git und aktualisiert das Dashboard.">Mission Control System-Update</button>
|
||||
<button id="w-unload" class="ghost" title="Wirft alle derzeit in VRAM geladenen Modelle sofort raus.">Aus dem VRAM entfernen</button>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:12px; margin-bottom:32px">
|
||||
Dienste starten via passwortlosem Sudo neu.
|
||||
$("#wartung").outerHTML = `<div id="wartung" style="display:flex;flex-direction:column;gap:var(--sp-4)">
|
||||
<div class="pagehead"><div>
|
||||
<h1>Server & Wartung</h1>
|
||||
<div class="sub">Dienste steuern, Updates einspielen und live mitlesen — ohne SSH/Terminal.</div>
|
||||
</div></div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Dienste & Applikation</h3></div>
|
||||
<div class="card-sub">Neustarts sind harmlos und passwortlos — nichts geht dabei verloren.</div>
|
||||
<div class="btn-row">
|
||||
<button id="w-restart-swap">LLM-Engine neustarten</button>
|
||||
<button id="w-restart-mc">Dashboard neustarten</button>
|
||||
<button id="w-update">Nach Updates suchen</button>
|
||||
<button id="w-unload" class="ghost">Grafikspeicher leeren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
|
||||
<div class="btn-row">
|
||||
<button id="w-os-update" title="Führt apt update & upgrade aus, um das Betriebssystem (Bosgame) zu aktualisieren.">OS-Updates installieren (apt update)</button>
|
||||
<button id="w-reboot" class="danger" title="Startet den gesamten Server physikalisch neu. Alles ist kurz offline.">Server Reboot</button>
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
|
||||
<div class="card-sub">Tiefe Eingriffe — das Dashboard fragt einmalig nach deinem sudo-Passwort.</div>
|
||||
<div class="btn-row">
|
||||
<button id="w-os-update">OS-Updates installieren</button>
|
||||
<button id="w-reboot" class="danger">Server neustarten (Reboot)</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:12px; margin-bottom:32px">
|
||||
Für tiefe Eingriffe fragt das Dashboard einmalig das sudo-Passwort ab.
|
||||
</div>
|
||||
|
||||
<div class="card-h">
|
||||
<h3>Live-Konsole</h3>
|
||||
<select id="w-console-sel" style="margin-left:auto; width:200px">
|
||||
<option value="llama-swap">llama-swap</option>
|
||||
<option value="mission-control">mission-control</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="w-console" style="background:#111; color:#0f0; font-family:monospace; font-size:12px; padding:12px; border-radius:8px; height:400px; overflow-y:auto; white-space:pre-wrap;">
|
||||
Verbinde...
|
||||
</div>`;
|
||||
|
||||
$("#w-restart-swap").addEventListener("click", () => restartService("llama-swap"));
|
||||
$("#w-restart-mc").addEventListener("click", () => restartService("mission-control"));
|
||||
<div class="card">
|
||||
<div class="card-h"><h3>Live-Konsole</h3>
|
||||
<select id="w-console-sel" style="margin:0 0 0 auto;width:220px">
|
||||
<option value="llama-swap">LLM-Engine (llama-swap)</option>
|
||||
<option value="mission-control">Dashboard (mission-control)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="card-sub">Was der Dienst gerade tut — live mitlesen.</div>
|
||||
<div id="w-console" class="console">Verbinde…</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$("#w-restart-swap").addEventListener("click", () => restartService("llama-swap",
|
||||
"LLM-Engine neustarten?", "Die Engine startet neu. Geladene Modelle werden kurz entladen (~5 Sekunden), laden danach automatisch wieder."));
|
||||
$("#w-restart-mc").addEventListener("click", () => restartService("mission-control",
|
||||
"Dashboard neustarten?", "Diese Oberfläche trennt sich kurz und verbindet automatisch wieder. Laufende Downloads laufen weiter."));
|
||||
$("#w-update").addEventListener("click", update);
|
||||
$("#w-unload").addEventListener("click", unloadAll);
|
||||
|
||||
$("#w-os-update").addEventListener("click", osUpdate);
|
||||
$("#w-reboot").addEventListener("click", rebootServer);
|
||||
|
||||
$("#w-console-sel").addEventListener("change", () => connectConsole());
|
||||
$("#w-console-sel").addEventListener("change", connectConsole);
|
||||
connectConsole();
|
||||
}
|
||||
|
||||
let ws = null;
|
||||
|
||||
function connectConsole() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
const svc = $("#w-console-sel").value;
|
||||
const out = $("#w-console");
|
||||
out.innerHTML = "Verbinde mit " + svc + "...\n";
|
||||
|
||||
if (ws) { ws.close(); ws = null; }
|
||||
const svc = $("#w-console-sel").value, out = $("#w-console");
|
||||
out.textContent = "Verbinde mit " + svc + "…\n";
|
||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const url = `${proto}//${location.host}/api/logs/${svc}?token=${getToken()}`;
|
||||
ws = new WebSocket(url);
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
out.innerHTML += e.data;
|
||||
out.scrollTop = out.scrollHeight;
|
||||
};
|
||||
ws.onclose = () => {
|
||||
out.innerHTML += "\n--- Verbindung getrennt ---";
|
||||
};
|
||||
ws = new WebSocket(`${proto}//${location.host}/api/logs/${svc}?token=${encodeURIComponent(getToken())}`);
|
||||
ws.onmessage = e => { out.textContent += e.data; out.scrollTop = out.scrollHeight; };
|
||||
ws.onclose = () => { out.textContent += "\n— Verbindung getrennt —"; };
|
||||
}
|
||||
|
||||
async function restartService(name) {
|
||||
try {
|
||||
await api("/api/service/" + name + "/restart", { method: "POST" });
|
||||
toast("Neustart ausgelöst: " + name);
|
||||
setTimeout(refreshSoon, 2000);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function osUpdate() {
|
||||
const pwd = window.prompt("Bitte sudo Passwort eingeben (für apt-get update & upgrade):");
|
||||
if (!pwd) return;
|
||||
try {
|
||||
const r = await api("/api/os-update", { method: "POST", body: JSON.stringify({ password: pwd }) });
|
||||
toast("OS-Update gestartet.");
|
||||
track(r.job_id);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function rebootServer() {
|
||||
if (!window.confirm("ACHTUNG: Server wird komplett neu gestartet. Fortfahren?")) return;
|
||||
const pwd = window.prompt("Bitte sudo Passwort eingeben (für reboot):");
|
||||
if (!pwd) return;
|
||||
try {
|
||||
await api("/api/reboot", { method: "POST", body: JSON.stringify({ password: pwd }) });
|
||||
toast("Reboot ausgelöst. UI ist gleich offline.");
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function update() {
|
||||
try {
|
||||
const r = await api("/api/update", { method: "POST" });
|
||||
toast("Update läuft.");
|
||||
track(r.job_id);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
async function restartService(name, title, body) {
|
||||
if (!await confirmModal({ title, body, confirmLabel: "Neustarten" })) return;
|
||||
try { await api(`/api/service/${name}/restart`, { method: "POST" }); toast("Neustart ausgelöst: " + name); setTimeout(refreshSoon, 2000); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function unloadAll() {
|
||||
try {
|
||||
await api("/api/unload", { method: "POST" });
|
||||
toast("Alle Modelle entladen.");
|
||||
setTimeout(refreshSoon, 600);
|
||||
} catch (e) { toast(e.message, true); }
|
||||
if (!await confirmModal({ title: "Grafikspeicher leeren?", body: "Alle geladenen Modelle werden entladen. Sie laden beim nächsten Aufruf automatisch neu — es geht nichts verloren.", confirmLabel: "Leeren" })) return;
|
||||
try { await api("/api/unload", { method: "POST" }); toast("Grafikspeicher geleert."); setTimeout(refreshSoon, 600); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function update() {
|
||||
try { const r = await api("/api/update", { method: "POST" }); toast("Update läuft — siehe Aktivität."); track(r.job_id); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function osUpdate() {
|
||||
if (!await confirmModal({ title: "OS-Updates installieren?", body: "Führt <code>apt update & upgrade</code> aus. Das kann ein paar Minuten dauern; der Fortschritt erscheint in der Aktivität." })) return;
|
||||
const pwd = await promptModal({ title: "sudo-Passwort", body: "Für die System-Updates wird einmalig dein sudo-Passwort gebraucht.", placeholder: "sudo-Passwort", password: true, confirmLabel: "Installieren" });
|
||||
if (!pwd) return;
|
||||
try { const r = await api("/api/os-update", { method: "POST", body: JSON.stringify({ password: pwd }) }); toast("OS-Update gestartet."); track(r.job_id); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
async function rebootServer() {
|
||||
if (!await confirmModal({ title: "Server wirklich neu starten?", body: "Der ganze Bosgame startet physisch neu. Alles ist für ~1 Minute offline — auch dieses Dashboard.", confirmLabel: "Reboot", danger: true })) return;
|
||||
const pwd = await promptModal({ title: "sudo-Passwort", body: "Für den Reboot wird einmalig dein sudo-Passwort gebraucht.", placeholder: "sudo-Passwort", password: true, confirmLabel: "Jetzt neustarten", danger: true });
|
||||
if (!pwd) return;
|
||||
try { await api("/api/reboot", { method: "POST", body: JSON.stringify({ password: pwd }) }); toast("Reboot ausgelöst — bis gleich."); }
|
||||
catch (e) { toast(e.message, true); }
|
||||
}
|
||||
|
||||
export default { id: "server", mount };
|
||||
|
||||
Reference in New Issue
Block a user