[fix](trx-frontend-http): refresh background decode settings UI

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-12 22:48:24 +01:00
parent 2462f1dd47
commit a91a1868d8
6 changed files with 92 additions and 37 deletions
@@ -750,6 +750,7 @@ function applyRigList(activeRigId, rigIds, displayNames) {
populateRigPicker(headerRigSwitchSelect, lastRigIds, activeRigId, disableSwitch);
updateRigSubtitle(activeRigId);
if (typeof reloadSchedulerRigSelect === "function") reloadSchedulerRigSelect();
if (typeof reloadBackgroundDecodeRigSelect === "function") reloadBackgroundDecodeRigSelect();
}
async function refreshRigList() {
@@ -804,7 +804,7 @@
</div>
</label>
</div>
<div id="background-decode-bookmark-list" class="bgd-bookmark-list"></div>
<div id="background-decode-bookmark-list" class="sch-extra-bm-list bgd-bookmark-list"></div>
</div>
<div class="sch-actions">
@@ -26,6 +26,7 @@
if (!sel) return;
const rigs = typeof getAvailableRigIds === "function" ? getAvailableRigIds() : [];
if (!rigs.length) return;
const prevRigId = currentRigId;
sel.innerHTML = "";
rigs.forEach(function (rigId) {
const opt = document.createElement("option");
@@ -37,6 +38,11 @@
if (!currentRigId || !rigs.includes(currentRigId)) {
currentRigId = rigs[0];
sel.value = currentRigId;
} else {
sel.value = currentRigId;
}
if (currentRigId && currentRigId !== prevRigId) {
loadBackgroundDecode();
}
}
@@ -158,14 +164,13 @@
ids.forEach(function (id) {
const bookmark = bookmarkList.find(function (item) { return item.id === id; });
const chip = document.createElement("div");
chip.className = "bgd-bookmark-chip";
chip.className = "sch-extra-bm-tag bgd-bookmark-tag";
const decoders = bookmarkDecoderKinds(bookmark);
chip.innerHTML =
'<span>' + escHtml(bookmark ? bookmark.name : id) + '</span>' +
'<span class="bgd-bookmark-chip-meta">' + escHtml(bookmark ? (formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) : "Missing bookmark") + '</span>';
const btn = document.createElement("button");
btn.type = "button";
btn.className = "bgd-bookmark-chip-remove sch-write";
'<span class="bgd-bookmark-meta">' + escHtml(bookmark ? (formatFreq(bookmark.freq_hz) + " " + bookmark.mode + " · " + decoders.join("/").toUpperCase()) : "Missing bookmark") + '</span>';
const btn = document.createElement("span");
btn.className = "sch-extra-bm-rm";
btn.textContent = "×";
btn.addEventListener("click", function () {
removeBookmark(id);
@@ -293,9 +298,11 @@
case "active": return "Active";
case "out_of_span": return "Out of span";
case "waiting_for_spectrum": return "Waiting";
case "waiting_for_user": return "No user";
case "missing_bookmark": return "Missing";
case "no_supported_decoders": return "Unsupported";
case "disabled": return "Disabled";
case "handled_by_scheduler": return "Scheduler";
default: return "Inactive";
}
}
@@ -333,7 +340,8 @@
function wireBackgroundDecodeEvents() {
const rigSel = document.getElementById("background-decode-rig-select");
if (rigSel) {
if (rigSel && !rigSel._wired) {
rigSel._wired = true;
rigSel.addEventListener("change", function () {
currentRigId = rigSel.value;
loadBackgroundDecode();
@@ -341,15 +349,25 @@
}
const addBtn = document.getElementById("background-decode-bookmark-add");
if (addBtn) addBtn.addEventListener("click", addBookmark);
if (addBtn && !addBtn._wired) {
addBtn._wired = true;
addBtn.addEventListener("click", addBookmark);
}
const saveBtn = document.getElementById("background-decode-save-btn");
if (saveBtn) saveBtn.addEventListener("click", saveBackgroundDecode);
if (saveBtn && !saveBtn._wired) {
saveBtn._wired = true;
saveBtn.addEventListener("click", saveBackgroundDecode);
}
const resetBtn = document.getElementById("background-decode-reset-btn");
if (resetBtn) resetBtn.addEventListener("click", resetBackgroundDecode);
if (resetBtn && !resetBtn._wired) {
resetBtn._wired = true;
resetBtn.addEventListener("click", resetBackgroundDecode);
}
}
window.initBackgroundDecode = initBackgroundDecode;
window.wireBackgroundDecodeEvents = wireBackgroundDecodeEvents;
window.reloadBackgroundDecodeRigSelect = renderRigSelect;
})();
@@ -3493,6 +3493,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
.sch-extra-bm-rm:hover { opacity: 1; }
.bgd-toggle-wrap {
min-width: 18rem;
flex: 1 1 20rem;
}
.bgd-toggle-row {
display: inline-flex;
@@ -3506,41 +3507,25 @@ button:focus-visible, input:focus-visible, select:focus-visible {
display: flex;
gap: 0.55rem;
align-items: center;
width: 100%;
}
.bgd-bookmark-pick {
min-width: min(34rem, 100%);
flex: 1 1 28rem;
min-width: 16rem;
}
.bgd-add-row .status-input {
flex: 1 1 auto;
}
.bgd-bookmark-list {
display: flex;
flex-wrap: wrap;
gap: 0.55rem;
min-height: 1.8rem;
}
.bgd-bookmark-chip {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.35rem 0.55rem;
border-radius: 999px;
border: 1px solid var(--border-light);
background: var(--btn-bg);
color: var(--text);
font-size: 0.85rem;
.bgd-bookmark-tag {
padding-right: 0.3rem;
}
.bgd-bookmark-chip-meta {
.bgd-bookmark-meta {
color: var(--text-muted);
font-size: 0.78rem;
}
.bgd-bookmark-chip-remove {
border: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
padding: 0;
height: auto;
}
.bgd-bookmark-chip-remove:hover {
color: var(--text);
}
.bgd-status-list {
display: grid;
gap: 0.65rem;
@@ -3572,7 +3557,9 @@ button:focus-visible, input:focus-visible, select:focus-visible {
}
.bgd-status-state[data-state="out_of_span"],
.bgd-status-state[data-state="waiting_for_spectrum"],
.bgd-status-state[data-state="inactive"] {
.bgd-status-state[data-state="waiting_for_user"],
.bgd-status-state[data-state="inactive"],
.bgd-status-state[data-state="handled_by_scheduler"] {
color: var(--accent-yellow);
}
.bgd-status-state[data-state="missing_bookmark"],
@@ -3591,4 +3578,7 @@ button:focus-visible, input:focus-visible, select:focus-visible {
flex-direction: column;
align-items: stretch;
}
.bgd-add-row button {
width: 100%;
}
}
@@ -4,6 +4,7 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use std::time::Duration;
@@ -17,6 +18,7 @@ use trx_frontend::{FrontendRuntimeContext, SharedSpectrum, VChanAudioCmd};
use uuid::Uuid;
use crate::server::bookmarks::{Bookmark, BookmarkStore};
use crate::server::scheduler::SchedulerStatusMap;
const SUPPORTED_DECODER_KINDS: &[&str] = &["ft8", "wspr", "hf-aprs"];
const CHANNEL_KIND_NAME: &str = "VirtualBackgroundDecodeChannel";
@@ -119,6 +121,7 @@ pub struct BackgroundDecodeManager {
store: Arc<BackgroundDecodeStore>,
bookmarks: Arc<BookmarkStore>,
context: Arc<FrontendRuntimeContext>,
scheduler_status: SchedulerStatusMap,
status: Arc<RwLock<HashMap<String, BackgroundDecodeStatus>>>,
notify_tx: broadcast::Sender<()>,
}
@@ -128,12 +131,14 @@ impl BackgroundDecodeManager {
store: Arc<BackgroundDecodeStore>,
bookmarks: Arc<BookmarkStore>,
context: Arc<FrontendRuntimeContext>,
scheduler_status: SchedulerStatusMap,
) -> Arc<Self> {
let (notify_tx, _) = broadcast::channel(16);
Arc::new(Self {
store,
bookmarks,
context,
scheduler_status,
status: Arc::new(RwLock::new(HashMap::new())),
notify_tx,
})
@@ -296,6 +301,12 @@ impl BackgroundDecodeManager {
let config = self.get_config(&rig_id);
let selected = dedup_ids(&config.bookmark_ids);
let users_connected = self.context.sse_clients.load(Ordering::Relaxed) > 0;
let scheduled_bookmark_ids = if users_connected {
Vec::new()
} else {
self.scheduler_bookmark_ids(&rig_id)
};
let selected_bookmarks: HashMap<String, Bookmark> = self
.bookmarks
.list()
@@ -344,6 +355,18 @@ impl BackgroundDecodeManager {
continue;
}
if !users_connected {
status.state = "waiting_for_user".to_string();
statuses.push(status);
continue;
}
if scheduled_bookmark_ids.iter().any(|id| id == &bookmark.id) {
status.state = "handled_by_scheduler".to_string();
statuses.push(status);
continue;
}
let (Some(center_hz), Some(half_span_hz)) = (center_hz, half_span_hz) else {
status.state = "waiting_for_spectrum".to_string();
statuses.push(status);
@@ -409,6 +432,28 @@ impl BackgroundDecodeManager {
}
}
fn scheduler_bookmark_ids(&self, rig_id: &str) -> Vec<String> {
let Ok(guard) = self.scheduler_status.read() else {
return Vec::new();
};
let Some(status) = guard.get(rig_id) else {
return Vec::new();
};
if !status.active {
return Vec::new();
}
let mut out = Vec::new();
if let Some(id) = status.last_bookmark_id.clone() {
out.push(id);
}
for id in &status.last_bookmark_ids {
if !out.iter().any(|existing| existing == id) {
out.push(id.clone());
}
}
out
}
async fn run(self: Arc<Self>) {
let mut runtime = BackgroundRuntimeState::default();
let mut notify_rx = self.notify_tx.subscribe();
@@ -92,6 +92,7 @@ async fn serve(
background_decode_store,
bookmark_store.clone(),
context.clone(),
scheduler_status.clone(),
);
background_decode_mgr.spawn();