v0.1.3 — update flow: detached restart, version-aware status, auto-reload UI, banner polling
This commit is contained in:
parent
3549c7cc9c
commit
c710d874b1
5 changed files with 65 additions and 19 deletions
|
|
@ -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() {
|
|||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button onClick={check} variant="outline" size="sm" disabled={checking}>
|
||||
<Button onClick={check} variant="outline" size="sm" disabled={checking || applying}>
|
||||
{checking ? <Loader2 className="mr-2 h-3 w-3 animate-spin" /> : <RefreshCcw className="mr-2 h-3 w-3" />}
|
||||
Jetzt prüfen
|
||||
</Button>
|
||||
{status.update_available && (
|
||||
{status.update_available && !applying && (
|
||||
<Button onClick={applyNow} size="sm" disabled={applying}>
|
||||
{applying ? <Loader2 className="mr-2 h-3 w-3 animate-spin" /> : <ArrowUpCircle className="mr-2 h-3 w-3" />}
|
||||
<ArrowUpCircle className="mr-2 h-3 w-3" />
|
||||
Update {status.latest} installieren
|
||||
</Button>
|
||||
)}
|
||||
{!status.update_available && !applying && (
|
||||
<span className="text-xs text-green-400">✓ Aktuelle Version</span>
|
||||
)}
|
||||
{status.release_url && <a href={status.release_url} target="_blank" rel="noreferrer" className="text-xs text-cyan-400 hover:underline">Release-Notes →</a>}
|
||||
</div>
|
||||
{applying && (
|
||||
<div className="flex items-center gap-2 rounded-md border border-cyan-500/30 bg-cyan-500/10 px-3 py-2 text-xs text-cyan-200">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<span>{msg || "Update läuft..."}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2 pt-2">
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={settings.update_auto === "true"} onChange={(e) => save({ update_auto: e.target.checked ? "true" : "false" })} disabled={saving} />
|
||||
|
|
@ -152,7 +183,7 @@ export default function SettingsPage() {
|
|||
Pre-Releases einbeziehen
|
||||
</label>
|
||||
</div>
|
||||
{msg && <p className="text-xs text-muted-foreground">{msg}</p>}
|
||||
{msg && !applying && <p className="text-xs text-muted-foreground">{msg}</p>}
|
||||
{status.last_check && <p className="text-xs text-muted-foreground">Letzte Prüfung: {new Date(status.last_check).toLocaleString("de-DE")}</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -83,10 +83,13 @@ export async function checkForUpdate(): Promise<UpdateStatus> {
|
|||
}
|
||||
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "corex-nexredirect",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "tsx watch server.ts",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue