Files
Sovran_SystemsOS/app/sovran_systemsos_web/static/js/rebuild.js
T
naturallaw777 d164d423ad Rework Update System dialog to the approved design; match rebuild dialog
The update dialog kept the old bare title + spinner layout from the
previous theme. Rework it (and the rebuild dialog, for consistency) to
the approved The Hub dialog anatomy:

- Header: green chip icon, title, version chip, status pill
  (Checking… / Up to date / Updating… / Restart required / Update
  failed / Status unknown) and a header close button. A spinner appears
  in the header while an update is starting or running.
- System Details card: current version, release channel, and last
  checked (relative time, refreshed on every check).
- The log renders as a console with green "ok" and dim hint lines; the
  up-to-date result shows as a single green console line exactly once
  (the redundant status line is hidden in that state).
- Footer: Close plus a "Check again" primary action that re-runs the
  update check. Close and Check again are disabled while a check or
  update is in flight; all existing recovery actions (Save Error
  Report, Retry Status, Retry Update, Restart Entire System) keep their
  exact semantics.
- Opening the dialog now shows a "Checking…" state immediately while
  the existing reattach-then-check logic runs; reattaching to an
  in-progress update (page reload, RDP reconnect) is unchanged.

Sidebar Update System status tints now use the theme palette (red /
amber / blue / green), and the periodic background check refreshes the
dialog's "last checked" value.

Rebuild dialog gets the same header (icon, version chip, Applying… /
Done / Restart required / Failed pill, header close disabled while a
rebuild runs); its log stays hidden as before.

No API or state-machine changes: same endpoints, same polling, same
reattach and recovery behavior.
2026-09-08 16:40:06 -05:00

144 lines
5.3 KiB
JavaScript

"use strict";
// ── Rebuild modal ─────────────────────────────────────────────────
// Status line + header pill for the rebuild dialog (same presentation
// contract as the update dialog's _setUpdateStatus).
function _setRebuildStatus(text) {
if ($rebuildStatus) $rebuildStatus.textContent = text;
if ($rebuildPill) {
var cls = "upd-pill";
var html;
if (text.charAt(0) === "✓") {
if (text.indexOf("restart required") !== -1) {
cls += " st-needs-attention"; html = '<span class="status-dot needs-attention pulse"></span>Restart required';
} else {
cls += " st-active"; html = '<span class="status-dot active"></span>Done';
}
} else if (text.charAt(0) === "✗") {
cls += " st-failed"; html = '<span class="status-dot failed"></span>Failed';
} else {
cls += " st-loading"; html = '<span class="status-dot loading pulse"></span>Applying…';
}
$rebuildPill.className = cls;
$rebuildPill.innerHTML = html;
}
if ($rebuildStatus) {
var msg = "update-status-msg";
if (text.charAt(0) === "✓") $rebuildStatus.className = (text.indexOf("restart required") !== -1) ? msg + " st-warn" : msg + " st-ok";
else if (text.charAt(0) === "✗") $rebuildStatus.className = msg + " st-err";
else $rebuildStatus.className = msg;
}
}
function openRebuildModal() {
if (!$rebuildModal) return;
_rebuildLog = "";
_rebuildLogOffset = 0;
_rebuildServerDown = false;
_rebuildFinished = false;
_rebuildPollInFlight = false;
_rebuildPollFailures = 0;
if ($rebuildLog) { $rebuildLog.textContent = ""; $rebuildLog.style.display = "none"; }
var action = _rebuildIsEnabling ? "Enabling" : "Disabling";
var label = _rebuildFeatureName || "feature";
_setRebuildStatus(action + " " + label + "…");
if ($rebuildSpinner) $rebuildSpinner.classList.add("spinning");
if ($rebuildReboot) $rebuildReboot.style.display = "none";
if ($rebuildSave) $rebuildSave.style.display = "none";
if ($rebuildClose) $rebuildClose.disabled = true;
if ($rebuildCloseHdr) $rebuildCloseHdr.disabled = true;
$rebuildModal.classList.add("open");
// Delay first poll slightly to let the rebuild service start and clear stale log
setTimeout(startRebuildPoll, 1500);
}
function closeRebuildModal() {
if ($rebuildModal) $rebuildModal.classList.remove("open");
stopRebuildPoll();
}
function appendRebuildLog(text) {
if (!text) return;
_rebuildLog += text;
// Log is collected silently for error reports — not displayed to user
}
function startRebuildPoll() {
if (_rebuildPollTimer) clearInterval(_rebuildPollTimer);
pollRebuildStatus();
_rebuildPollTimer = setInterval(pollRebuildStatus, UPDATE_POLL_INTERVAL);
}
function stopRebuildPoll() {
if (_rebuildPollTimer) { clearInterval(_rebuildPollTimer); _rebuildPollTimer = null; }
}
async function pollRebuildStatus() {
if (_rebuildFinished || _rebuildPollInFlight) return;
_rebuildPollInFlight = true;
try {
var data = await apiFetchWithTimeout(
"/api/rebuild/status?offset=" + _rebuildLogOffset,
{ cache: "no-store" },
STATUS_POLL_FETCH_TIMEOUT
);
_rebuildPollFailures = 0;
if (_rebuildServerDown) { _rebuildServerDown = false; }
if (data.log) appendRebuildLog(data.log);
_rebuildLogOffset = data.offset;
if (data.running) return;
_rebuildFinished = true;
stopRebuildPoll();
if (data.result === "reboot_required") {
onRebuildDone("reboot_required");
} else {
onRebuildDone(data.result === "success");
}
} catch (err) {
_rebuildPollFailures += 1;
// The Hub restarts itself during activation, which briefly drops this poll.
// If polling stays broken long past a normal restart, reload to
// re-authenticate and show the resulting feature state.
if (_rebuildPollFailures >= STATUS_POLL_MAX_FAILURES) {
_rebuildFinished = true;
stopRebuildPoll();
window.location.reload();
return;
}
if (!_rebuildServerDown) { _rebuildServerDown = true; _setRebuildStatus("Applying changes…"); }
} finally {
_rebuildPollInFlight = false;
}
}
function onRebuildDone(result) {
if ($rebuildSpinner) $rebuildSpinner.classList.remove("spinning");
if ($rebuildClose) $rebuildClose.disabled = false;
if ($rebuildCloseHdr) $rebuildCloseHdr.disabled = false;
if (result === true) {
_setRebuildStatus("✓ Done");
// Auto-reload the page after a short delay so tiles and toggles reflect the new state
setTimeout(function() { window.location.reload(); }, 1200);
} else if (result === "reboot_required") {
_setRebuildStatus("✓ Done — restart required");
if ($rebuildReboot) $rebuildReboot.style.display = "inline-flex";
} else {
_setRebuildStatus("✗ Something went wrong");
if ($rebuildSave) $rebuildSave.style.display = "inline-flex";
if ($rebuildReboot) $rebuildReboot.style.display = "inline-flex";
}
}
function saveRebuildErrorReport() {
var blob = new Blob([_rebuildLog], { type: "text/plain" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "sovran-rebuild-error-" + new Date().toISOString().split(".")[0].replace(/:/g, "-") + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}