From 3dd3e3eaef6669fbf7570ecb8f76745b384878bd Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 10 May 2026 22:33:36 +0200 Subject: [PATCH] feat(web): per-field URL editor with validation in the Plan card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Service URLs section to the Plan card with four free-form URL inputs (margeServerUrl, statsServerUrl, swUpdateUrl, bmxRegistryUrl), a "Current on Device" column populated from telnet getpdo (falling back to the SSH-read XML config), a Soundcork-mode checkbox that flips the /marge suffix on margeServerUrl, and a Reset-to-defaults button. Validation runs on every keystroke (oninput) and on each summary render: each URL must parse via the URL constructor, the scheme must be http or https, the hostname must be non-empty, and "localhost" or "127.0.0.1" are explicitly rejected (the speaker can't reach this machine via that name). Invalid inputs get a red border, an inline error list surfaces under the table, and the Apply Suggested Plan button is disabled until everything is valid. migrate() also gates on validatePlanURLs() and surfaces a clear status message rather than sending typoed URLs that would silently brick the speaker. The Plan card's per-field URLs feed both XML and Telnet migrations via the marge_url / stats_url / sw_update_url / bmx_url options the backend's applyURLOverrides honors. The legacy XML dropdowns (self/proxied/original) and the duplicate URL Targets table inside the Telnet pane stay in the markup for now — the next iteration removes them once we're confident the Plan card flow covers everything. Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/service/handlers/web/index.html | 87 ++++++++++ pkg/service/handlers/web/js/script.js | 223 ++++++++++++++++++++++++-- 2 files changed, 301 insertions(+), 9 deletions(-) diff --git a/pkg/service/handlers/web/index.html b/pkg/service/handlers/web/index.html index a4f975f..f4a943f 100644 --- a/pkg/service/handlers/web/index.html +++ b/pkg/service/handlers/web/index.html @@ -787,6 +787,93 @@ +
+

Service URLs

+

+ Pre-filled from the target URL above. Edit any field for advanced setups (e.g. soundcork users + append /marge to margeServerUrl). These overrides apply to both XML + and Telnet migrations. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldCurrent on DeviceTarget URL
margeServerUrl + +
statsServerUrl + +
swUpdateUrl + +
bmxRegistryUrl + +
+
+ + +
+ +
+

Suggested plan

0; + } + + return errors.length === 0; +} + +// resetPlanURLsToDefaults wipes manual edits and reapplies the +// canonical defaults derived from the current target URL, honoring +// the Soundcork-mode checkbox. +function resetPlanURLsToDefaults() { + const targetUrl = document.getElementById("plan-target-url").value; + const soundcork = document.getElementById("plan-soundcork-mode") && + document.getElementById("plan-soundcork-mode").checked; + fillPlanURLInputs(defaultServiceURLs(targetUrl, {soundcorkMode: soundcork}), {force: true}); +} + +// toggleSoundcorkMode reapplies defaults so the /marge suffix appears +// or disappears on margeServerUrl. Manual edits are intentionally +// reset — the checkbox is a deliberate "give me the canonical +// soundcork shape" affordance, not a soft hint. +function toggleSoundcorkMode() { + resetPlanURLsToDefaults(); +} + // computeSuggestedPlan picks the most conservative migration recipe // for the device based on which transports are reachable. The chosen // default is XML over SSH with HTTP — fewest moving parts, no DNS or @@ -2545,6 +2732,24 @@ function computeSuggestedPlan(summary) { }; } +// renderPlanCurrentURLs populates the "Current on Device" cells in the +// Service URLs table from whichever transport answered (telnet +// getpdo, falling back to the SSH-read XML config). +function renderPlanCurrentURLs(summary) { + const live = parseTelnetVerifiedConfig(summary.telnet_verified_config || ""); + const xml = summary.parsed_current_config || {}; + const cells = [ + ["plan-current-marge", live.margeServerUrl || xml.margeServerUrl], + ["plan-current-stats", live.statsServerUrl || xml.statsServerUrl], + ["plan-current-sw_update", live.swUpdateUrl || xml.swUpdateUrl], + ["plan-current-bmx", live.bmxRegistryUrl || xml.bmxRegistryUrl], + ]; + for (const [id, value] of cells) { + const el = document.getElementById(id); + if (el) el.innerText = value || "—"; + } +} + // renderPlan populates the Plan card: capabilities header + suggested // plan box. Reads only fields the backend already exposes; the // suggested-plan logic lives in computeSuggestedPlan.