Fix: Engine-Update-Badge bleibt nicht mehr haengen + Update-Detail-Fenster
- engine_update_job leert jetzt _engine_cache nach Abschluss (on_done), sonst zeigte das Dashboard bis zu 1h "Update verfuegbar" trotz erfolgter Aktualisierung (1h-Cache wurde nie invalidiert wie bei den anderen Jobs). - check_updates_job leert zusaetzlich _comp_cache, damit "Nach Updates suchen" auch den Hermes-Status frisch prueft. - Neu: GET /api/maintenance/update-details (os|engine|hermes) liefert, was genau aktualisiert wird (apt-Paketliste, Engine Build X->Y + Release-Notes, Hermes-Commits HEAD..origin/branch). - Frontend: "Aktualisieren"-Buttons -> "Anzeigen"; oeffnen ein Detail-Fenster mit den konkreten Aenderungen, erst "Jetzt aktualisieren" startet das Update. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,13 @@ class RestartReq(BaseModel):
|
||||
def updates() -> dict:
|
||||
return maintenance.updates()
|
||||
|
||||
|
||||
@router.get("/maintenance/update-details")
|
||||
def update_details(kind: str) -> dict:
|
||||
if kind not in ("os", "engine", "hermes"):
|
||||
raise HTTPException(400, "Unbekannte Update-Art.")
|
||||
return maintenance.update_details(kind)
|
||||
|
||||
@router.post("/maintenance/check-updates")
|
||||
def check_updates(body: SudoReq) -> dict:
|
||||
res = maintenance.check_updates_job(body.sudo_password)
|
||||
|
||||
@@ -245,6 +245,80 @@ def updates() -> dict:
|
||||
"components": _components_cached()}
|
||||
|
||||
|
||||
# ── Update-Details (was genau wird aktualisiert) — on-demand beim Öffnen des Fensters ──
|
||||
|
||||
def os_update_details() -> dict:
|
||||
"""Liste der aktualisierbaren apt-Pakete (Name, installiert → Kandidat)."""
|
||||
out_pkgs: list[dict] = []
|
||||
try:
|
||||
out = subprocess.run(["bash", "-c", "apt list --upgradable 2>/dev/null"],
|
||||
capture_output=True, text=True, timeout=20)
|
||||
for line in (out.stdout or "").splitlines():
|
||||
# Format: name/repo neue_version arch [upgradable from: alte_version]
|
||||
m = re.match(r"^([^/\s]+)/\S+\s+(\S+)\s+\S+\s+\[upgradable from:\s*([^\]]+)\]",
|
||||
line.strip())
|
||||
if m:
|
||||
out_pkgs.append({"name": m.group(1), "candidate": m.group(2),
|
||||
"current": m.group(3).strip()})
|
||||
out_pkgs.sort(key=lambda p: p["name"])
|
||||
return {"kind": "os", "count": len(out_pkgs), "packages": out_pkgs}
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return {"kind": "os", "count": len(out_pkgs), "packages": out_pkgs, "error": str(exc)}
|
||||
|
||||
|
||||
def engine_update_details() -> dict:
|
||||
"""Installierte vs. neueste Engine-Build-Nummer + Release-Name/-Notizen/-Link."""
|
||||
info: dict = {"kind": "engine", "installed_build": _installed_engine_build(),
|
||||
"latest_build": None, "latest_tag": None, "name": None,
|
||||
"url": None, "body": None}
|
||||
try:
|
||||
rel = httpx.get(f"https://api.github.com/repos/{ENGINE_REPO}/releases/latest",
|
||||
timeout=8, headers={"User-Agent": "MissionControl2"}).json()
|
||||
tag = str(rel.get("tag_name", ""))
|
||||
info["latest_tag"] = tag
|
||||
info["latest_build"] = int(m.group(1)) if (m := re.search(r"(\d{3,})", tag)) else None
|
||||
info["name"] = rel.get("name") or tag
|
||||
info["url"] = rel.get("html_url")
|
||||
body = (rel.get("body") or "").strip()
|
||||
info["body"] = body[:2000] if body else None
|
||||
except Exception as exc: # noqa: BLE001
|
||||
info["error"] = str(exc)
|
||||
return info
|
||||
|
||||
|
||||
def hermes_update_details() -> dict:
|
||||
"""Commits, die ein Hermes-Update einspielen würde (HEAD..origin/<branch>)."""
|
||||
info: dict = {"kind": "hermes", "branch": None, "behind": 0, "commits": []}
|
||||
git = system.find_hermes_agent_git()
|
||||
if not git or not git.get("path"):
|
||||
info["error"] = "Hermes-Agent-Repo nicht gefunden."
|
||||
return info
|
||||
path = git["path"]
|
||||
try:
|
||||
branch = (subprocess.run(["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"],
|
||||
capture_output=True, text=True, timeout=8).stdout.strip() or "main")
|
||||
info["branch"] = branch
|
||||
subprocess.run(["git", "-C", path, "fetch", "-q", "origin", branch],
|
||||
capture_output=True, text=True, timeout=25)
|
||||
log = subprocess.run(["git", "-C", path, "log", "--pretty=format:%h\x1f%s\x1f%cr",
|
||||
f"HEAD..origin/{branch}"], capture_output=True, text=True, timeout=10)
|
||||
commits = []
|
||||
for line in (log.stdout or "").splitlines():
|
||||
parts = line.split("\x1f")
|
||||
if len(parts) == 3:
|
||||
commits.append({"hash": parts[0], "subject": parts[1], "when": parts[2]})
|
||||
info["commits"] = commits
|
||||
info["behind"] = len(commits)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
info["error"] = str(exc)
|
||||
return info
|
||||
|
||||
|
||||
def update_details(kind: str) -> dict:
|
||||
return {"os": os_update_details, "engine": engine_update_details,
|
||||
"hermes": hermes_update_details}.get(kind, lambda: {"error": "unbekannt"})()
|
||||
|
||||
|
||||
def _run(cmd: list[str], sudo_password: str | None = None) -> dict:
|
||||
actual_cmd = list(cmd)
|
||||
has_sudo = False
|
||||
@@ -321,7 +395,8 @@ def check_updates_job(sudo_password: str | None = None) -> dict:
|
||||
|
||||
def on_done():
|
||||
_engine_cache.update(ts=0.0, avail=False)
|
||||
|
||||
_comp_cache.update(ts=0.0, data=[]) # Hermes-Status ebenfalls neu berechnen lassen
|
||||
|
||||
cmd = "sudo apt-get update"
|
||||
job_id = jobengine.start_job(["bash", "-c", cmd], "Nach Updates suchen", on_done=on_done, sudo_password=sudo_password)
|
||||
return {"ok": True, "job_id": job_id}
|
||||
@@ -340,9 +415,14 @@ def engine_update_job(sudo_password: str | None = None) -> dict | None:
|
||||
return None
|
||||
if err := check_sudo_needs_password(sudo_password):
|
||||
return err
|
||||
|
||||
def on_done():
|
||||
_engine_cache.update(ts=0.0, avail=False) # Cache leeren → frischer Build-Vergleich
|
||||
|
||||
# update-engine.sh läuft via sudo als root und startet llama-swap am Ende selbst neu.
|
||||
job_id = jobengine.start_job(["bash", "-c", ENGINE_UPDATE_CMD],
|
||||
"Engine-Update (llama.cpp Vulkan)", sudo_password=sudo_password)
|
||||
"Engine-Update (llama.cpp Vulkan)",
|
||||
on_done=on_done, sudo_password=sudo_password)
|
||||
return {"ok": True, "job_id": job_id}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user