Simplify Systems Operational, reword Domain Status, blue restart buttons

- Restart and retry actions (topbar Reboot, Restart Entire System in the
  update and rebuild dialogs and the restart confirmation, Retry Update,
  Try Again) are now blue instead of amber — amber read as an error.
  Status pills keep their semantic colors (amber = restart required).
- The service-modal domain section is no longer a checklist: it is titled
  "Domain Status" and shows a single green "Domain is active" line with
  the domain (or the backend's not-configured detail and Configure
  Domain action when there is no domain yet). No "Step 1" wording.
- Systems Operational keeps the complexity hidden: the Router card now
  shows just a verdict — green "Ports 80 and 443 are open" or red
  "Ports 80 and 443 are not open" — derived from the same live backend
  diagnostics. Port-forwarding instructions are gone (onboarding covers
  them); on the Node-only role the card still explains that ports only
  matter once BTCPay Server or Lightning Wallet Connections (LNURL) is
  enabled.
This commit is contained in:
2026-09-08 17:52:04 -05:00
parent ea9d4f21d7
commit e6078b9c89
3 changed files with 50 additions and 64 deletions
+12 -11
View File
@@ -62,22 +62,23 @@ button:disabled {
background: rgba(255, 255, 255, 0.04); background: rgba(255, 255, 255, 0.04);
} }
/* Amberdestructive-ish confirmations (restart, reboot, retry) */ /* Bluerestart / retry actions (amber reads as an error) */
.btn-amber, .btn-amber,
.btn-reboot, .btn-reboot,
.btn-restart-amber, .btn-restart-amber,
.btn-warning { .btn-warning {
background: rgba(233, 182, 74, 0.10); background: rgba(120, 174, 237, 0.10);
border-color: rgba(233, 182, 74, 0.32); border-color: rgba(120, 174, 237, 0.35);
color: #f2c45e; color: #a5cbf2;
} }
.btn-amber:hover:not(:disabled), .btn-amber:hover:not(:disabled),
.btn-reboot:hover:not(:disabled), .btn-reboot:hover:not(:disabled),
.btn-restart-amber:hover:not(:disabled), .btn-restart-amber:hover:not(:disabled),
.btn-warning:hover:not(:disabled) { .btn-warning:hover:not(:disabled) {
background: rgba(233, 182, 74, 0.18); background: rgba(120, 174, 237, 0.18);
border-color: rgba(233, 182, 74, 0.5); border-color: rgba(120, 174, 237, 0.55);
color: #c2dcf8;
} }
.btn-danger { .btn-danger {
@@ -96,9 +97,9 @@ button:disabled {
/* ── Header / topbar buttons ────────────────────────────────────── */ /* ── Header / topbar buttons ────────────────────────────────────── */
.btn-header-reboot { .btn-header-reboot {
background: rgba(233, 182, 74, 0.10); background: rgba(120, 174, 237, 0.10);
border: 1px solid rgba(233, 182, 74, 0.32); border: 1px solid rgba(120, 174, 237, 0.35);
color: #f2c45e; color: #a5cbf2;
font-size: 0.77rem; font-size: 0.77rem;
font-weight: 700; font-weight: 700;
padding: 6px 14px; padding: 6px 14px;
@@ -106,8 +107,8 @@ button:disabled {
} }
.btn-header-reboot:hover:not(:disabled) { .btn-header-reboot:hover:not(:disabled) {
background: rgba(233, 182, 74, 0.18); background: rgba(120, 174, 237, 0.18);
border-color: rgba(233, 182, 74, 0.5); border-color: rgba(120, 174, 237, 0.55);
} }
.btn-logout { .btn-logout {
+24 -43
View File
@@ -208,12 +208,6 @@
/* ── Systems Operational modal ───────────────────────────────── */ /* ── Systems Operational modal ───────────────────────────────── */
function internalIp() {
var el = document.getElementById("ip-internal");
var v = el ? el.textContent.trim() : "";
return (v && v !== "…" && v !== "—") ? v : "";
}
function step(n, title, sub, value) { function step(n, title, sub, value) {
return '<div class="sysstep"><div class="sysnum">' + n + '</div><div class="sysstep-x">' + return '<div class="sysstep"><div class="sysnum">' + n + '</div><div class="sysstep-x">' +
'<div class="sysstep-t">' + title + (sub ? ' <span class="sysstep-sub">· ' + sub + '</span>' : '') + '</div>' + '<div class="sysstep-t">' + title + (sub ? ' <span class="sysstep-sub">· ' + sub + '</span>' : '') + '</div>' +
@@ -250,9 +244,6 @@
else running++; else running++;
}); });
var ip = internalIp();
var fwd = ip ? ('\u2192 ' + escHtml(ip)) : "forward to this computer";
var html = ""; var html = "";
/* System status */ /* System status */
@@ -263,32 +254,23 @@
step(3, "Turned off", "", off ? escHtml(offNames.join(", ")) : "None") + step(3, "Turned off", "", off ? escHtml(offNames.join(", ")) : "None") +
'</div>'; '</div>';
/* Router ports — repo wording (domain-prereqs.js / server.py). /* Router — a simple open / not-open verdict. How to open the ports is
Node-only role: ports matter only once BTCPay Server or Lightning covered during onboarding, so the modal does not repeat instructions.
Wallet Connections (LNURL) is turned on — until then, no router task. */ Node-only role: ports only matter once BTCPay Server or Lightning
Wallet Connections (LNURL) is turned on. */
if (isNodeRole() && !hasEnabledDomainService(services)) { if (isNodeRole() && !hasEnabledDomainService(services)) {
html += '<div class="sysmodal-card">' + html += '<div class="sysmodal-card">' +
'<div class="sysmodal-card-title">' + icon("g-wifi") + 'Router</div>' + '<div class="sysmodal-card-title">' + icon("g-wifi") + 'Router</div>' +
'<div class="sysnote"><div class="sysnote-title">' + icon("g-check") + 'No router setup needed yet</div>' + '<div class="sysnote"><div class="sysnote-title">' + icon("g-check") + 'No router setup needed yet</div>' +
'<div class="sysnote-desc">Ports 80 and 443 only need to be forwarded on your router if you turn on <strong>BTCPay Server</strong> or <strong>Lightning Wallet Connections (LNURL)</strong>. If you enable one of them, come back here — this card will show exactly what to do.</div></div>' + '<div class="sysnote-desc">Ports 80 and 443 only need to be forwarded on your router if you turn on <strong>BTCPay Server</strong> or <strong>Lightning Wallet Connections (LNURL)</strong>. If you enable one of them, come back here to check your ports.</div></div>' +
'</div>'; '</div>';
} else { } else {
html += '<div class="sysmodal-card">' + html += '<div class="sysmodal-card" id="sys-ports-card" style="display:none">' +
'<div class="sysmodal-card-title">' + icon("g-wifi") + 'Router — Ports to Forward</div>' + '<div class="sysmodal-card-title">' + icon("g-wifi") + 'Router</div>' +
step(1, "Port 80", "TCP — HTTP (redirect to HTTPS)", fwd) + '<div id="sys-ports-status"><div class="sysfineprint">Checking…</div></div>' +
step(2, "Port 443", "TCP — HTTPS", fwd) +
'<div class="sysnote"><div class="sysnote-title">' + icon("g-alert") + 'One router task</div>' +
'<div class="sysnote-desc">Set the internal and external port to the <strong>same number</strong>. You only need to do this once — all your services share these two ports.</div></div>' +
'</div>'; '</div>';
} }
/* Live domain diagnostics (sequential checklist from the backend —
same data the domain-service modals show) */
html += '<div class="sysmodal-card" id="sys-check-card" style="display:none">' +
'<div class="sysmodal-card-title">' + icon("g-check") + 'Ports 80 &amp; 443 Check</div>' +
'<div id="sys-check-steps"><div class="sysfineprint">Checking…</div></div>' +
'</div>';
/* Who uses these ports (redundant on a Node install with no domain /* Who uses these ports (redundant on a Node install with no domain
services on — the router note above already covers it) */ services on — the router note above already covers it) */
if (!isNodeRole() || hasEnabledDomainService(services)) { if (!isNodeRole() || hasEnabledDomainService(services)) {
@@ -300,33 +282,32 @@
$sysBody.innerHTML = html; $sysBody.innerHTML = html;
$sysModal.classList.add("open"); $sysModal.classList.add("open");
/* Poll the first configured domain service for its live checklist */ /* Poll the first configured domain service and reduce its diagnostics
var checkCard = document.getElementById("sys-check-card"); to one verdict: the ports are open or they are not. */
var stepsEl = document.getElementById("sys-check-steps"); var portsCard = document.getElementById("sys-ports-card");
var portsEl = document.getElementById("sys-ports-status");
var units = DOMAIN_UNITS.filter(function (u) { var units = DOMAIN_UNITS.filter(function (u) {
return services.some(function (s) { return s.unit === u && s.enabled; }); return services.some(function (s) { return s.unit === u && s.enabled; });
}); });
if (!units.length || !checkCard || !stepsEl) return; if (!units.length || !portsCard || !portsEl) return;
checkCard.style.display = ""; portsCard.style.display = "";
apiFetch("/api/service-detail/" + encodeURIComponent(units[0])) apiFetch("/api/service-detail/" + encodeURIComponent(units[0]))
.then(function (data) { .then(function (data) {
var steps = (data && data.domain_check_steps) || []; var steps = (data && data.domain_check_steps) || [];
if (!steps.length) { checkCard.style.display = "none"; return; } var portsStep = null;
var mark = { ok: "\u2705", error: "\u274c", warning: "\u26a0\ufe0f", skipped: "\u23ed\ufe0f" }; steps.forEach(function (s) {
var html2 = ""; if (Number(s.step) === 3 || /ports?\s*80/i.test(s.label || "")) portsStep = s;
steps.forEach(function (st) {
var m = mark[st.status] || "\u2014";
var detail = escHtml(st.detail || "").replace(/\n/g, "<br>");
html2 += '<div class="sysstep"><div class="sysnum">' + st.step + '</div><div class="sysstep-x">' +
'<div class="sysstep-t">' + m + " " + escHtml(st.label || "") + '</div>' +
(detail ? '<div class="sysstep-t" style="font-weight:400;color:var(--text-2);font-size:0.77rem;margin-top:3px">' + detail + '</div>' : '') +
'</div></div>';
}); });
stepsEl.innerHTML = html2; if (!portsStep) { portsCard.style.display = "none"; return; }
if (portsStep.status === "ok") {
portsEl.innerHTML = '<div class="svc-detail-status" style="font-size:0.92rem"><span class="status-dot active"></span>Ports 80 and 443 are open</div>';
} else {
portsEl.innerHTML = '<div class="svc-detail-status" style="font-size:0.92rem"><span class="status-dot failed"></span>Ports 80 and 443 are not open</div>';
}
}) })
.catch(function () { .catch(function () {
checkCard.style.display = "none"; portsCard.style.display = "none";
}); });
} }
@@ -725,16 +725,20 @@ async function openServiceDetailModal(unit, name, icon) {
}); });
var stepsHtml = ""; var stepsHtml = "";
steps.forEach(function(step) { steps.forEach(function(step) {
var iconLabel = "—";
if (step.status === "ok") iconLabel = "✅";
else if (step.status === "error") iconLabel = "❌";
else if (step.status === "warning") iconLabel = "⚠️";
else if (step.status === "skipped") iconLabel = "⏭️";
var detail = escHtml(step.detail || "").replace(/\n/g, "<br>"); var detail = escHtml(step.detail || "").replace(/\n/g, "<br>");
stepsHtml += '<div class="svc-detail-troubleshoot" style="margin-bottom:10px">' + if (step.status === "ok") {
'<strong>' + iconLabel + ' Step ' + escHtml(String(step.step)) + ': ' + escHtml(step.label || "") + '</strong>' + // Not a checklist anymore — just note that the domain works.
(detail ? '<div style="margin-top:6px">' + detail + '</div>' : '') + stepsHtml += '<div class="svc-detail-status"><span class="status-dot active"></span>Domain is active</div>' +
'</div>'; (detail ? '<div class="svc-detail-desc" style="margin-top:6px">' + detail + '</div>' : '');
} else {
var iconLabel = "❌";
if (step.status === "warning") iconLabel = "⚠️";
else if (step.status === "skipped") iconLabel = "⏭️";
stepsHtml += '<div class="svc-detail-troubleshoot" style="margin-bottom:10px">' +
'<strong>' + iconLabel + ' ' + escHtml(step.label || "Domain not configured") + '</strong>' +
(detail ? '<div style="margin-top:6px">' + detail + '</div>' : '') +
'</div>';
}
}); });
var domainActionHtml = ""; var domainActionHtml = "";
@@ -746,7 +750,7 @@ async function openServiceDetailModal(unit, name, icon) {
} }
addSetup('<div class="svc-detail-section">' + addSetup('<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Domain Diagnostic Checklist</div>' + '<div class="svc-detail-section-title">Domain Status</div>' +
stepsHtml + stepsHtml +
domainActionHtml + domainActionHtml +
'</div>'); '</div>');