70c7cba613
- Neuer Self-Update-Button ("Mission Control aktualisieren"): POST /api/self-update
macht git pull (Quelle) -> rsync nach Prod -> systemctl restart (NOPASSWD).
Pfade aus config.py (SOURCE_DIR/PROD_DIR), nicht user-hartkodiert.
- "LLM-Engine aktualisieren" ehrlich (war MC, ist llama.cpp via MC_UPDATE_CMD).
- Guides: Kopier-Buttons fuer alle Configs + Codeblock (Copy-Paste-tauglich).
- Button-Audit: Funktionen/Labels in allen Panels geprueft, stimmig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
7.2 KiB
JavaScript
118 lines
7.2 KiB
JavaScript
// server.js — Server & Wartung (v3): Dienste, OS-Updates, Reboot, Live-Konsole.
|
|
// Selbsterklärende Aktionszeilen + heikle Aktionen mit Klartext-Bestätigung.
|
|
|
|
import { api, getToken } from "../core/api.js";
|
|
import { $, toast, icon, confirmModal, promptModal } from "../core/ui.js";
|
|
import { track } from "./jobs.js";
|
|
|
|
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
|
|
|
|
function row(id, ic, tone, title, desc) {
|
|
return `<button class="qa${tone === "red" ? " qa-danger" : ""}" id="${id}">
|
|
<span class="qa-ic ${tone}">${icon(ic)}</span>
|
|
<span class="qa-main"><span class="qa-t">${title}</span><span class="qa-s">${desc}</span></span>
|
|
<span class="qa-arrow">${icon("chevron")}</span></button>`;
|
|
}
|
|
|
|
function mount() {
|
|
$("#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 — ganz ohne SSH oder Terminal.</div>
|
|
</div></div>
|
|
|
|
<div class="card">
|
|
<div class="card-h"><h3>Dienste & Applikation</h3></div>
|
|
<div class="card-sub">Diese Aktionen sind harmlos und passwortlos — es geht nichts dabei verloren.</div>
|
|
${row("w-restart-swap", "refresh", "teal", "LLM-Engine neustarten", "Startet llama-swap neu. Geladene Modelle werden kurz entladen (~5 Sek), laden danach automatisch wieder.")}
|
|
${row("w-restart-mc", "refresh", "blue", "Dashboard neustarten", "Startet diese Oberfläche neu. Sie trennt kurz und verbindet automatisch wieder.")}
|
|
${row("w-self-update", "download", "teal", "Mission Control aktualisieren", "Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu (~10 Sek).")}
|
|
${row("w-update", "bolt", "blue", "LLM-Engine aktualisieren", "Lädt die neueste llama.cpp-Version (ROCm) für deine GPU und installiert sie.")}
|
|
${row("w-unload", "database", "amber", "Grafikspeicher leeren", "Wirft alle aktuell geladenen Modelle aus dem VRAM. Fragt vorher nach.")}
|
|
</div>
|
|
|
|
<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>
|
|
${row("w-os-update", "refresh", "amber", "OS-Updates installieren", "Führt apt update & upgrade aus. Kann ein paar Minuten dauern — Fortschritt in der Aktivität.")}
|
|
${row("w-reboot", "bolt", "red", "Server neustarten (Reboot)", "Bootet den ganzen Bosgame neu. Alles ist für ~1 Minute offline, auch dieses Dashboard.")}
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-h"><h3>Live-Konsole</h3>
|
|
<select id="w-console-sel" style="margin:0 0 0 auto;width:240px">
|
|
<option value="llama-swap">LLM-Engine (llama-swap)</option>
|
|
<option value="mission-control">Dashboard (mission-control)</option>
|
|
<option value="system">System (ganzer Server)</option>
|
|
</select>
|
|
</div>
|
|
<div class="card-sub">Live mitlesen, was der Dienst gerade tut.</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-self-update").addEventListener("click", selfUpdate);
|
|
$("#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);
|
|
connectConsole();
|
|
}
|
|
|
|
let ws = null;
|
|
function connectConsole() {
|
|
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:";
|
|
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, 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() {
|
|
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 selfUpdate() {
|
|
if (!await confirmModal({ title: "Mission Control aktualisieren?", body: "Holt die neueste Dashboard-Version aus Gitea und startet automatisch neu. Die Seite trennt kurz und verbindet von selbst wieder (~10 Sek)." })) return;
|
|
try { const r = await api("/api/self-update", { method: "POST" }); toast("Update läuft — Dashboard startet gleich neu."); track(r.job_id); }
|
|
catch (e) { toast(e.message, true); }
|
|
}
|
|
|
|
async function update() {
|
|
try { const r = await api("/api/update", { method: "POST" }); toast("llama.cpp-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 };
|