fix(trx-client): prefer TX rig default and add header rig picker
This commit is contained in:
@@ -211,11 +211,11 @@ async fn refresh_remote_snapshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let selected = selected_rig_id(config);
|
let selected = selected_rig_id(config);
|
||||||
let fallback = &rigs[0];
|
|
||||||
let target = selected
|
let target = selected
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(|id| rigs.iter().find(|entry| entry.rig_id == id))
|
.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()) {
|
if selected.as_deref() != Some(target.rig_id.as_str()) {
|
||||||
set_selected_rig_id(config, Some(target.rig_id.clone()));
|
set_selected_rig_id(config, Some(target.rig_id.clone()));
|
||||||
@@ -287,6 +287,15 @@ fn set_selected_rig_id(config: &RemoteClientConfig, value: Option<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<R: AsyncBufRead + Unpin>(
|
async fn read_limited_line<R: AsyncBufRead + Unpin>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
max_bytes: usize,
|
max_bytes: usize,
|
||||||
|
|||||||
@@ -279,6 +279,8 @@ const headerSigCanvas = document.getElementById("header-sig-canvas");
|
|||||||
const themeToggleBtn = document.getElementById("theme-toggle");
|
const themeToggleBtn = document.getElementById("theme-toggle");
|
||||||
const rigSwitchSelect = document.getElementById("rig-switch-select");
|
const rigSwitchSelect = document.getElementById("rig-switch-select");
|
||||||
const rigSwitchBtn = document.getElementById("rig-switch-btn");
|
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 lastControl;
|
||||||
let lastTxEn = null;
|
let lastTxEn = null;
|
||||||
@@ -341,6 +343,25 @@ function readyText() {
|
|||||||
return lastClientCount !== null ? `Ready \u00b7 ${lastClientCount} user${lastClientCount !== 1 ? "s" : ""}` : "Ready";
|
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) {
|
function showHint(msg, duration) {
|
||||||
powerHint.textContent = msg;
|
powerHint.textContent = msg;
|
||||||
if (hintTimer) clearTimeout(hintTimer);
|
if (hintTimer) clearTimeout(hintTimer);
|
||||||
@@ -950,26 +971,11 @@ function render(update) {
|
|||||||
if (Array.isArray(update.rig_ids)) {
|
if (Array.isArray(update.rig_ids)) {
|
||||||
lastRigIds = update.rig_ids.filter((id) => typeof id === "string" && id.length > 0);
|
lastRigIds = update.rig_ids.filter((id) => typeof id === "string" && id.length > 0);
|
||||||
document.getElementById("about-rig-list").textContent = lastRigIds.length ? lastRigIds.join(", ") : "--";
|
document.getElementById("about-rig-list").textContent = lastRigIds.length ? lastRigIds.join(", ") : "--";
|
||||||
if (rigSwitchSelect) {
|
const disableSwitch = lastRigIds.length === 0 || authRole === "rx";
|
||||||
const selectedBefore = rigSwitchSelect.value;
|
populateRigPicker(rigSwitchSelect, lastRigIds, update.active_rig_id, lastRigIds.length === 0);
|
||||||
rigSwitchSelect.innerHTML = "";
|
populateRigPicker(headerRigSwitchSelect, lastRigIds, update.active_rig_id, lastRigIds.length === 0);
|
||||||
lastRigIds.forEach((id) => {
|
if (rigSwitchBtn) rigSwitchBtn.disabled = disableSwitch;
|
||||||
const opt = document.createElement("option");
|
if (headerRigSwitchBtn) headerRigSwitchBtn.disabled = disableSwitch;
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (typeof update.rigctl_clients === "number") {
|
if (typeof update.rigctl_clients === "number") {
|
||||||
document.getElementById("about-rigctl-clients").textContent = update.rigctl_clients;
|
document.getElementById("about-rigctl-clients").textContent = update.rigctl_clients;
|
||||||
@@ -1110,8 +1116,8 @@ async function postPath(path) {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchRig() {
|
async function switchRigFromSelect(selectEl) {
|
||||||
if (!rigSwitchSelect || !rigSwitchSelect.value) {
|
if (!selectEl || !selectEl.value) {
|
||||||
showHint("No rig selected", 1500);
|
showHint("No rig selected", 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1119,25 +1125,31 @@ async function switchRig() {
|
|||||||
showHint("Control role required", 1500);
|
showHint("Control role required", 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!lastRigIds.includes(rigSwitchSelect.value)) {
|
if (!lastRigIds.includes(selectEl.value)) {
|
||||||
showHint("Unknown rig", 1500);
|
showHint("Unknown rig", 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rigSwitchBtn) rigSwitchBtn.disabled = true;
|
if (rigSwitchBtn) rigSwitchBtn.disabled = true;
|
||||||
|
if (headerRigSwitchBtn) headerRigSwitchBtn.disabled = true;
|
||||||
showHint("Switching rig…");
|
showHint("Switching rig…");
|
||||||
try {
|
try {
|
||||||
await postPath(`/select_rig?rig_id=${encodeURIComponent(rigSwitchSelect.value)}`);
|
await postPath(`/select_rig?rig_id=${encodeURIComponent(selectEl.value)}`);
|
||||||
showHint("Rig switch requested", 1500);
|
showHint("Rig switch requested", 1500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showHint("Rig switch failed", 2000);
|
showHint("Rig switch failed", 2000);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
} 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) {
|
if (rigSwitchBtn) {
|
||||||
rigSwitchBtn.addEventListener("click", switchRig);
|
rigSwitchBtn.addEventListener("click", () => switchRigFromSelect(rigSwitchSelect));
|
||||||
|
}
|
||||||
|
if (headerRigSwitchBtn) {
|
||||||
|
headerRigSwitchBtn.addEventListener("click", () => switchRigFromSelect(headerRigSwitchSelect));
|
||||||
}
|
}
|
||||||
|
|
||||||
powerBtn.addEventListener("click", async () => {
|
powerBtn.addEventListener("click", async () => {
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<img id="logo" class="header-logo" src="/logo.png?v=1" alt="trx logo" onerror="this.style.display='none'" />
|
<img id="logo" class="header-logo" src="/logo.png?v=1" alt="trx logo" onerror="this.style.display='none'" />
|
||||||
|
<div class="header-rig-switch">
|
||||||
|
<select id="header-rig-switch-select" aria-label="Select active rig"></select>
|
||||||
|
<button id="header-rig-switch-btn" type="button">Switch Rig</button>
|
||||||
|
</div>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button id="theme-toggle" class="theme-toggle-btn" type="button" aria-label="Toggle dark or light theme">Light</button>
|
<button id="theme-toggle" class="theme-toggle-btn" type="button" aria-label="Toggle dark or light theme">Light</button>
|
||||||
<button id="header-auth-btn" class="theme-toggle-btn" type="button" style="display:none;" aria-label="Login or Logout">Login</button>
|
<button id="header-auth-btn" class="theme-toggle-btn" type="button" style="display:none;" aria-label="Login or Logout">Login</button>
|
||||||
|
|||||||
@@ -338,6 +338,27 @@ small { color: var(--text-muted); }
|
|||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
flex-shrink: 0;
|
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)); }
|
.header-logo { height: 6em; width: auto; flex-shrink: 0; filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35)); }
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
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-text { width: auto; min-width: 0; flex: 0 1 auto; }
|
||||||
.header-signal-wrap { display: none; }
|
.header-signal-wrap { display: none; }
|
||||||
.header-right { align-items: flex-end; }
|
.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-row { grid-template-columns: 1fr auto; }
|
||||||
.controls-col-power { grid-column: 1 / -1; }
|
.controls-col-power { grid-column: 1 / -1; }
|
||||||
.controls-col.label-below-col .inline,
|
.controls-col.label-below-col .inline,
|
||||||
|
|||||||
Reference in New Issue
Block a user