[fix](trx-frontend-http): show scheduler interleave timing
Replace the misleading scheduler task countdown with the actual time-span interleave switch timing in the main controls row. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -267,7 +267,7 @@
|
|||||||
<div class="scheduler-release-wrap">
|
<div class="scheduler-release-wrap">
|
||||||
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
|
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
|
||||||
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
|
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
|
||||||
<div id="scheduler-cycle-status" class="scheduler-cycle-status">Next scheduler cycle in 30s.</div>
|
<div id="scheduler-cycle-status" class="scheduler-cycle-status">Interleaving: --</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
let currentConfig = null;
|
let currentConfig = null;
|
||||||
let bookmarkList = []; // [{id, name, freq_hz, mode}, ...]
|
let bookmarkList = []; // [{id, name, freq_hz, mode}, ...]
|
||||||
let statusInterval = null;
|
let statusInterval = null;
|
||||||
|
let interleaveTicker = null;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Init
|
// Init
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
currentRigId = rigId || null;
|
currentRigId = rigId || null;
|
||||||
if (currentRigId) loadScheduler();
|
if (currentRigId) loadScheduler();
|
||||||
startStatusPolling();
|
startStatusPolling();
|
||||||
|
startInterleaveTicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroyScheduler() {
|
function destroyScheduler() {
|
||||||
@@ -31,6 +33,10 @@
|
|||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
statusInterval = null;
|
statusInterval = null;
|
||||||
}
|
}
|
||||||
|
if (interleaveTicker) {
|
||||||
|
clearInterval(interleaveTicker);
|
||||||
|
interleaveTicker = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -40,6 +46,7 @@
|
|||||||
const nextRigId = rigId || null;
|
const nextRigId = rigId || null;
|
||||||
if (nextRigId === currentRigId) return;
|
if (nextRigId === currentRigId) return;
|
||||||
currentRigId = nextRigId;
|
currentRigId = nextRigId;
|
||||||
|
renderSchedulerInterleaveStatus();
|
||||||
if (!currentRigId) return;
|
if (!currentRigId) return;
|
||||||
loadScheduler();
|
loadScheduler();
|
||||||
pollStatus();
|
pollStatus();
|
||||||
@@ -104,9 +111,11 @@
|
|||||||
bookmarkList = Array.isArray(bms) ? bms : [];
|
bookmarkList = Array.isArray(bms) ? bms : [];
|
||||||
populateTsBookmarkSelect();
|
populateTsBookmarkSelect();
|
||||||
renderScheduler();
|
renderScheduler();
|
||||||
|
renderSchedulerInterleaveStatus();
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
console.error("scheduler load failed", e);
|
console.error("scheduler load failed", e);
|
||||||
|
renderSchedulerInterleaveStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +128,74 @@
|
|||||||
pollStatus();
|
pollStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startInterleaveTicker() {
|
||||||
|
if (interleaveTicker) clearInterval(interleaveTicker);
|
||||||
|
interleaveTicker = setInterval(renderSchedulerInterleaveStatus, 1000);
|
||||||
|
renderSchedulerInterleaveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulerUtcSeconds() {
|
||||||
|
return Math.floor(Date.now() / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulerUtcMinuteInfo() {
|
||||||
|
const secs = schedulerUtcSeconds();
|
||||||
|
const secsIntoDay = ((secs % 86400) + 86400) % 86400;
|
||||||
|
return {
|
||||||
|
minuteOfDay: Math.floor(secsIntoDay / 60),
|
||||||
|
secondOfMinute: secsIntoDay % 60,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulerEntryIsActive(entry, nowMin) {
|
||||||
|
const start = Number(entry && entry.start_min);
|
||||||
|
const end = Number(entry && entry.end_min);
|
||||||
|
if (!Number.isFinite(start) || !Number.isFinite(end)) return false;
|
||||||
|
if (start === end) return true;
|
||||||
|
if (start < end) return nowMin >= start && nowMin < end;
|
||||||
|
return nowMin >= start || nowMin < end;
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulerInterleaveSummary(config) {
|
||||||
|
if (!config || config.mode !== "time_span") return "Interleaving: off";
|
||||||
|
const entries = Array.isArray(config.entries) ? config.entries : [];
|
||||||
|
const minuteInfo = schedulerUtcMinuteInfo();
|
||||||
|
const nowMin = minuteInfo.minuteOfDay;
|
||||||
|
const active = entries.filter(function (entry) {
|
||||||
|
return schedulerEntryIsActive(entry, nowMin);
|
||||||
|
});
|
||||||
|
if (active.length <= 1) return "Interleaving: off";
|
||||||
|
const defaultInterleave = Number(config.interleave_min);
|
||||||
|
const durations = active.map(function (entry) {
|
||||||
|
const own = Number(entry && entry.interleave_min);
|
||||||
|
if (Number.isFinite(own) && own > 0) return Math.floor(own);
|
||||||
|
if (Number.isFinite(defaultInterleave) && defaultInterleave > 0) return Math.floor(defaultInterleave);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
const cycleMin = durations.reduce(function (sum, value) { return sum + value; }, 0);
|
||||||
|
if (!(cycleMin > 0)) return "Interleaving: off";
|
||||||
|
const posMin = minuteInfo.minuteOfDay % cycleMin;
|
||||||
|
let cumulative = 0;
|
||||||
|
let currentDuration = 0;
|
||||||
|
for (let i = 0; i < durations.length; i += 1) {
|
||||||
|
cumulative += durations[i];
|
||||||
|
if (posMin < cumulative) {
|
||||||
|
currentDuration = durations[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(currentDuration > 0)) return "Interleaving: off";
|
||||||
|
const remainingMin = cumulative - posMin;
|
||||||
|
const remainingSec = Math.max(1, (remainingMin * 60) - minuteInfo.secondOfMinute);
|
||||||
|
return "Interleaving: next switch in " + remainingSec + "s (" + cycleMin + " min cycle)";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSchedulerInterleaveStatus() {
|
||||||
|
const el = document.getElementById("scheduler-cycle-status");
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = schedulerInterleaveSummary(currentConfig);
|
||||||
|
}
|
||||||
|
|
||||||
function pollStatus() {
|
function pollStatus() {
|
||||||
const rig = currentRigId;
|
const rig = currentRigId;
|
||||||
if (!rig) return;
|
if (!rig) return;
|
||||||
|
|||||||
@@ -13,18 +13,6 @@ let vchanChannels = [];
|
|||||||
let vchanActiveId = null;
|
let vchanActiveId = null;
|
||||||
let schedulerReleaseState = null;
|
let schedulerReleaseState = null;
|
||||||
let schedulerReleasePollTimer = null;
|
let schedulerReleasePollTimer = null;
|
||||||
let schedulerCycleCountdownTimer = null;
|
|
||||||
|
|
||||||
function schedulerNextCycleSeconds() {
|
|
||||||
const periodMs = 30000;
|
|
||||||
const remMs = periodMs - (Date.now() % periodMs);
|
|
||||||
const secs = Math.ceil(remMs / 1000);
|
|
||||||
return Math.max(1, Math.min(30, secs));
|
|
||||||
}
|
|
||||||
|
|
||||||
function schedulerCycleSummaryText() {
|
|
||||||
return `Next scheduler cycle in ${schedulerNextCycleSeconds()}s.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function vchanFmtFreq(hz) {
|
function vchanFmtFreq(hz) {
|
||||||
if (!Number.isFinite(hz) || hz <= 0) return "--";
|
if (!Number.isFinite(hz) || hz <= 0) return "--";
|
||||||
@@ -59,14 +47,12 @@ function schedulerReleaseSummaryText(state) {
|
|||||||
function vchanRenderSchedulerRelease() {
|
function vchanRenderSchedulerRelease() {
|
||||||
const btn = document.getElementById("scheduler-release-btn");
|
const btn = document.getElementById("scheduler-release-btn");
|
||||||
const status = document.getElementById("scheduler-release-status");
|
const status = document.getElementById("scheduler-release-status");
|
||||||
const cycleStatus = document.getElementById("scheduler-cycle-status");
|
|
||||||
if (!btn || !status) return;
|
if (!btn || !status) return;
|
||||||
const currentReleased = !!(schedulerReleaseState && schedulerReleaseState.current_session_released);
|
const currentReleased = !!(schedulerReleaseState && schedulerReleaseState.current_session_released);
|
||||||
btn.disabled = !vchanSessionId || currentReleased;
|
btn.disabled = !vchanSessionId || currentReleased;
|
||||||
btn.classList.toggle("active", !currentReleased);
|
btn.classList.toggle("active", !currentReleased);
|
||||||
btn.textContent = "Release to Scheduler";
|
btn.textContent = "Release to Scheduler";
|
||||||
status.textContent = schedulerReleaseSummaryText(schedulerReleaseState);
|
status.textContent = schedulerReleaseSummaryText(schedulerReleaseState);
|
||||||
if (cycleStatus) cycleStatus.textContent = schedulerCycleSummaryText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function vchanPollSchedulerRelease() {
|
async function vchanPollSchedulerRelease() {
|
||||||
@@ -90,10 +76,6 @@ function vchanStartSchedulerReleasePolling() {
|
|||||||
clearInterval(schedulerReleasePollTimer);
|
clearInterval(schedulerReleasePollTimer);
|
||||||
}
|
}
|
||||||
schedulerReleasePollTimer = setInterval(vchanPollSchedulerRelease, 10000);
|
schedulerReleasePollTimer = setInterval(vchanPollSchedulerRelease, 10000);
|
||||||
if (schedulerCycleCountdownTimer) {
|
|
||||||
clearInterval(schedulerCycleCountdownTimer);
|
|
||||||
}
|
|
||||||
schedulerCycleCountdownTimer = setInterval(vchanRenderSchedulerRelease, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function vchanToggleSchedulerRelease() {
|
async function vchanToggleSchedulerRelease() {
|
||||||
|
|||||||
Reference in New Issue
Block a user