From 04bac7e13e507afcfff83edcee54e0987d5e4b62 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sat, 20 Jun 2026 21:34:14 +0200 Subject: [PATCH] feat: server management (Feature 1) --- routers/maintenance.py | 23 +++++++++++++ static/js/panels/maintenance.js | 60 ++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 8 deletions(-) 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

+
+ + +
- + +
+
+ Dienste starten via passwortlosem Sudo neu. +
+ +

Betriebssystem (Bosgame)

+
+ +
- 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); } }