diff --git a/routers/maintenance.py b/routers/maintenance.py
index 1397faa..b793b30 100644
--- a/routers/maintenance.py
+++ b/routers/maintenance.py
@@ -7,8 +7,10 @@ Server-Wartung hinein (siehe Roadmap: Server-Management).
"""
import shlex
+import subprocess
from fastapi import APIRouter, Depends, HTTPException
+from pydantic import BaseModel
from auth import auth
from config import UPDATE_CMD
@@ -16,6 +18,8 @@ from jobengine import start_job
router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
+class PwdReq(BaseModel):
+ password: str
@router.post("/update")
def update():
@@ -23,3 +27,22 @@ def update():
raise HTTPException(400, "Kein Update-Befehl gesetzt (MC_UPDATE_CMD).")
job_id = start_job(shlex.split(UPDATE_CMD), "update containers")
return {"job_id": job_id}
+
+@router.post("/os-update")
+def os_update(req: PwdReq):
+ cmd = f"echo {shlex.quote(req.password)} | sudo -S bash -c 'apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y'"
+ job_id = start_job(["bash", "-c", cmd], "os update")
+ return {"job_id": job_id}
+
+@router.post("/service/{name}/restart")
+def service_restart(name: str):
+ if name not in ("llama-swap", "mission-control"):
+ raise HTTPException(400, "Dienst nicht erlaubt.")
+ subprocess.Popen(["sudo", "systemctl", "restart", name])
+ return {"ok": True, "note": f"Restart {name} getriggert."}
+
+@router.post("/reboot")
+def reboot(req: PwdReq):
+ cmd = f"echo {shlex.quote(req.password)} | sudo -S reboot"
+ subprocess.Popen(["bash", "-c", cmd])
+ return {"ok": True, "note": "Reboot getriggert."}
diff --git a/static/js/panels/maintenance.js b/static/js/panels/maintenance.js
index ae22dd4..5fb6da1 100644
--- a/static/js/panels/maintenance.js
+++ b/static/js/panels/maintenance.js
@@ -1,24 +1,68 @@
-// maintenance.js — Wartungs-Karte: Container aktualisieren + alle Modelle entladen.
-// Spaeter waechst hier das Server-Management an (Roadmap Feature 1).
-
import { api } from "../core/api.js";
import { $, toast } from "../core/ui.js";
import { track } from "./jobs.js";
+function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
+
function mount() {
$("#wartung").innerHTML = `
-
Wartung
+
Dienste & Applikation
+
+ llama-swap neustarten
+ Mission Control neustarten
+
Container aktualisieren
- Alles aus dem Speicher
+ Modelle entladen
+
+
+ Dienste starten via passwortlosem Sudo neu.
+
+
+
Betriebssystem (Bosgame)
+
+ OS-Updates installieren (apt update)
+ Server Reboot
- Update-Befehl wird per MC_UPDATE_CMD gesetzt.
- Server-Steuerung (Dienste, OS-Updates, Reboot) folgt als eigener Bereich.
+ Für tiefe Eingriffe fragt das Dashboard einmalig das sudo-Passwort ab.
`;
+ $("#w-restart-swap").addEventListener("click", () => restartService("llama-swap"));
+ $("#w-restart-mc").addEventListener("click", () => restartService("mission-control"));
$("#w-update").addEventListener("click", update);
$("#w-unload").addEventListener("click", unloadAll);
+
+ $("#w-os-update").addEventListener("click", osUpdate);
+ $("#w-reboot").addEventListener("click", rebootServer);
+}
+
+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() {
@@ -33,7 +77,7 @@ async function unloadAll() {
try {
await api("/api/unload", { method: "POST" });
toast("Alle Modelle entladen.");
- setTimeout(() => document.dispatchEvent(new Event("mc:refresh")), 600);
+ setTimeout(refreshSoon, 600);
} catch (e) { toast(e.message, true); }
}