From 23b2cd49edde47c4ba2acbb70ff36ebc21ff4d8e Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 10 May 2026 23:49:30 +0200 Subject: [PATCH] feat(web): wire telnet round-trip probe into pre-flight panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the placeholder "skip — telnet round-trip probe not yet implemented" branch with an actual call to POST /setup/telnet-probe when SSH is unreachable but Telnet:17000 is. SSH-less speakers now get real reachability verification before any migration step runs, instead of being silently ignored by the pre-flight pipeline. Decision tree for the reachability check: - SSH reachable → HTTPS connection test from device (existing) - Telnet:17000 only → Telnet round-trip probe (new) - neither → skip with "no transport reachable" message The probe row reports its result inline with the existing pre-flight panel idiom (🕐 / ⟳ / ✅ / ❌), surfacing elapsed_ms on success so users see how long the round-trip took. Failure messages from the backend (timeout, sys configuration rejected, dial refused) propagate verbatim so the user knows which step of the orchestration tripped. Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/service/handlers/web/js/script.js | 50 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/pkg/service/handlers/web/js/script.js b/pkg/service/handlers/web/js/script.js index 60d6b1f..eb12d3f 100644 --- a/pkg/service/handlers/web/js/script.js +++ b/pkg/service/handlers/web/js/script.js @@ -3016,6 +3016,30 @@ async function checkDNSRedirectionFromDevice(deviceId, targetUrl) { } } +// checkTelnetRoundTrip is the SSH-less alternative to the curl-from- +// device HTTPS test: temporarily flips the speaker's swUpdateUrl via +// telnet, triggers :8090/swUpdateCheck, and reports whether the +// device's outbound landed on our /probe/{token} catch-all. See +// pkg/service/setup/telnet_probe.go for the orchestration details. +async function checkTelnetRoundTrip(deviceId, targetUrl) { + try { + const q = `?target_url=${encodeURIComponent(targetUrl)}`; + const resp = await fetch(`/setup/telnet-probe/${encodeURIComponent(deviceId)}${q}`, {method: "POST"}); + const result = await resp.json(); + if (result.ok) { + const ms = result.result && result.result.elapsed_ms; + return {status: "ok", message: ms ? `reached in ${ms}ms` : undefined}; + } + if (result.error) return {status: "fail", message: result.error.split("\n")[0]}; + if (result.result && result.result.reached === false) { + return {status: "fail", message: "probe inbound not observed before timeout"}; + } + return {status: "fail", message: "probe failed"}; + } catch (e) { + return {status: "fail", message: String(e)}; + } +} + // --- Pre-flight panel: orchestrator --------------------------------- // runApplyPreflight runs the checks visible in the pre-flight panel. @@ -3042,22 +3066,28 @@ async function runApplyPreflight(deviceId, methods, opts, targetUrl) { results.push({name: "Backend summary re-check", status: "ok"}); const summary = r.summary; - // Step 2: HTTPS connection test from the device. SSH-only — for - // SSH-less migrations this becomes the future telnet round-trip - // probe (see TELNET-MIGRATION-METHOD.md). + // Step 2: reachability from the device. SSH-capable speakers get + // the existing curl-from-device HTTPS test; SSH-less speakers + // fall through to the telnet round-trip probe that temporarily + // flips swUpdateUrl and observes the resulting outbound. If + // neither transport is reachable, the check is surfaced as a + // skip rather than silently dropped. if (summary.ssh_success && summary.server_https_url) { const item = addPreflightItem("HTTPS connection from device"); setPreflightItemStatus(item, "running"); const cr = await checkConnectionFromDevice(deviceId, summary.server_https_url); setPreflightItemStatus(item, cr.status, cr.message); results.push({name: "HTTPS connection from device", ...cr}); - } else if (!summary.ssh_success) { - // Surface the deliberate skip rather than silently dropping - // the check — that's what the user means by "feedback always - // visible." - const item = addPreflightItem("HTTPS connection from device"); - setPreflightItemStatus(item, "skip", "SSH unreachable; telnet round-trip probe not yet implemented"); - results.push({name: "HTTPS connection from device", status: "skip"}); + } else if (summary.telnet_reachable) { + const item = addPreflightItem("Telnet round-trip probe (swUpdateUrl)"); + setPreflightItemStatus(item, "running"); + const cr = await checkTelnetRoundTrip(deviceId, targetUrl); + setPreflightItemStatus(item, cr.status, cr.message); + results.push({name: "Telnet round-trip probe", ...cr}); + } else { + const item = addPreflightItem("Reachability from device"); + setPreflightItemStatus(item, "skip", "neither SSH nor Telnet:17000 is reachable"); + results.push({name: "Reachability from device", status: "skip"}); } // Step 3: DNS redirection test (only for resolv plans).