Update-all-Button: alle ausstehenden System-Updates in EINEM Job
Neuer POST /api/maintenance/update-all kettet die AUSSTEHENDEN Updates sequenziell (Engine -> Router -> Hermes -> OS) in einem maintenance-Job. Bewusst reine Wiederverwendung: jeder Teil ist exakt der Befehl des Einzel-Updates inkl. dessen Backup/Postcheck/Selbst-Rollback; &&-Kette stoppt beim ersten Fehler, Banner-Zeilen im Log zeigen den Schritt. OS zuletzt (breitester Eingriff, braucht als einziges das Box-Passwort; fehlt es, laufen die sudo-freien Teile trotzdem und OS wird uebersprungen). Hermes-Befehlskette in _hermes_update_cmd() extrahiert (DRY). UI (SystemDrawer/Updates): Button 'Alle aktualisieren (N)' neben der Update-Suche, nur sichtbar wenn etwas aussteht, gesperrt waehrend ein Wartungs-Job laeuft, mit Bestaetigungs-Dialog der die Kette benennt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+163
-158
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-CDoBbBNY.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BrJPl95N.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CMtwLeqK.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { X, RefreshCw, Cpu, Server, Shield, Power, Camera, Bot, Box, Shuffle } from "lucide-react"
|
||||
import { X, RefreshCw, Cpu, Server, Shield, Power, Camera, Bot, Box, Shuffle, DownloadCloud } from "lucide-react"
|
||||
import { api, type Job, type UpdatesResp, type ServicesResp, type UpdateDetails } from "@/lib/api"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CustomDialog } from "./CustomDialog"
|
||||
@@ -245,6 +245,33 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
|
||||
else if (kind === "hermes") doHermesUpdate()
|
||||
}
|
||||
|
||||
// „Alle aktualisieren": kettet alle AUSSTEHENDEN Updates in EINEM Wartungs-Job
|
||||
// (Engine → Router → Hermes → OS; das Backend nutzt exakt die Einzel-Update-Befehle
|
||||
// inkl. deren Backup/Postcheck/Rollback — bei einem Fehler stoppt die Kette).
|
||||
function triggerUpdateAll() {
|
||||
const teile = [
|
||||
updates?.engine ? "Engine" : null,
|
||||
updates?.swap ? "Router" : null,
|
||||
updates?.components?.some((c) => c.update === true) ? "Hermes-Agent" : null,
|
||||
updates?.os ? `OS (${updates.os} Pakete)` : null,
|
||||
].filter(Boolean)
|
||||
showConfirm(
|
||||
"Alle Updates einspielen?",
|
||||
`Nacheinander in einem Job: ${teile.join(" → ")}. Jeder Schritt sichert und prüft sich selbst; schlägt einer fehl, stoppt der Rest. (OS braucht das Box-Passwort aus den Einstellungen — fehlt es, wird OS übersprungen.)`,
|
||||
async () => {
|
||||
try {
|
||||
const res = await api<any>("/api/maintenance/update-all", { method: "POST" })
|
||||
if (handledBusy(res) || handledSudo(res)) return
|
||||
if (res?.status === "nothing") { showAlert("Nichts zu tun", "Es stehen keine Updates aus."); return }
|
||||
loadJobs()
|
||||
setActiveTab("maintenance")
|
||||
} catch (e: any) {
|
||||
showAlert("Fehler", `„Alle aktualisieren" konnte nicht starten: ${e.message}`)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async function triggerCheckUpdates() {
|
||||
setCheckingUpdates(true)
|
||||
try {
|
||||
@@ -337,6 +364,10 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
|
||||
|
||||
// Aktiver Wartungs-Job (System-Update) → UI-Aktionen sperren, Dashboard bleibt sichtbar.
|
||||
const maintenanceJob = jobs.find(j => (j.state === "running" || j.state === "queued") && j.group === "maintenance")
|
||||
// Wie viele Update-ARTEN ausstehen (für den „Alle aktualisieren"-Button).
|
||||
const pendingAll =
|
||||
(updates ? (updates.os > 0 ? 1 : 0) + (updates.engine ? 1 : 0) + (updates.swap ? 1 : 0) : 0) +
|
||||
(updates?.components?.filter((c) => c.update === true).length ?? 0)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -415,10 +446,18 @@ export function SystemDrawer({ open, onClose, defaultTab = "maintenance" }: Syst
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">Updates</h3>
|
||||
<button onClick={triggerCheckUpdates} disabled={checkingUpdates || !!maintenanceJob}
|
||||
className="flex items-center gap-1 text-[10px] font-semibold text-primary hover:text-primary/80 transition-colors disabled:opacity-50">
|
||||
<RefreshCw className={cn("h-3 w-3", checkingUpdates && "animate-spin")} /> Nach Updates suchen
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
{pendingAll > 0 && (
|
||||
<button onClick={triggerUpdateAll} disabled={!!maintenanceJob}
|
||||
className="flex items-center gap-1 rounded-md border border-primary/40 bg-primary/10 px-2 py-1 text-[10px] font-bold text-primary transition-colors hover:bg-primary/20 disabled:opacity-50">
|
||||
<DownloadCloud className="h-3 w-3" /> Alle aktualisieren ({pendingAll})
|
||||
</button>
|
||||
)}
|
||||
<button onClick={triggerCheckUpdates} disabled={checkingUpdates || !!maintenanceJob}
|
||||
className="flex items-center gap-1 text-[10px] font-semibold text-primary hover:text-primary/80 transition-colors disabled:opacity-50">
|
||||
<RefreshCw className={cn("h-3 w-3", checkingUpdates && "animate-spin")} /> Nach Updates suchen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{updates?.last_check && (
|
||||
<div className="text-[9px] text-muted-foreground -mt-1">Zuletzt gesucht: {formatLastCheck(updates.last_check)}</div>
|
||||
|
||||
Reference in New Issue
Block a user