feat: server management (Feature 1)

This commit is contained in:
Hitonabi
2026-06-20 21:34:14 +02:00
parent 8800dd4558
commit 04bac7e13e
2 changed files with 75 additions and 8 deletions
+23
View File
@@ -7,8 +7,10 @@ Server-Wartung hinein (siehe Roadmap: Server-Management).
""" """
import shlex import shlex
import subprocess
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from auth import auth from auth import auth
from config import UPDATE_CMD from config import UPDATE_CMD
@@ -16,6 +18,8 @@ from jobengine import start_job
router = APIRouter(prefix="/api", dependencies=[Depends(auth)]) router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
class PwdReq(BaseModel):
password: str
@router.post("/update") @router.post("/update")
def update(): def update():
@@ -23,3 +27,22 @@ def update():
raise HTTPException(400, "Kein Update-Befehl gesetzt (MC_UPDATE_CMD).") raise HTTPException(400, "Kein Update-Befehl gesetzt (MC_UPDATE_CMD).")
job_id = start_job(shlex.split(UPDATE_CMD), "update containers") job_id = start_job(shlex.split(UPDATE_CMD), "update containers")
return {"job_id": job_id} 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."}
+52 -8
View File
@@ -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 { api } from "../core/api.js";
import { $, toast } from "../core/ui.js"; import { $, toast } from "../core/ui.js";
import { track } from "./jobs.js"; import { track } from "./jobs.js";
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
function mount() { function mount() {
$("#wartung").innerHTML = ` $("#wartung").innerHTML = `
<div class="card-h"><h3>Wartung</h3></div> <div class="card-h"><h3>Dienste & Applikation</h3></div>
<div class="btn-row" style="margin-bottom:20px">
<button id="w-restart-swap">llama-swap neustarten</button>
<button id="w-restart-mc">Mission Control neustarten</button>
</div>
<div class="btn-row"> <div class="btn-row">
<button id="w-update">Container aktualisieren</button> <button id="w-update">Container aktualisieren</button>
<button id="w-unload" class="danger">Alles aus dem Speicher</button> <button id="w-unload" class="ghost">Modelle entladen</button>
</div>
<div class="hint" style="margin-top:12px; margin-bottom:32px">
Dienste starten via passwortlosem Sudo neu.
</div>
<div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
<div class="btn-row">
<button id="w-os-update">OS-Updates installieren (apt update)</button>
<button id="w-reboot" class="danger">Server Reboot</button>
</div> </div>
<div class="hint" style="margin-top:12px"> <div class="hint" style="margin-top:12px">
Update-Befehl wird per <span class="mono-sm">MC_UPDATE_CMD</span> gesetzt. Für tiefe Eingriffe fragt das Dashboard einmalig das sudo-Passwort ab.
Server-Steuerung (Dienste, OS-Updates, Reboot) folgt als eigener Bereich.
</div>`; </div>`;
$("#w-restart-swap").addEventListener("click", () => restartService("llama-swap"));
$("#w-restart-mc").addEventListener("click", () => restartService("mission-control"));
$("#w-update").addEventListener("click", update); $("#w-update").addEventListener("click", update);
$("#w-unload").addEventListener("click", unloadAll); $("#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() { async function update() {
@@ -33,7 +77,7 @@ async function unloadAll() {
try { try {
await api("/api/unload", { method: "POST" }); await api("/api/unload", { method: "POST" });
toast("Alle Modelle entladen."); toast("Alle Modelle entladen.");
setTimeout(() => document.dispatchEvent(new Event("mc:refresh")), 600); setTimeout(refreshSoon, 600);
} catch (e) { toast(e.message, true); } } catch (e) { toast(e.message, true); }
} }