diff --git a/pkg/service/handlers/web/index.html b/pkg/service/handlers/web/index.html index 3b3f9dc..442fb8c 100644 --- a/pkg/service/handlers/web/index.html +++ b/pkg/service/handlers/web/index.html @@ -510,8 +510,71 @@ Migration Summary for -
Migration Status:
+Migration Status:
+ +| URL Configuration | ++ |
| DNS Interception | ++ |
| CA / TLS | ++ |
| remote_services | ++ |
| Account paired | ++ |
| XML config backup | ++ |
SSH Connection:
Telnet (Port 17000):
diff --git a/pkg/service/handlers/web/js/script.js b/pkg/service/handlers/web/js/script.js
index f9fb938..f4483cb 100644
--- a/pkg/service/handlers/web/js/script.js
+++ b/pkg/service/handlers/web/js/script.js
@@ -1761,6 +1761,7 @@ async function showSummary(deviceId) {
document.getElementById("ssh-status").innerText = summary.ssh_success ? "✅ Success" : "❌ Failed";
document.getElementById("ssh-status").style.color = summary.ssh_success ? "green" : "red";
+ renderMigrationState(summary);
renderTelnetPreflight(summary);
renderPreflightWarnings(summary);
fillTelnetURLInputs(defaultTelnetURLs(targetUrl));
@@ -2445,6 +2446,175 @@ function readTelnetURLOptions() {
return out;
}
+// renderMigrationState fills the three-axis state card at the top of
+// the migration summary: transports, migration-state axes (URL config,
+// DNS interception, CA/TLS), and preconditions (remote_services,
+// pairing, backup). Reads only fields the backend already exposes —
+// is_migrated remains the OR of the per-axis booleans.
+function renderMigrationState(summary) {
+ // --- Transports ---
+ setStateChip("state-ssh", summary.ssh_success, "Reachable", "Unreachable");
+ setStateChip("state-telnet", summary.telnet_reachable, "Reachable", "Unreachable");
+
+ const bannerEl = document.getElementById("state-telnet-banner");
+ if (bannerEl) bannerEl.innerText = summary.telnet_banner ? `(${summary.telnet_banner})` : "";
+
+ const errorEl = document.getElementById("state-telnet-error");
+ if (errorEl) {
+ if (summary.telnet_probe_error && !summary.telnet_reachable) {
+ errorEl.innerText = "Probe error: " + summary.telnet_probe_error;
+ errorEl.style.display = "block";
+ } else {
+ errorEl.style.display = "none";
+ }
+ }
+
+ // --- URL Configuration axis ---
+ const urlCell = document.getElementById("state-url");
+ if (urlCell) {
+ urlCell.replaceChildren();
+ const verdict = urlConfigVerdict(summary);
+ urlCell.appendChild(stateLine(verdict.icon, verdict.text, verdict.note));
+
+ const live = parseTelnetVerifiedConfig(summary.telnet_verified_config || "");
+ const xml = summary.parsed_current_config || {};
+ const fields = [
+ ["margeServerUrl", "marge"],
+ ["statsServerUrl", "stats"],
+ ["swUpdateUrl", "sw_update"],
+ ["bmxRegistryUrl", "bmx"],
+ ];
+ const detail = document.createElement("div");
+ detail.style.cssText = "margin-top: 4px; font-family: monospace; font-size: 0.85em; color: #555";
+ for (const [key] of fields) {
+ const xmlVal = xml[key] || "";
+ const telVal = live[key] || "";
+ const row = document.createElement("div");
+ row.style.cssText = "padding: 1px 0";
+ const label = document.createElement("strong");
+ label.style.cssText = "color: #333";
+ label.textContent = key + ": ";
+ row.appendChild(label);
+ row.appendChild(document.createTextNode(formatURLPair(xmlVal, telVal)));
+ detail.appendChild(row);
+ }
+ urlCell.appendChild(detail);
+ }
+
+ // --- DNS Interception axis ---
+ const dnsCell = document.getElementById("state-dns");
+ if (dnsCell) {
+ dnsCell.replaceChildren();
+ const v = dnsInterceptionVerdict(summary);
+ dnsCell.appendChild(stateLine(v.icon, v.text, v.note));
+ }
+
+ // --- CA / TLS axis ---
+ const caCell = document.getElementById("state-ca");
+ if (caCell) {
+ caCell.replaceChildren();
+ const v = caVerdict(summary);
+ caCell.appendChild(stateLine(v.icon, v.text, v.note));
+ }
+
+ // --- Preconditions ---
+ const remoteCell = document.getElementById("state-remote-services-cell");
+ if (remoteCell) {
+ remoteCell.replaceChildren();
+ const v = remoteServicesVerdict(summary);
+ remoteCell.appendChild(stateLine(v.icon, v.text, v.note));
+ }
+
+ const pairedCell = document.getElementById("state-paired");
+ if (pairedCell) {
+ pairedCell.replaceChildren();
+ if (summary.is_paired) {
+ pairedCell.appendChild(stateLine("✅", "Paired", `(account ${summary.account_id || "?"})`));
+ } else {
+ pairedCell.appendChild(stateLine("❌", "Not paired", "(/setMargeAccount or telnet envswitch needed)"));
+ }
+ }
+
+ const backupCell = document.getElementById("state-backup");
+ if (backupCell) {
+ backupCell.replaceChildren();
+ if (summary.original_config) {
+ backupCell.appendChild(stateLine("✅", "Found .original", ""));
+ } else {
+ backupCell.appendChild(stateLine("❌", "Not found", "(only relevant for the XML migration method)"));
+ }
+ }
+}
+
+// setStateChip writes a green ✅ / red ❌ chip into the element.
+function setStateChip(elemId, ok, okText, badText) {
+ const el = document.getElementById(elemId);
+ if (!el) return;
+ if (ok) {
+ el.innerText = "✅ " + okText;
+ el.style.color = "green";
+ } else {
+ el.innerText = "❌ " + badText;
+ el.style.color = "red";
+ }
+}
+
+// stateLine returns a DOM fragment "