From 2102481ed0133b57228f7fcea7c9cbcd2186d398 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 4 Jun 2026 14:31:53 +0200 Subject: [PATCH] changed poll and some UI bugs. --- agent/package-lock.json | 3 +++ agent/package.json | 3 +++ deploy/install-agent.sh | 13 +++++++++++ docs/deployment.md | 2 ++ frontend/src/App.jsx | 27 ++++++++++++++------- frontend/src/components/Nodes.jsx | 39 ++++++++++++++++++++++++++++--- management/src/routes/proxy.js | 4 ++-- 7 files changed, 77 insertions(+), 14 deletions(-) diff --git a/agent/package-lock.json b/agent/package-lock.json index ab32f3f..ee00f49 100644 --- a/agent/package-lock.json +++ b/agent/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "incus-backup-ui-agent", "version": "0.1.0", + "engines": { + "node": ">=22.5.0" + }, "dependencies": { "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/agent/package.json b/agent/package.json index ef75dad..f527c6f 100644 --- a/agent/package.json +++ b/agent/package.json @@ -7,6 +7,9 @@ "dev": "node --watch src/index.js", "start": "node src/index.js" }, + "engines": { + "node": ">=22.5.0" + }, "dependencies": { "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/deploy/install-agent.sh b/deploy/install-agent.sh index f1f6c89..fdf2275 100755 --- a/deploy/install-agent.sh +++ b/deploy/install-agent.sh @@ -27,6 +27,18 @@ need_cmd() { command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1" } +node_major_version() { + node -p "Number(process.versions.node.split('.')[0])" +} + +check_node_version() { + need_cmd node + major=$(node_major_version) + if [ "$major" -lt 22 ]; then + fail "Node.js 22.5 or newer is required. Found: $(node -v). Install a current Node.js release and rerun this installer." + fi +} + as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" @@ -177,6 +189,7 @@ configure_env() { install_dependencies() { need_cmd npm + check_node_version log "Installing agent dependencies..." if [ -f "$AGENT_DIR/package-lock.json" ]; then npm --prefix "$AGENT_DIR" ci --omit=dev diff --git a/docs/deployment.md b/docs/deployment.md index a91b024..7d60b43 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -4,6 +4,8 @@ Run this on every Incus host. The installer clones or updates the repository, creates the agent state/config directories, prepares `agent/.env`, installs npm dependencies, installs the systemd unit, and starts the service: +The agent requires Node.js 22.5 or newer because it uses Node's built-in SQLite module. + ```bash curl -fsSL https://forgejo.digital-droplets.de/philschlo/incus-backup-ui/raw/branch/main/deploy/install-agent.sh | sudo sh ``` diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3fa0bb5..94cb881 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -36,7 +36,6 @@ export default function App() { const [loading, setLoading] = useState(true); async function refresh() { - setError(''); try { const [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult] = await Promise.allSettled([ api.get('/health'), @@ -47,20 +46,20 @@ export default function App() { ]); if (healthResult.status === 'fulfilled') { - setHealth(healthResult.value.data); + setStableState(setHealth, healthResult.value.data); } else { - setHealth(healthResult.reason.response?.data || { ok: false, checks: {} }); + setStableState(setHealth, healthResult.reason.response?.data || { ok: false, checks: {} }); } - if (vmsResult.status === 'fulfilled') setVms(vmsResult.value.data); - if (jobsResult.status === 'fulfilled') setJobs(jobsResult.value.data); - if (schedulesResult.status === 'fulfilled') setSchedules(schedulesResult.value.data); - if (nodesResult.status === 'fulfilled') setNodes(nodesResult.value.data); + if (vmsResult.status === 'fulfilled') setStableState(setVms, vmsResult.value.data); + if (jobsResult.status === 'fulfilled') setStableState(setJobs, jobsResult.value.data); + if (schedulesResult.status === 'fulfilled') setStableState(setSchedules, schedulesResult.value.data); + if (nodesResult.status === 'fulfilled') setStableState(setNodes, nodesResult.value.data); const firstFailure = [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult].find((result) => result.status === 'rejected'); - if (firstFailure) setError(errorMessage(firstFailure.reason)); + setStableState(setError, firstFailure ? errorMessage(firstFailure.reason) : ''); } catch (requestError) { - setError(errorMessage(requestError)); + setStableState(setError, errorMessage(requestError)); } finally { setLoading(false); } @@ -227,6 +226,16 @@ export default function App() { } } +function setStableState(setter, nextValue) { + setter((currentValue) => ( + stableStringify(currentValue) === stableStringify(nextValue) ? currentValue : nextValue + )); +} + +function stableStringify(value) { + return JSON.stringify(value); +} + function NavButton({ active, icon: Icon, label, onClick }) { return (