diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index 633f914..8039170 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -211,11 +211,11 @@ async fn refresh_remote_snapshot( } let selected = selected_rig_id(config); - let fallback = &rigs[0]; let target = selected .as_deref() .and_then(|id| rigs.iter().find(|entry| entry.rig_id == id)) - .unwrap_or(fallback); + .or_else(|| choose_default_rig(rigs.as_slice())) + .ok_or_else(|| RigError::communication("GetRigs returned no selectable rig"))?; if selected.as_deref() != Some(target.rig_id.as_str()) { set_selected_rig_id(config, Some(target.rig_id.clone())); @@ -287,6 +287,15 @@ fn set_selected_rig_id(config: &RemoteClientConfig, value: Option) { } } +fn choose_default_rig(rigs: &[RigEntry]) -> Option<&RigEntry> { + rigs.iter().max_by_key(|entry| { + let tx_capable = entry.state.info.capabilities.tx; + let initialized = entry.state.initialized; + // Prefer initialized TX-capable rigs; tie-break by rig_id for deterministic choice. + (tx_capable, initialized, entry.rig_id.as_str()) + }) +} + async fn read_limited_line( reader: &mut R, max_bytes: usize, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index f9719c4..e796a9c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -279,6 +279,8 @@ const headerSigCanvas = document.getElementById("header-sig-canvas"); const themeToggleBtn = document.getElementById("theme-toggle"); const rigSwitchSelect = document.getElementById("rig-switch-select"); const rigSwitchBtn = document.getElementById("rig-switch-btn"); +const headerRigSwitchSelect = document.getElementById("header-rig-switch-select"); +const headerRigSwitchBtn = document.getElementById("header-rig-switch-btn"); let lastControl; let lastTxEn = null; @@ -341,6 +343,25 @@ function readyText() { return lastClientCount !== null ? `Ready \u00b7 ${lastClientCount} user${lastClientCount !== 1 ? "s" : ""}` : "Ready"; } +function populateRigPicker(selectEl, rigIds, activeRigId, disabled) { + if (!selectEl) return; + const selectedBefore = selectEl.value; + selectEl.innerHTML = ""; + rigIds.forEach((id) => { + const opt = document.createElement("option"); + opt.value = id; + opt.textContent = id; + selectEl.appendChild(opt); + }); + const preferred = (typeof activeRigId === "string" && rigIds.includes(activeRigId)) + ? activeRigId + : selectedBefore; + if (preferred && rigIds.includes(preferred)) { + selectEl.value = preferred; + } + selectEl.disabled = disabled; +} + function showHint(msg, duration) { powerHint.textContent = msg; if (hintTimer) clearTimeout(hintTimer); @@ -950,26 +971,11 @@ function render(update) { if (Array.isArray(update.rig_ids)) { lastRigIds = update.rig_ids.filter((id) => typeof id === "string" && id.length > 0); document.getElementById("about-rig-list").textContent = lastRigIds.length ? lastRigIds.join(", ") : "--"; - if (rigSwitchSelect) { - const selectedBefore = rigSwitchSelect.value; - rigSwitchSelect.innerHTML = ""; - lastRigIds.forEach((id) => { - const opt = document.createElement("option"); - opt.value = id; - opt.textContent = id; - rigSwitchSelect.appendChild(opt); - }); - const preferred = (typeof update.active_rig_id === "string" && lastRigIds.includes(update.active_rig_id)) - ? update.active_rig_id - : selectedBefore; - if (preferred && lastRigIds.includes(preferred)) { - rigSwitchSelect.value = preferred; - } - rigSwitchSelect.disabled = lastRigIds.length === 0; - if (rigSwitchBtn) { - rigSwitchBtn.disabled = lastRigIds.length === 0 || authRole === "rx"; - } - } + const disableSwitch = lastRigIds.length === 0 || authRole === "rx"; + populateRigPicker(rigSwitchSelect, lastRigIds, update.active_rig_id, lastRigIds.length === 0); + populateRigPicker(headerRigSwitchSelect, lastRigIds, update.active_rig_id, lastRigIds.length === 0); + if (rigSwitchBtn) rigSwitchBtn.disabled = disableSwitch; + if (headerRigSwitchBtn) headerRigSwitchBtn.disabled = disableSwitch; } if (typeof update.rigctl_clients === "number") { document.getElementById("about-rigctl-clients").textContent = update.rigctl_clients; @@ -1110,8 +1116,8 @@ async function postPath(path) { return resp; } -async function switchRig() { - if (!rigSwitchSelect || !rigSwitchSelect.value) { +async function switchRigFromSelect(selectEl) { + if (!selectEl || !selectEl.value) { showHint("No rig selected", 1500); return; } @@ -1119,25 +1125,31 @@ async function switchRig() { showHint("Control role required", 1500); return; } - if (!lastRigIds.includes(rigSwitchSelect.value)) { + if (!lastRigIds.includes(selectEl.value)) { showHint("Unknown rig", 1500); return; } if (rigSwitchBtn) rigSwitchBtn.disabled = true; + if (headerRigSwitchBtn) headerRigSwitchBtn.disabled = true; showHint("Switching rig…"); try { - await postPath(`/select_rig?rig_id=${encodeURIComponent(rigSwitchSelect.value)}`); + await postPath(`/select_rig?rig_id=${encodeURIComponent(selectEl.value)}`); showHint("Rig switch requested", 1500); } catch (err) { showHint("Rig switch failed", 2000); console.error(err); } finally { - if (rigSwitchBtn) rigSwitchBtn.disabled = authRole === "rx"; + const disableSwitch = lastRigIds.length === 0 || authRole === "rx"; + if (rigSwitchBtn) rigSwitchBtn.disabled = disableSwitch; + if (headerRigSwitchBtn) headerRigSwitchBtn.disabled = disableSwitch; } } if (rigSwitchBtn) { - rigSwitchBtn.addEventListener("click", switchRig); + rigSwitchBtn.addEventListener("click", () => switchRigFromSelect(rigSwitchSelect)); +} +if (headerRigSwitchBtn) { + headerRigSwitchBtn.addEventListener("click", () => switchRigFromSelect(headerRigSwitchSelect)); } powerBtn.addEventListener("click", async () => { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index a3dab1c..870ba12 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -22,6 +22,10 @@
+
+ + +
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index 5d544f6..9ffe6b9 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -338,6 +338,27 @@ small { color: var(--text-muted); } gap: 0.4rem; flex-shrink: 0; } +.header-rig-switch { + display: flex; + align-items: center; + gap: 0.35rem; +} +.header-rig-switch select { + min-width: 8rem; + height: 2rem; + padding: 0.15rem 0.35rem; + border: 1px solid var(--border-light); + border-radius: 6px; + background: var(--input-bg); + color: var(--text); + font-size: 0.85rem; +} +.header-rig-switch button { + height: 2rem; + padding: 0 0.65rem; + font-size: 0.78rem; + white-space: nowrap; +} .header-logo { height: 6em; width: auto; flex-shrink: 0; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); } .button-group { display: flex; @@ -549,6 +570,8 @@ button:focus-visible, input:focus-visible, select:focus-visible { .header-text { width: auto; min-width: 0; flex: 0 1 auto; } .header-signal-wrap { display: none; } .header-right { align-items: flex-end; } + .header-rig-switch { width: 100%; justify-content: flex-end; } + .header-rig-switch select { min-width: 6.5rem; } .controls-row { grid-template-columns: 1fr auto; } .controls-col-power { grid-column: 1 / -1; } .controls-col.label-below-col .inline,