[feat](trx-frontend-http): add Background Decoding Scheduler
Implements a scheduler that retunes the rig automatically when no SSE
clients are connected. Two modes are supported:
- Grayline: tunes to per-period bookmarks (dawn/day/dusk/night) based on
an inline NOAA solar algorithm given station lat/lon.
- Time Span: tunes to bookmarks within user-defined UTC windows; midnight-
spanning intervals supported.
Backend:
- SchedulerStore (PickleDB, sch:{rig_id} keys) in scheduler.rs
- spawn_scheduler_task polls every 30 s, checks context.sse_clients == 0,
sends SetFreq + SetMode via RigRequest with rig_id_override
- HTTP API: GET/PUT/DELETE /scheduler/{rig_id}, GET …/status
- sse_clients Arc<AtomicUsize> added to FrontendRuntimeContext and shared
with the SSE counter in build_server (single source of truth)
- /scheduler/ added to Read auth routes (write requires Control)
Frontend:
- Scheduler tab (clock icon, 6th position) with Grayline/TimeSpan UI
- scheduler.js plugin: loads config + bookmarks, live status polling
every 15 s, write controls hidden for Rx-role users
- CSS .sch-* component styles added to style.css
- SCHEDULER.md design document at repo root
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -3347,7 +3347,7 @@ if (spectrumBwSweetBtn) {
|
||||
}
|
||||
|
||||
// --- Tab navigation ---
|
||||
const TAB_ORDER = ["main", "bookmarks", "decoders", "map", "about"];
|
||||
const TAB_ORDER = ["main", "bookmarks", "decoders", "map", "scheduler", "about"];
|
||||
|
||||
function navigateToTab(name) {
|
||||
if (authEnabled && !authRole && name !== "main") return;
|
||||
@@ -3415,6 +3415,10 @@ document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
||||
window.addEventListener("resize", () => { scheduleSpectrumLayout(); });
|
||||
|
||||
// --- Auth startup sequence ---
|
||||
function getAvailableRigIds() {
|
||||
return lastRigIds || [];
|
||||
}
|
||||
|
||||
async function initializeApp() {
|
||||
showAuthGate(false);
|
||||
const authStatus = await checkAuthStatus();
|
||||
@@ -3426,6 +3430,7 @@ async function initializeApp() {
|
||||
updateAuthUI();
|
||||
connect();
|
||||
connectDecode();
|
||||
initSchedulerUI();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
return;
|
||||
@@ -3439,6 +3444,7 @@ async function initializeApp() {
|
||||
applyAuthRestrictions();
|
||||
connect();
|
||||
connectDecode();
|
||||
initSchedulerUI();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
} else {
|
||||
@@ -3449,6 +3455,13 @@ async function initializeApp() {
|
||||
}
|
||||
}
|
||||
|
||||
function initSchedulerUI() {
|
||||
if (typeof initScheduler === "function") {
|
||||
initScheduler(lastActiveRigId, authRole);
|
||||
wireSchedulerEvents();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup auth form
|
||||
document.getElementById("auth-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -3466,6 +3479,7 @@ document.getElementById("auth-form").addEventListener("submit", async (e) => {
|
||||
applyAuthRestrictions();
|
||||
connect();
|
||||
connectDecode();
|
||||
initSchedulerUI();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
} catch (err) {
|
||||
@@ -3488,6 +3502,7 @@ if (guestBtn) {
|
||||
applyAuthRestrictions();
|
||||
connect();
|
||||
connectDecode();
|
||||
initSchedulerUI();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user