diff --git a/app/(app)/settings/page.tsx b/app/(app)/settings/page.tsx index c26ae5e..1ace520 100644 --- a/app/(app)/settings/page.tsx +++ b/app/(app)/settings/page.tsx @@ -95,16 +95,38 @@ export default function SettingsPage() { setChecking(false); } + async function waitForServerBack() { + for (let i = 0; i < 60; i++) { + try { + const r = await fetch("/api/v1/health", { cache: "no-store" }); + if (r.ok) return true; + } catch {} + await new Promise((res) => setTimeout(res, 1000)); + } + return false; + } + async function applyNow() { if (!confirm(`Update auf ${status?.latest} jetzt installieren?\n\nDer Server wird neu gestartet (kurze Downtime der Admin-UI). Redirects bleiben über Caddy aktiv.`)) return; setApplying(true); - setMsg(""); + setMsg("Update läuft — bitte warten..."); try { const r = await fetch("/api/update/apply", { method: "POST" }); - const d = await r.json(); - setMsg(d.ok ? `Update erfolgreich: ${d.from} → ${d.to}` : `Fehler: ${d.error}`); - load(); - } finally { + let d: { ok?: boolean; from?: string; to?: string; error?: string } = {}; + try { d = await r.json(); } catch {} + if (!r.ok || d.error) { + setMsg(`Fehler: ${d.error || r.statusText}`); + setApplying(false); + return; + } + setMsg(`Update gezogen (${d.from} → ${d.to}). Server startet neu...`); + // Wait for restart, then hard-reload to drop all client cache + await new Promise((res) => setTimeout(res, 3000)); + const back = await waitForServerBack(); + setMsg(back ? "Server zurück. Lade Seite neu..." : "Restart dauert ungewöhnlich lang. Trotzdem neu laden."); + window.location.reload(); + } catch (e) { + setMsg(`Fehler: ${e instanceof Error ? e.message : String(e)}`); setApplying(false); } } @@ -130,18 +152,27 @@ export default function SettingsPage() {
- - {status.update_available && ( + {status.update_available && !applying && ( )} + {!status.update_available && !applying && ( + ✓ Aktuelle Version + )} {status.release_url && Release-Notes →}
+ {applying && ( +
+ + {msg || "Update läuft..."} +
+ )}
- {msg &&

{msg}

} + {msg && !applying &&

{msg}

} {status.last_check &&

Letzte Prüfung: {new Date(status.last_check).toLocaleString("de-DE")}

}
diff --git a/components/UpdateBanner.tsx b/components/UpdateBanner.tsx index 491ed57..17fe1cd 100644 --- a/components/UpdateBanner.tsx +++ b/components/UpdateBanner.tsx @@ -15,10 +15,16 @@ export function UpdateBanner() { const [dismissed, setDismissed] = useState(false); useEffect(() => { - fetch("/api/update/check") - .then((r) => r.json()) - .then(setInfo) - .catch(() => {}); + let cancelled = false; + function load() { + fetch("/api/update/check", { cache: "no-store" }) + .then((r) => r.json()) + .then((d) => { if (!cancelled) setInfo(d); }) + .catch(() => {}); + } + load(); + const id = setInterval(load, 60_000); + return () => { cancelled = true; clearInterval(id); }; }, []); if (!info?.update_available || dismissed) return null; diff --git a/lib/updater.ts b/lib/updater.ts index aded5d6..07e152c 100644 --- a/lib/updater.ts +++ b/lib/updater.ts @@ -83,10 +83,13 @@ export async function checkForUpdate(): Promise { } export function getUpdateStatus(): UpdateStatus { + const current = pkg.version; + const latest = getSetting("latest_version") || null; + const update_available = !!latest && cmpVersions(current, latest) < 0; return { - current: pkg.version, - latest: getSetting("latest_version") || null, - update_available: getSetting("update_available") === "true", + current, + latest, + update_available, release_url: getSetting("update_release_url") || undefined, last_check: Number(getSetting("update_last_check") || 0) || undefined, auto_update: getSetting("update_auto") === "true", diff --git a/package.json b/package.json index 040b100..0ce8822 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "corex-nexredirect", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "scripts": { "dev": "tsx watch server.ts", diff --git a/scripts/update.sh b/scripts/update.sh index 3c8065e..7cec260 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -50,5 +50,11 @@ fi ln -sf "$INSTALL_DIR/bin/nexredirect" /usr/local/bin/nexredirect chmod +x "$INSTALL_DIR/bin/nexredirect" 2>/dev/null || true -systemctl restart corex-nexredirect -echo "Update auf $(sudo -u "$SERVICE_USER" -H bash -c "cd '$INSTALL_DIR' && git describe --tags --always") abgeschlossen" +VERSION=$(sudo -u "$SERVICE_USER" -H bash -c "cd '$INSTALL_DIR' && git describe --tags --always") +echo "Update auf $VERSION abgeschlossen — Restart wird in 2s ausgelöst" + +# Detach restart so this process can return cleanly to the API caller +# (the API can then respond before its own service gets killed). +( sleep 2 && systemctl restart corex-nexredirect ) >/dev/null 2>&1 & +disown +exit 0