Add update progress polling overlay and strengthen state flags
This commit is contained in:
21
pikit-api.py
21
pikit-api.py
@@ -5,6 +5,7 @@ import re
|
||||
import urllib.request
|
||||
import hashlib
|
||||
import fcntl
|
||||
from functools import partial
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT = 4000
|
||||
@@ -598,6 +599,7 @@ def apply_update_stub():
|
||||
|
||||
manifest = None
|
||||
state["in_progress"] = True
|
||||
state["status"] = "in_progress"
|
||||
state["progress"] = "Starting update…"
|
||||
save_update_state(state)
|
||||
|
||||
@@ -642,7 +644,8 @@ def apply_update_stub():
|
||||
ensure_dir(backup_dir)
|
||||
# Backup web and api
|
||||
if WEB_ROOT.exists():
|
||||
shutil.copytree(WEB_ROOT, backup_dir / "pikit-web")
|
||||
ensure_dir(backup_dir / "pikit-web")
|
||||
shutil.copytree(WEB_ROOT, backup_dir / "pikit-web", dirs_exist_ok=True)
|
||||
if API_PATH.exists():
|
||||
shutil.copy2(API_PATH, backup_dir / "pikit-api.py")
|
||||
|
||||
@@ -672,7 +675,7 @@ def apply_update_stub():
|
||||
state["progress"] = None
|
||||
save_update_state(state)
|
||||
except urllib.error.HTTPError as e:
|
||||
state["status"] = "up_to_date"
|
||||
state["status"] = "error"
|
||||
state["message"] = f"No release available ({e.code})"
|
||||
except Exception as e:
|
||||
state["status"] = "error"
|
||||
@@ -714,17 +717,24 @@ def rollback_update_stub():
|
||||
state["message"] = "Another update is running"
|
||||
save_update_state(state)
|
||||
return state
|
||||
state["in_progress"] = True
|
||||
state["status"] = "in_progress"
|
||||
state["progress"] = "Rolling back…"
|
||||
save_update_state(state)
|
||||
backups = sorted(BACKUP_ROOT.glob("*"), reverse=True)
|
||||
if not backups:
|
||||
state["status"] = "error"
|
||||
state["message"] = "No backup available to rollback."
|
||||
state["in_progress"] = False
|
||||
state["progress"] = None
|
||||
save_update_state(state)
|
||||
release_lock(lock)
|
||||
return state
|
||||
target = backups[0]
|
||||
try:
|
||||
if (target / "pikit-web").exists():
|
||||
shutil.rmtree(WEB_ROOT, ignore_errors=True)
|
||||
shutil.copytree(target / "pikit-web", WEB_ROOT)
|
||||
shutil.copytree(target / "pikit-web", WEB_ROOT, dirs_exist_ok=True)
|
||||
if (target / "pikit-api.py").exists():
|
||||
shutil.copy2(target / "pikit-api.py", API_PATH)
|
||||
os.chmod(API_PATH, 0o755)
|
||||
@@ -735,9 +745,10 @@ def rollback_update_stub():
|
||||
except Exception as e:
|
||||
state["status"] = "error"
|
||||
state["message"] = f"Rollback failed: {e}"
|
||||
state["in_progress"] = False
|
||||
state["progress"] = None
|
||||
save_update_state(state)
|
||||
if lock:
|
||||
release_lock(lock)
|
||||
release_lock(lock)
|
||||
return state
|
||||
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ const busyOverlay = document.getElementById("busyOverlay");
|
||||
const busyTitle = document.getElementById("busyTitle");
|
||||
const busyText = document.getElementById("busyText");
|
||||
const toastContainer = document.getElementById("toastContainer");
|
||||
const readyOverlay = document.getElementById("readyOverlay");
|
||||
|
||||
const TOAST_POS_KEY = "pikit-toast-pos";
|
||||
const TOAST_ANIM_KEY = "pikit-toast-anim";
|
||||
@@ -398,6 +399,7 @@ async function loadReleaseStatus() {
|
||||
auto_check = false,
|
||||
progress = null,
|
||||
} = data || {};
|
||||
window.__lastReleaseState = data;
|
||||
setReleaseChip(data);
|
||||
if (releaseCurrent) releaseCurrent.textContent = current_version;
|
||||
if (releaseLatest) releaseLatest.textContent = latest_version;
|
||||
@@ -485,9 +487,10 @@ function wireReleaseControls() {
|
||||
|
||||
releaseApplyBtn?.addEventListener("click", async () => {
|
||||
try {
|
||||
showBusy("Updating Pi-Kit…", "Applying release. This can take up to a minute.");
|
||||
if (releaseProgress) releaseProgress.textContent = "Downloading and installing…";
|
||||
await applyRelease();
|
||||
await loadReleaseStatus();
|
||||
pollReleaseStatus();
|
||||
showToast("Update started", "success");
|
||||
} catch (e) {
|
||||
showToast(e.error || "Update failed", "error");
|
||||
@@ -498,10 +501,11 @@ function wireReleaseControls() {
|
||||
|
||||
releaseRollbackBtn?.addEventListener("click", async () => {
|
||||
try {
|
||||
showBusy("Rolling back…", "Restoring previous backup.");
|
||||
if (releaseProgress) releaseProgress.textContent = "Rolling back…";
|
||||
await rollbackRelease();
|
||||
await loadReleaseStatus();
|
||||
showToast("Rollback complete", "success");
|
||||
pollReleaseStatus();
|
||||
showToast("Rollback started", "success");
|
||||
} catch (e) {
|
||||
showToast(e.error || "Rollback failed", "error");
|
||||
} finally {
|
||||
@@ -520,6 +524,27 @@ function wireReleaseControls() {
|
||||
});
|
||||
}
|
||||
|
||||
function pollReleaseStatus() {
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30; // ~1 min at 2s
|
||||
const tick = async () => {
|
||||
attempts += 1;
|
||||
await loadReleaseStatus();
|
||||
const state = window.__lastReleaseState || {};
|
||||
if (state.status === "in_progress" && attempts < maxAttempts) {
|
||||
setTimeout(tick, 2000);
|
||||
} else {
|
||||
hideBusy();
|
||||
if (state.status === "up_to_date") {
|
||||
showToast("Update complete", "success");
|
||||
} else if (state.status === "error") {
|
||||
showToast(state.message || "Update failed", "error");
|
||||
}
|
||||
}
|
||||
};
|
||||
tick();
|
||||
}
|
||||
|
||||
function showBusy(title = "Working…", text = "This may take a few seconds.") {
|
||||
if (!busyOverlay) return;
|
||||
busyTitle.textContent = title;
|
||||
|
||||
@@ -1374,6 +1374,11 @@ select:focus-visible,
|
||||
#addServiceModal .controls {
|
||||
padding: 0 2px 4px;
|
||||
}
|
||||
|
||||
/* Busy overlay already defined; ensure modal width for release modal */
|
||||
#releaseModal .modal-card.wide {
|
||||
max-width: 760px;
|
||||
}
|
||||
.modal:not(.hidden) .modal-card {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user