Updater/rebuild self-heal: - Add a shared run_step wrapper used by both the update and rebuild scripts. On the first failure matching a transient fetch/cache signature (truncated tarball, corrupt NAR, hash mismatch, network timeout, interrupted download), clear Nix's fetch caches and repair the store, then retry once. Real config errors do not match and still fail loudly. - The kernel-change boot fallback in the rebuild path is also wrapped. - Fixes the reported 'cannot read file from tarball: Truncated tar archive detected' failure, which a plain re-run cannot clear because Nix reuses the corrupt cached archive. Failed-update recovery / reporting: - check_for_updates() now compares the running Hub version against the branch VERSION, so a failed 'nix flake update' (lock advanced but no generation staged) can no longer masquerade as 'up to date' and block retries. - /api/updates/check surfaces a persistent 'failed' state; /api/updates/run never blocks a retry after a failure. - Dashboard shows a red 'Update failed - click to retry' tile; the modal offers a Retry Update button and stops offering a reboot on failure.
125 lines
6.2 KiB
JavaScript
125 lines
6.2 KiB
JavaScript
"use strict";
|
|
|
|
// ── State ─────────────────────────────────────────────────────────
|
|
|
|
let _servicesCache = [];
|
|
let _categoryLabels = {};
|
|
let _updateLog = "";
|
|
let _updatePollTimer = null;
|
|
let _updatePollInFlight = false;
|
|
let _updateLogOffset = 0;
|
|
let _updateVisibleLogChars = 0;
|
|
let _serverWasDown = false;
|
|
let _updateFinished = false;
|
|
let _updateStatusUnavailable = false;
|
|
let _updatePollFailures = 0; // consecutive failed update-status polls
|
|
let _supportTimerInt = null;
|
|
let _supportEnabledAt = null;
|
|
let _supportStatus = null; // last fetched /api/support/status payload
|
|
let _walletUnlockTimerInt = null;
|
|
let _cachedExternalIp = null;
|
|
|
|
// Current role (set during init from /api/config)
|
|
let _currentRole = "server_plus_desktop";
|
|
|
|
// Feature Manager state
|
|
let _featuresData = null;
|
|
let _rebuildLog = "";
|
|
let _rebuildLogOffset = 0;
|
|
let _rebuildPollTimer = null;
|
|
let _rebuildPollInFlight = false;
|
|
let _rebuildFinished = false;
|
|
let _rebuildServerDown = false;
|
|
let _rebuildPollFailures = 0; // consecutive failed rebuild-status polls
|
|
let _pendingToggle = null; // {feature, extra} waiting for domain/confirm
|
|
let _rebuildFeatureName = "";
|
|
let _rebuildIsEnabling = true;
|
|
|
|
// ── DOM refs ──────────────────────────────────────────────────────
|
|
|
|
const $tilesArea = document.getElementById("tiles-area");
|
|
const $sidebarSupport = document.getElementById("sidebar-support");
|
|
const $sidebarFeatures = document.getElementById("sidebar-features");
|
|
// No longer needed — Update System moved to sidebar
|
|
// const $updateBtn = document.getElementById("btn-update");
|
|
// const $updateBadge = document.getElementById("update-badge");
|
|
const $internalIp = document.getElementById("ip-internal");
|
|
const $externalIp = document.getElementById("ip-external");
|
|
|
|
const $modal = document.getElementById("update-modal");
|
|
const $modalSpinner = document.getElementById("modal-spinner");
|
|
const $modalStatus = document.getElementById("modal-status");
|
|
const $modalLog = document.getElementById("modal-log");
|
|
const $btnReboot = document.getElementById("btn-reboot");
|
|
const $btnSave = document.getElementById("btn-save-report");
|
|
const $btnRetryUpdate = document.getElementById("btn-retry-update-status");
|
|
const $btnRetryRun = document.getElementById("btn-retry-update");
|
|
const $btnCloseModal = document.getElementById("btn-close-modal");
|
|
|
|
const $rebootOverlay = document.getElementById("reboot-overlay");
|
|
const $rebootMainCard = document.getElementById("reboot-main-card");
|
|
const $rebootErrorCard = document.getElementById("reboot-error-card");
|
|
const $rebootSubmessage = document.getElementById("reboot-submessage");
|
|
|
|
const $credsModal = document.getElementById("creds-modal");
|
|
const $credsTitle = document.getElementById("creds-modal-title");
|
|
const $credsBody = document.getElementById("creds-body");
|
|
const $credsCloseBtn = document.getElementById("creds-close-btn");
|
|
|
|
const $supportModal = document.getElementById("support-modal");
|
|
const $supportBody = document.getElementById("support-body");
|
|
const $supportCloseBtn = document.getElementById("support-close-btn");
|
|
|
|
const $logoutBtn = document.getElementById("btn-logout");
|
|
|
|
// Feature Manager — rebuild modal
|
|
const $rebuildModal = document.getElementById("rebuild-modal");
|
|
const $rebuildSpinner = document.getElementById("rebuild-spinner");
|
|
const $rebuildStatus = document.getElementById("rebuild-status");
|
|
const $rebuildLog = document.getElementById("rebuild-log");
|
|
const $rebuildReboot = document.getElementById("rebuild-reboot-btn");
|
|
const $rebuildSave = document.getElementById("rebuild-save-report");
|
|
const $rebuildClose = document.getElementById("rebuild-close-btn");
|
|
|
|
// Feature Manager — domain setup modal
|
|
const $domainSetupModal = document.getElementById("domain-setup-modal");
|
|
const $domainSetupTitle = document.getElementById("domain-setup-title");
|
|
const $domainSetupBody = document.getElementById("domain-setup-body");
|
|
const $domainSetupClose = document.getElementById("domain-setup-close-btn");
|
|
|
|
// Feature Manager — SSL email modal
|
|
const $sslEmailModal = document.getElementById("ssl-email-modal");
|
|
const $sslEmailInput = document.getElementById("ssl-email-input");
|
|
const $sslEmailSave = document.getElementById("ssl-email-save-btn");
|
|
const $sslEmailCancel = document.getElementById("ssl-email-cancel-btn");
|
|
const $sslEmailClose = document.getElementById("ssl-email-close-btn");
|
|
|
|
// Feature Manager — confirm modal
|
|
const $featureConfirmModal = document.getElementById("feature-confirm-modal");
|
|
const $featureConfirmMsg = document.getElementById("feature-confirm-message");
|
|
const $featureConfirmOk = document.getElementById("feature-confirm-ok-btn");
|
|
const $featureConfirmCancel = document.getElementById("feature-confirm-cancel-btn");
|
|
const $featureConfirmClose = document.getElementById("feature-confirm-close-btn");
|
|
|
|
// Port Requirements modal
|
|
const $portReqModal = document.getElementById("port-requirements-modal");
|
|
const $portReqBody = document.getElementById("port-req-body");
|
|
const $portReqClose = document.getElementById("port-req-close-btn");
|
|
|
|
// Upgrade modal (Node → Server+Desktop)
|
|
const $upgradeModal = document.getElementById("upgrade-modal");
|
|
const $upgradeConfirmBtn = document.getElementById("upgrade-confirm-btn");
|
|
const $upgradeCancelBtn = document.getElementById("upgrade-cancel-btn");
|
|
const $upgradeCloseBtn = document.getElementById("upgrade-close-btn");
|
|
|
|
// Restart confirm dialog
|
|
const $restartConfirmModal = document.getElementById("restart-confirm-modal");
|
|
const $restartConfirmOk = document.getElementById("restart-confirm-ok-btn");
|
|
const $restartConfirmCancel = document.getElementById("restart-confirm-cancel-btn");
|
|
const $restartConflictBox = document.getElementById("restart-conflict-box");
|
|
|
|
// Header reboot button
|
|
const $headerRebootBtn = document.getElementById("btn-header-reboot");
|
|
|
|
// System status banner
|
|
// (removed — health is now shown per-tile via the composite health field)
|