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 ec2bc11..7af7812 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 @@ -1393,6 +1393,7 @@ + diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat-scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat-scheduler.js new file mode 100644 index 0000000..e6555a5 --- /dev/null +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat-scheduler.js @@ -0,0 +1,308 @@ +// SPDX-FileCopyrightText: 2026 Stan Grams +// +// SPDX-License-Identifier: BSD-2-Clause + +// Satellite Pass Scheduling UI +// Manages the satellite overlay section within the background decoding scheduler. +// Communicates with scheduler.js via a thin window API for shared state access. + +(function () { + "use strict"; + + // ── DOM references (cached once) ────────────────────────────────── + const dom = { + enabled: document.getElementById("scheduler-sat-enabled"), + pretune: document.getElementById("scheduler-sat-pretune"), + body: document.getElementById("scheduler-sat-body"), + tbody: document.getElementById("scheduler-sat-tbody"), + addBtn: document.getElementById("scheduler-sat-add-btn"), + passStatus: document.getElementById("scheduler-sat-pass-status"), + formWrap: document.getElementById("sch-sat-form-wrap"), + formTitle: document.getElementById("sch-sat-form-title"), + form: document.getElementById("sch-sat-form"), + formCancel: document.getElementById("sch-sat-form-cancel"), + preset: document.getElementById("scheduler-sat-preset"), + name: document.getElementById("scheduler-sat-name"), + norad: document.getElementById("scheduler-sat-norad"), + bookmark: document.getElementById("scheduler-sat-bookmark"), + minEl: document.getElementById("scheduler-sat-min-el"), + priority: document.getElementById("scheduler-sat-priority"), + centerHz: document.getElementById("scheduler-sat-center-hz"), + }; + + // ── Local state ─────────────────────────────────────────────────── + let editIdx = null; // null = adding, number = editing + + // ── Scheduler bridge ────────────────────────────────────────────── + // These accessors call into scheduler.js via window.schedulerBridge, + // which is set up by scheduler.js after it initializes. + function getBridge() { + return window.schedulerBridge || {}; + } + + function getConfig() { + const b = getBridge(); + return typeof b.getConfig === "function" ? b.getConfig() : null; + } + + function getStatus() { + const b = getBridge(); + return typeof b.getStatus === "function" ? b.getStatus() : null; + } + + function getBookmarks() { + const b = getBridge(); + return typeof b.getBookmarks === "function" ? b.getBookmarks() : []; + } + + function bmName(id) { + const bm = getBookmarks().find(function (b) { return b.id === id; }); + return bm ? bm.name : String(id || ""); + } + + function escHtml(s) { + return String(s) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + } + + function formatFreq(hz) { + if (hz >= 1e6) return (hz / 1e6).toFixed(3) + " MHz"; + if (hz >= 1e3) return (hz / 1e3).toFixed(1) + " kHz"; + return hz + " Hz"; + } + + // ── Satellite config helpers ────────────────────────────────────── + function getSatelliteEntries() { + var config = getConfig(); + return (config && config.satellites && Array.isArray(config.satellites.entries)) + ? config.satellites.entries + : []; + } + + function ensureSatelliteConfig() { + var config = getConfig(); + if (!config) return { enabled: false, pretune_secs: 60, entries: [] }; + if (!config.satellites) config.satellites = { enabled: false, pretune_secs: 60, entries: [] }; + if (!config.satellites.entries) config.satellites.entries = []; + return config.satellites; + } + + function collectSatelliteConfig() { + var enabled = dom.enabled ? dom.enabled.checked : false; + var pretune = dom.pretune ? parseInt(dom.pretune.value, 10) : 60; + return { + enabled: enabled, + pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune, + entries: getSatelliteEntries(), + }; + } + + // ── Render: section ─────────────────────────────────────────────── + function renderSection() { + var config = getConfig(); + var satCfg = (config && config.satellites) || {}; + var enabled = !!satCfg.enabled; + + if (dom.enabled) dom.enabled.checked = enabled; + if (dom.pretune) dom.pretune.value = satCfg.pretune_secs != null ? satCfg.pretune_secs : 60; + if (dom.body) dom.body.style.display = enabled ? "" : "none"; + + renderEntries(); + renderPassStatus(); + } + + // ── Render: entries table ───────────────────────────────────────── + function renderEntries() { + if (!dom.tbody) return; + var entries = getSatelliteEntries(); + var frag = document.createDocumentFragment(); + + entries.forEach(function (entry, idx) { + var tr = document.createElement("tr"); + + var tdSat = document.createElement("td"); + tdSat.textContent = entry.satellite || ""; + tr.appendChild(tdSat); + + var tdNorad = document.createElement("td"); + tdNorad.textContent = entry.norad_id || ""; + tr.appendChild(tdNorad); + + var tdBm = document.createElement("td"); + tdBm.textContent = bmName(entry.bookmark_id); + tr.appendChild(tdBm); + + var tdEl = document.createElement("td"); + tdEl.textContent = (entry.min_elevation_deg != null ? entry.min_elevation_deg + "\u00B0" : "5\u00B0"); + tr.appendChild(tdEl); + + var tdPrio = document.createElement("td"); + tdPrio.textContent = entry.priority || 0; + tr.appendChild(tdPrio); + + var tdActions = document.createElement("td"); + + var editBtn = document.createElement("button"); + editBtn.className = "sch-write"; + editBtn.type = "button"; + editBtn.textContent = "Edit"; + editBtn.addEventListener("click", function () { + openForm(entry, idx); + }); + tdActions.appendChild(editBtn); + + var removeBtn = document.createElement("button"); + removeBtn.className = "sch-write"; + removeBtn.type = "button"; + removeBtn.textContent = "Remove"; + removeBtn.addEventListener("click", function () { + removeEntry(idx); + }); + tdActions.appendChild(removeBtn); + + tr.appendChild(tdActions); + frag.appendChild(tr); + }); + + dom.tbody.replaceChildren(frag); + } + + // ── Render: pass status ─────────────────────────────────────────── + function renderPassStatus() { + if (!dom.passStatus) return; + var entries = getSatelliteEntries(); + if (entries.length === 0) { + dom.passStatus.innerHTML = ""; + return; + } + var status = getStatus(); + if (status && status.active_satellite) { + dom.passStatus.innerHTML = + 'PASS ACTIVE: ' + + escHtml(status.active_satellite) + + ''; + } else { + dom.passStatus.innerHTML = + 'No satellite pass active. Predictions available in the SAT tab.'; + } + } + + // ── Render: bookmark dropdown ───────────────────────────────────── + function renderBookmarkSelect(selectedId) { + if (!dom.bookmark) return; + dom.bookmark.innerHTML = ''; + getBookmarks().forEach(function (bm) { + var opt = document.createElement("option"); + opt.value = bm.id; + opt.textContent = bm.name + " (" + formatFreq(bm.freq_hz) + " " + bm.mode + ")"; + if (bm.id === selectedId) opt.selected = true; + dom.bookmark.appendChild(opt); + }); + } + + // ── Entry management ────────────────────────────────────────────── + function removeEntry(idx) { + var sat = ensureSatelliteConfig(); + sat.entries.splice(idx, 1); + renderEntries(); + } + + // ── Form: open ──────────────────────────────────────────────────── + function openForm(entry, idx) { + editIdx = (idx != null) ? idx : null; + + if (dom.formTitle) dom.formTitle.textContent = entry ? "Edit Satellite" : "Add Satellite"; + if (dom.preset) dom.preset.value = ""; + if (dom.name) dom.name.value = entry ? (entry.satellite || "") : ""; + if (dom.norad) dom.norad.value = entry ? (entry.norad_id || "") : ""; + if (dom.minEl) dom.minEl.value = entry && entry.min_elevation_deg != null ? entry.min_elevation_deg : 5; + if (dom.priority) dom.priority.value = entry && entry.priority != null ? entry.priority : 0; + if (dom.centerHz) dom.centerHz.value = entry && entry.center_hz ? entry.center_hz : ""; + + renderBookmarkSelect(entry ? entry.bookmark_id : null); + + if (dom.formWrap) { + dom.formWrap.style.display = "flex"; + if (dom.name) dom.name.focus(); + } + } + + // ── Form: close ─────────────────────────────────────────────────── + function closeForm() { + if (dom.formWrap) dom.formWrap.style.display = "none"; + editIdx = null; + } + + // ── Form: submit ────────────────────────────────────────────────── + function onFormSubmit(e) { + e.preventDefault(); + + var satellite = dom.name ? dom.name.value.trim() : ""; + var noradId = dom.norad ? parseInt(dom.norad.value, 10) : NaN; + var bmId = dom.bookmark ? dom.bookmark.value : ""; + + if (!satellite) { alert("Please enter a satellite name."); return; } + if (isNaN(noradId) || noradId <= 0) { alert("Please enter a valid NORAD catalog number."); return; } + if (!bmId) { alert("Please select a bookmark."); return; } + + var minEl = dom.minEl ? parseFloat(dom.minEl.value) : 5; + var prio = dom.priority ? parseInt(dom.priority.value, 10) : 0; + var centerHzRaw = dom.centerHz ? parseInt(dom.centerHz.value, 10) : NaN; + + var sat = ensureSatelliteConfig(); + + var entryData = { + satellite: satellite, + norad_id: noradId, + bookmark_id: bmId, + min_elevation_deg: isNaN(minEl) ? 5 : minEl, + priority: isNaN(prio) ? 0 : prio, + center_hz: !isNaN(centerHzRaw) && centerHzRaw > 0 ? centerHzRaw : null, + bookmark_ids: [], + }; + + if (editIdx !== null) { + var existing = sat.entries[editIdx]; + entryData.id = existing ? existing.id : ("sat_" + Date.now().toString(36)); + sat.entries[editIdx] = entryData; + } else { + entryData.id = "sat_" + Date.now().toString(36); + sat.entries.push(entryData); + } + + closeForm(); + renderEntries(); + } + + // ── Preset change handler ───────────────────────────────────────── + function onPresetChange() { + if (!dom.preset || !dom.preset.value) return; + var parts = dom.preset.value.split("|"); + if (dom.name) dom.name.value = parts[0] || ""; + if (dom.norad) dom.norad.value = parts[1] || ""; + } + + // ── Wire all events ─────────────────────────────────────────────── + function wireEvents() { + if (dom.enabled) { + dom.enabled.addEventListener("change", function () { + if (dom.body) dom.body.style.display = dom.enabled.checked ? "" : "none"; + }); + } + if (dom.addBtn) dom.addBtn.addEventListener("click", function () { openForm(null, null); }); + if (dom.form) dom.form.addEventListener("submit", onFormSubmit); + if (dom.formCancel) dom.formCancel.addEventListener("click", closeForm); + if (dom.preset) dom.preset.addEventListener("change", onPresetChange); + } + + // ── Public API ──────────────────────────────────────────────────── + window.satScheduler = { + wireEvents: wireEvents, + renderSection: renderSection, + renderPassStatus: renderPassStatus, + collectSatelliteConfig: collectSatelliteConfig, + }; +})(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js index 170c5d1..c853ac2 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js @@ -3,27 +3,46 @@ // History view: filterable table of all decoded images // Predictions view: next 24 h passes for ham satellites -// ── DOM references ────────────────────────────────────────────────── -const satStatus = document.getElementById("sat-status"); -const satLiveView = document.getElementById("sat-live-view"); -const satHistoryView = document.getElementById("sat-history-view"); -const satPredictionsView = document.getElementById("sat-predictions-view"); -const satLiveLatest = document.getElementById("sat-live-latest"); -const satHistoryList = document.getElementById("sat-history-list"); -const satHistoryCount = document.getElementById("sat-history-count"); -const satFilterInput = document.getElementById("sat-filter"); -const satSortSelect = document.getElementById("sat-sort"); -const satTypeFilter = document.getElementById("sat-type-filter"); -const satAptState = document.getElementById("sat-apt-state"); -const satLrptState = document.getElementById("sat-lrpt-state"); +// ── DOM references (cached once) ─────────────────────────────────── +const satDom = { + status: document.getElementById("sat-status"), + liveView: document.getElementById("sat-live-view"), + historyView: document.getElementById("sat-history-view"), + predictionsView: document.getElementById("sat-predictions-view"), + liveLatest: document.getElementById("sat-live-latest"), + historyList: document.getElementById("sat-history-list"), + historyCount: document.getElementById("sat-history-count"), + filterInput: document.getElementById("sat-filter"), + sortSelect: document.getElementById("sat-sort"), + typeFilter: document.getElementById("sat-type-filter"), + aptState: document.getElementById("sat-apt-state"), + lrptState: document.getElementById("sat-lrpt-state"), + viewLiveBtn: document.getElementById("sat-view-live"), + viewHistoryBtn: document.getElementById("sat-view-history"), + viewPredBtn: document.getElementById("sat-view-predictions"), + predFilter: document.getElementById("sat-pred-filter"), + predMinEl: document.getElementById("sat-pred-min-el"), + predCategory: document.getElementById("sat-pred-category"), + predCurrentList: document.getElementById("sat-pred-current-list"), + predUpcomingList: document.getElementById("sat-pred-list"), + predCurrentSec: document.getElementById("sat-pred-current-section"), + predUpcomingSec: document.getElementById("sat-pred-upcoming-section"), + predStatus: document.getElementById("sat-pred-status"), +}; // ── State ─────────────────────────────────────────────────────────── let satImageHistory = []; const SAT_MAX_IMAGES = 100; -const SAT_PRED_PAGE_SIZE = 50; // max rows before "Show more" +const SAT_PRED_PAGE_SIZE = 50; let satPredShowAll = false; let satFilterText = ""; let satActiveView = "live"; // "live" | "history" | "predictions" +let satPredData = []; +let satPredFilterText = ""; +let satPredMinEl = 0; +let satPredCategory = "all"; +let satPredSatCount = 0; +let satPredCountdownTimer = null; // ── UI scheduler helper ───────────────────────────────────────────── function scheduleSatUi(key, job) { @@ -35,23 +54,16 @@ function scheduleSatUi(key, job) { } // ── View switching ────────────────────────────────────────────────── -const satViewLiveBtn = document.getElementById("sat-view-live"); -const satViewHistoryBtn = document.getElementById("sat-view-history"); -const satViewPredictionsBtn = document.getElementById("sat-view-predictions"); - function switchSatView(view) { const leavingPredictions = satActiveView === "predictions" && view !== "predictions"; satActiveView = view; - if (satLiveView) satLiveView.style.display = view === "live" ? "" : "none"; - if (satHistoryView) satHistoryView.style.display = view === "history" ? "" : "none"; - if (satPredictionsView) satPredictionsView.style.display = view === "predictions" ? "" : "none"; - if (satViewLiveBtn) satViewLiveBtn.classList.toggle("sat-view-active", view === "live"); - if (satViewHistoryBtn) satViewHistoryBtn.classList.toggle("sat-view-active", view === "history"); - if (satViewPredictionsBtn) satViewPredictionsBtn.classList.toggle("sat-view-active", view === "predictions"); - // Clear prediction DOM when leaving to reduce node count. - if (leavingPredictions) { - clearPredictionDom(); - } + if (satDom.liveView) satDom.liveView.style.display = view === "live" ? "" : "none"; + if (satDom.historyView) satDom.historyView.style.display = view === "history" ? "" : "none"; + if (satDom.predictionsView) satDom.predictionsView.style.display = view === "predictions" ? "" : "none"; + if (satDom.viewLiveBtn) satDom.viewLiveBtn.classList.toggle("sat-view-active", view === "live"); + if (satDom.viewHistoryBtn) satDom.viewHistoryBtn.classList.toggle("sat-view-active", view === "history"); + if (satDom.viewPredBtn) satDom.viewPredBtn.classList.toggle("sat-view-active", view === "predictions"); + if (leavingPredictions) clearPredictionDom(); if (view === "history") { renderSatHistoryTable(); } else if (view === "predictions") { @@ -61,39 +73,38 @@ function switchSatView(view) { } function clearPredictionDom() { - if (satPredCountdownTimer) { clearInterval(satPredCountdownTimer); satPredCountdownTimer = null; } - if (satPredCurrentList) satPredCurrentList.innerHTML = ""; - if (satPredUpcomingList) satPredUpcomingList.innerHTML = ""; + stopCountdownTimer(); + if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; + if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; } window.clearSatPredictionDom = clearPredictionDom; -satViewLiveBtn?.addEventListener("click", () => switchSatView("live")); -satViewHistoryBtn?.addEventListener("click", () => switchSatView("history")); -satViewPredictionsBtn?.addEventListener("click", () => switchSatView("predictions")); +satDom.viewLiveBtn?.addEventListener("click", () => switchSatView("live")); +satDom.viewHistoryBtn?.addEventListener("click", () => switchSatView("history")); +satDom.viewPredBtn?.addEventListener("click", () => switchSatView("predictions")); // ── Live view: decoder state ──────────────────────────────────────── -// Updated from app.js render() via window.updateSatLiveState let _lastSatAptOn = null, _lastSatLrptOn = null; window.updateSatLiveState = function (update) { - if (!satAptState || !satLrptState) return; + if (!satDom.aptState || !satDom.lrptState) return; const aptOn = !!update.wxsat_decode_enabled; const lrptOn = !!update.lrpt_decode_enabled; if (aptOn !== _lastSatAptOn) { _lastSatAptOn = aptOn; - satAptState.textContent = aptOn ? "Listening" : "Idle"; - satAptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle"); + satDom.aptState.textContent = aptOn ? "Listening" : "Idle"; + satDom.aptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle"); } if (lrptOn !== _lastSatLrptOn) { _lastSatLrptOn = lrptOn; - satLrptState.textContent = lrptOn ? "Listening" : "Idle"; - satLrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle"); + satDom.lrptState.textContent = lrptOn ? "Listening" : "Idle"; + satDom.lrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle"); } }; function renderSatLatestCard() { - if (!satLiveLatest) return; + if (!satDom.liveLatest) return; if (satImageHistory.length === 0) { - satLiveLatest.innerHTML = + satDom.liveLatest.innerHTML = '
No images decoded yet. Enable a decoder and wait for a satellite pass.
'; return; } @@ -124,19 +135,17 @@ function renderSatLatestCard() { html += ` `; } html += ``; - satLiveLatest.innerHTML = html; + satDom.liveLatest.innerHTML = html; } // ── History view: table ───────────────────────────────────────────── function getSatFilteredHistory() { let items = satImageHistory; - // Type filter - const typeVal = satTypeFilter ? satTypeFilter.value : "all"; + const typeVal = satDom.typeFilter ? satDom.typeFilter.value : "all"; if (typeVal === "apt") items = items.filter((i) => i._decoder === "apt"); else if (typeVal === "lrpt") items = items.filter((i) => i._decoder === "lrpt"); - // Text filter if (satFilterText) { items = items.filter((i) => { const haystack = [ @@ -145,18 +154,13 @@ function getSatFilteredHistory() { i.channels || "", i.channel_a || "", i.channel_b || "", - ] - .join(" ") - .toUpperCase(); + ].join(" ").toUpperCase(); return haystack.includes(satFilterText); }); } - // Sort - const sortVal = satSortSelect ? satSortSelect.value : "newest"; - if (sortVal === "oldest") { - items = items.slice().reverse(); - } + const sortVal = satDom.sortSelect ? satDom.sortSelect.value : "newest"; + if (sortVal === "oldest") items = items.slice().reverse(); return items; } @@ -194,18 +198,18 @@ function renderSatHistoryRow(img) { } function renderSatHistoryTable() { - if (!satHistoryList) return; + if (!satDom.historyList) return; const items = getSatFilteredHistory(); const fragment = document.createDocumentFragment(); for (let i = 0; i < items.length; i += 1) { fragment.appendChild(renderSatHistoryRow(items[i])); } - satHistoryList.replaceChildren(fragment); + satDom.historyList.replaceChildren(fragment); - if (satHistoryCount) { + if (satDom.historyCount) { const total = satImageHistory.length; const shown = items.length; - satHistoryCount.textContent = + satDom.historyCount.textContent = total === 0 ? "No images yet" : shown === total @@ -238,7 +242,7 @@ function addSatImage(img, decoder) { // ── Server callbacks ──────────────────────────────────────────────── window.onServerSatImage = function (msg) { - if (satStatus) satStatus.textContent = "Image received (NOAA APT)"; + if (satDom.status) satDom.status.textContent = "Image received (NOAA APT)"; addSatImage(msg, "apt"); if (msg.geo_bounds && msg.path && window.addSatMapOverlay) { window.addSatMapOverlay(msg); @@ -246,7 +250,7 @@ window.onServerSatImage = function (msg) { }; window.onServerLrptImage = function (msg) { - if (satStatus) satStatus.textContent = "Image received (Meteor LRPT)"; + if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)"; addSatImage(msg, "lrpt"); if (msg.geo_bounds && msg.path && window.addSatMapOverlay) { window.addSatMapOverlay(msg); @@ -255,7 +259,7 @@ window.onServerLrptImage = function (msg) { window.resetSatHistoryView = function () { satImageHistory = []; - if (satHistoryList) satHistoryList.innerHTML = ""; + if (satDom.historyList) satDom.historyList.innerHTML = ""; renderSatLatestCard(); renderSatHistoryTable(); if (window.clearSatMapOverlays) window.clearSatMapOverlays(); @@ -288,13 +292,13 @@ lrptDecodeToggleBtn?.addEventListener("click", async () => { }); // ── Filter / sort event listeners ─────────────────────────────────── -satFilterInput?.addEventListener("input", () => { - satFilterText = satFilterInput.value.trim().toUpperCase(); +satDom.filterInput?.addEventListener("input", () => { + satFilterText = satDom.filterInput.value.trim().toUpperCase(); renderSatHistoryTable(); }); -satSortSelect?.addEventListener("change", () => renderSatHistoryTable()); -satTypeFilter?.addEventListener("change", () => renderSatHistoryTable()); +satDom.sortSelect?.addEventListener("change", () => renderSatHistoryTable()); +satDom.typeFilter?.addEventListener("change", () => renderSatHistoryTable()); // ── Settings: clear history ───────────────────────────────────────── document @@ -309,55 +313,7 @@ document } }); -// ── Predictions view ──────────────────────────────────────────────── -let satPredData = []; -let satPredFilterText = ""; -let satPredMinEl = 0; -let satPredCategory = "all"; -let satPredSatCount = 0; -let satPredCountdownTimer = null; -const satPredFilterInput = document.getElementById("sat-pred-filter"); -const satPredMinElSelect = document.getElementById("sat-pred-min-el"); -const satPredCategorySelect = document.getElementById("sat-pred-category"); -const satPredCurrentList = document.getElementById("sat-pred-current-list"); -const satPredUpcomingList = document.getElementById("sat-pred-list"); -const satPredCurrentSection = document.getElementById("sat-pred-current-section"); -const satPredUpcomingSection = document.getElementById("sat-pred-upcoming-section"); -const satPredStatus = document.getElementById("sat-pred-status"); - -function getFilteredPredictions() { - let items = satPredData; - if (satPredCategory !== "all") { - items = items.filter((p) => p.category === satPredCategory); - } - if (satPredMinEl > 0) { - items = items.filter((p) => p.max_elevation_deg >= satPredMinEl); - } - if (satPredFilterText) { - items = items.filter((p) => p.satellite.toUpperCase().includes(satPredFilterText)); - } - return items; -} - -function applyPredFilters() { - renderSatPredictions(getFilteredPredictions()); -} - -satPredFilterInput?.addEventListener("input", () => { - satPredFilterText = satPredFilterInput.value.trim().toUpperCase(); - applyPredFilters(); -}); - -satPredMinElSelect?.addEventListener("change", () => { - satPredMinEl = parseInt(satPredMinElSelect.value, 10) || 0; - applyPredFilters(); -}); - -satPredCategorySelect?.addEventListener("change", () => { - satPredCategory = satPredCategorySelect.value; - applyPredFilters(); -}); - +// ── Predictions: helpers ──────────────────────────────────────────── function azToCardinal(deg) { const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]; return dirs[Math.round(deg / 45) % 8]; @@ -367,9 +323,7 @@ function formatPredTime(ms) { const d = new Date(ms); const now = new Date(); const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const day = d.getUTCDay() !== now.getUTCDay() - ? dayNames[d.getUTCDay()] + " " - : ""; + const day = d.getUTCDay() !== now.getUTCDay() ? dayNames[d.getUTCDay()] + " " : ""; const hh = String(d.getUTCHours()).padStart(2, "0"); const mm = String(d.getUTCMinutes()).padStart(2, "0"); return `${day}${hh}:${mm}`; @@ -387,31 +341,126 @@ function formatCountdown(ms) { return `${m}:${String(s).padStart(2, "0")}`; } -function renderSatPredictions(passes, error) { - const currentList = satPredCurrentList; - const upcomingList = satPredUpcomingList; - const currentSection = satPredCurrentSection; - const upcomingSection = satPredUpcomingSection; - const status = satPredStatus; +function elevationClass(deg) { + if (deg >= 45) return "sat-pred-el-high"; + if (deg >= 10) return "sat-pred-el-mid"; + return "sat-pred-el-low"; +} - // Stop any previous countdown timer - if (satPredCountdownTimer) { clearInterval(satPredCountdownTimer); satPredCountdownTimer = null; } +// ── Predictions: countdown timer management ───────────────────────── +function stopCountdownTimer() { + if (satPredCountdownTimer) { + clearInterval(satPredCountdownTimer); + satPredCountdownTimer = null; + } +} + +function startCountdownTimer(container) { + const countdownEls = container ? container.querySelectorAll(".sat-pred-col-countdown") : []; + if (countdownEls.length === 0) return; + + satPredCountdownTimer = setInterval(() => { + if (satActiveView !== "predictions") { + stopCountdownTimer(); + return; + } + const n = Date.now(); + let anyActive = false; + for (const el of countdownEls) { + const los = parseInt(el.dataset.los, 10); + const rem = los - n; + if (rem > 0) { + el.textContent = formatCountdown(rem); + anyActive = true; + } else { + el.textContent = "0:00"; + } + } + if (!anyActive) { + stopCountdownTimer(); + renderSatPredictions(getFilteredPredictions()); + } + }, 1000); +} + +// ── Predictions: row builders ─────────────────────────────────────── +function buildCurrentPassRow(pass, now) { + const row = document.createElement("div"); + row.className = "sat-pred-row-current"; + const dir = `${azToCardinal(pass.azimuth_aos_deg)} \u2192 ${azToCardinal(pass.azimuth_los_deg)}`; + const remaining = Math.max(0, pass.los_ms - now); + row.innerHTML = [ + `${pass.satellite}`, + `${pass.max_elevation_deg.toFixed(1)}\u00B0`, + `${formatPredTime(pass.aos_ms)}`, + `${formatPredTime(pass.los_ms)}`, + `${formatCountdown(remaining)}`, + `${dir}`, + ].join(""); + return row; +} + +function buildUpcomingPassRow(pass) { + const row = document.createElement("div"); + row.className = "sat-pred-row"; + const dir = `${azToCardinal(pass.azimuth_aos_deg)} \u2192 ${azToCardinal(pass.azimuth_los_deg)}`; + row.innerHTML = [ + `${formatPredTime(pass.aos_ms)}`, + `${pass.satellite}`, + `${pass.max_elevation_deg.toFixed(1)}\u00B0`, + `${formatPredDuration(pass.duration_s)}`, + `${dir}`, + ].join(""); + return row; +} + +// ── Predictions: filter state ─────────────────────────────────────── +function getFilteredPredictions() { + let items = satPredData; + if (satPredCategory !== "all") items = items.filter((p) => p.category === satPredCategory); + if (satPredMinEl > 0) items = items.filter((p) => p.max_elevation_deg >= satPredMinEl); + if (satPredFilterText) items = items.filter((p) => p.satellite.toUpperCase().includes(satPredFilterText)); + return items; +} + +function applyPredFilters() { + renderSatPredictions(getFilteredPredictions()); +} + +satDom.predFilter?.addEventListener("input", () => { + satPredFilterText = satDom.predFilter.value.trim().toUpperCase(); + applyPredFilters(); +}); + +satDom.predMinEl?.addEventListener("change", () => { + satPredMinEl = parseInt(satDom.predMinEl.value, 10) || 0; + applyPredFilters(); +}); + +satDom.predCategory?.addEventListener("change", () => { + satPredCategory = satDom.predCategory.value; + applyPredFilters(); +}); + +// ── Predictions: main render ──────────────────────────────────────── +function renderSatPredictions(passes, error) { + stopCountdownTimer(); if (error) { - if (currentList) currentList.innerHTML = ""; - if (upcomingList) upcomingList.innerHTML = ""; - if (currentSection) currentSection.style.display = "none"; - if (upcomingSection) upcomingSection.style.display = "none"; - if (status) status.textContent = error; + if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; + if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; + if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none"; + if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none"; + if (satDom.predStatus) satDom.predStatus.textContent = error; return; } if (!Array.isArray(passes) || passes.length === 0) { - if (currentList) currentList.innerHTML = ""; - if (upcomingList) upcomingList.innerHTML = ""; - if (currentSection) currentSection.style.display = "none"; - if (upcomingSection) upcomingSection.style.display = "none"; - if (status) status.textContent = "No passes found in the next 24 hours."; + if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; + if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; + if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = "none"; + if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = "none"; + if (satDom.predStatus) satDom.predStatus.textContent = "No passes found in the next 24 hours."; return; } @@ -420,61 +469,25 @@ function renderSatPredictions(passes, error) { const upcoming = passes.filter((p) => p.aos_ms > now); // ── Current passes ── - if (currentSection) currentSection.style.display = current.length > 0 ? "" : "none"; - if (currentList) { + if (satDom.predCurrentSec) satDom.predCurrentSec.style.display = current.length > 0 ? "" : "none"; + if (satDom.predCurrentList) { if (current.length === 0) { - currentList.innerHTML = ""; + satDom.predCurrentList.innerHTML = ""; } else { const frag = document.createDocumentFragment(); - for (const pass of current) { - const row = document.createElement("div"); - row.className = "sat-pred-row-current"; - const elClass = pass.max_elevation_deg >= 45 - ? "sat-pred-el-high" - : pass.max_elevation_deg >= 10 - ? "sat-pred-el-mid" - : "sat-pred-el-low"; - const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; - const remaining = Math.max(0, pass.los_ms - now); - row.innerHTML = [ - `${pass.satellite}`, - `${pass.max_elevation_deg.toFixed(1)}°`, - `${formatPredTime(pass.aos_ms)}`, - `${formatPredTime(pass.los_ms)}`, - `${formatCountdown(remaining)}`, - `${dir}`, - ].join(""); - frag.appendChild(row); - } - currentList.replaceChildren(frag); + for (const pass of current) frag.appendChild(buildCurrentPassRow(pass, now)); + satDom.predCurrentList.replaceChildren(frag); } } - // ── Upcoming passes (capped to reduce DOM node count) ── + // ── Upcoming passes ── const upcomingLimit = satPredShowAll ? upcoming.length : SAT_PRED_PAGE_SIZE; const visibleUpcoming = upcoming.slice(0, upcomingLimit); const hiddenCount = upcoming.length - visibleUpcoming.length; - if (upcomingSection) upcomingSection.style.display = upcoming.length > 0 ? "" : "none"; - if (upcomingList) { + if (satDom.predUpcomingSec) satDom.predUpcomingSec.style.display = upcoming.length > 0 ? "" : "none"; + if (satDom.predUpcomingList) { const frag = document.createDocumentFragment(); - for (const pass of visibleUpcoming) { - const row = document.createElement("div"); - row.className = "sat-pred-row"; - const elClass = pass.max_elevation_deg >= 45 - ? "sat-pred-el-high" - : pass.max_elevation_deg >= 10 - ? "sat-pred-el-mid" - : "sat-pred-el-low"; - const dir = `${azToCardinal(pass.azimuth_aos_deg)} → ${azToCardinal(pass.azimuth_los_deg)}`; - row.innerHTML = [ - `${formatPredTime(pass.aos_ms)}`, - `${pass.satellite}`, - `${pass.max_elevation_deg.toFixed(1)}°`, - `${formatPredDuration(pass.duration_s)}`, - `${dir}`, - ].join(""); - frag.appendChild(row); - } + for (const pass of visibleUpcoming) frag.appendChild(buildUpcomingPassRow(pass)); if (hiddenCount > 0) { const moreRow = document.createElement("div"); moreRow.className = "sat-pred-row"; @@ -487,54 +500,27 @@ function renderSatPredictions(passes, error) { }); frag.appendChild(moreRow); } - upcomingList.replaceChildren(frag); + satDom.predUpcomingList.replaceChildren(frag); } // ── Status ── - if (status) { - let text = `${current.length} active · ${upcoming.length} upcoming · times in UTC`; - if (satPredSatCount > 0) text += ` · ${satPredSatCount} satellites tracked`; - status.textContent = text; + if (satDom.predStatus) { + let text = `${current.length} active \u00B7 ${upcoming.length} upcoming \u00B7 times in UTC`; + if (satPredSatCount > 0) text += ` \u00B7 ${satPredSatCount} satellites tracked`; + satDom.predStatus.textContent = text; } - // ── Countdown timer: update "time left" every second ── - // Only run when predictions view is actually visible. + // ── Countdown timer ── if (current.length > 0 && satActiveView === "predictions") { - const countdownEls = currentList ? currentList.querySelectorAll(".sat-pred-col-countdown") : []; - if (countdownEls.length > 0) { - satPredCountdownTimer = setInterval(() => { - // Pause timer if predictions view was hidden (e.g. switched tabs). - if (satActiveView !== "predictions") { - clearInterval(satPredCountdownTimer); - satPredCountdownTimer = null; - return; - } - const n = Date.now(); - let anyActive = false; - for (const el of countdownEls) { - const los = parseInt(el.dataset.los, 10); - const rem = los - n; - if (rem > 0) { - el.textContent = formatCountdown(rem); - anyActive = true; - } else { - el.textContent = "0:00"; - } - } - if (!anyActive) { - clearInterval(satPredCountdownTimer); - satPredCountdownTimer = null; - renderSatPredictions(getFilteredPredictions()); - } - }, 1000); - } + startCountdownTimer(satDom.predCurrentList); } } +// ── Predictions: data loading ─────────────────────────────────────── async function loadSatPredictions() { - if (satPredStatus) satPredStatus.textContent = "Loading predictions\u2026"; - if (satPredCurrentList) satPredCurrentList.innerHTML = ""; - if (satPredUpcomingList) satPredUpcomingList.innerHTML = ""; + if (satDom.predStatus) satDom.predStatus.textContent = "Loading predictions\u2026"; + if (satDom.predCurrentList) satDom.predCurrentList.innerHTML = ""; + if (satDom.predUpcomingList) satDom.predUpcomingList.innerHTML = ""; try { const resp = await fetch("/sat_passes"); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); @@ -554,11 +540,9 @@ async function loadSatPredictions() { // ── Navigate to map centered on satellite image bounds ────────────── window.satShowOnMap = function (south, west, north, east) { - // Enable sat filter if not active if (typeof window.enableMapSourceFilter === "function") { window.enableMapSourceFilter("sat"); } - // Navigate to the center of the image bounds const lat = (south + north) / 2; const lon = (west + east) / 2; if (window.navigateToAprsMap) { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js index 24bb843..fe56d43 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js @@ -19,7 +19,7 @@ let interleaveTicker = null; let schedulerStepPending = false; let schEntryEditIdx = null; // null = adding, number = editing that index - let schSatEditIdx = null; // null = adding, number = editing satellite entry + // Satellite entry editing state moved to sat-scheduler.js // ------------------------------------------------------------------------- // Init @@ -866,225 +866,30 @@ } // ------------------------------------------------------------------------- - // Satellite overlay + // Satellite overlay (delegated to sat-scheduler.js) // ------------------------------------------------------------------------- - function getSatelliteEntries() { - return (currentConfig && currentConfig.satellites && Array.isArray(currentConfig.satellites.entries)) - ? currentConfig.satellites.entries - : []; - } - - function ensureSatelliteConfig() { - if (!currentConfig) currentConfig = { remote: currentRigId, mode: "disabled", entries: [] }; - if (!currentConfig.satellites) currentConfig.satellites = { enabled: false, pretune_secs: 60, entries: [] }; - if (!currentConfig.satellites.entries) currentConfig.satellites.entries = []; - return currentConfig.satellites; - } - - function collectSatelliteConfig() { - const enabledEl = document.getElementById("scheduler-sat-enabled"); - const pretuneEl = document.getElementById("scheduler-sat-pretune"); - const enabled = enabledEl ? enabledEl.checked : false; - const pretune = pretuneEl ? parseInt(pretuneEl.value, 10) : 60; - return { - enabled: enabled, - pretune_secs: isNaN(pretune) || pretune < 0 ? 60 : pretune, - entries: getSatelliteEntries(), - }; - } - function renderSatelliteSection() { - const satCfg = (currentConfig && currentConfig.satellites) || {}; - const enabled = !!satCfg.enabled; - const enabledEl = document.getElementById("scheduler-sat-enabled"); - if (enabledEl) enabledEl.checked = enabled; - - const pretuneEl = document.getElementById("scheduler-sat-pretune"); - if (pretuneEl) pretuneEl.value = satCfg.pretune_secs != null ? satCfg.pretune_secs : 60; - - const bodyEl = document.getElementById("scheduler-sat-body"); - if (bodyEl) bodyEl.style.display = enabled ? "" : "none"; - - renderSatelliteEntries(); - renderSatPassStatus(); - } - - function renderSatelliteEntries() { - const tbody = document.getElementById("scheduler-sat-tbody"); - if (!tbody) return; - tbody.innerHTML = ""; - const entries = getSatelliteEntries(); - entries.forEach(function (entry, idx) { - const tr = document.createElement("tr"); - tr.innerHTML = - "" + escHtml(entry.satellite || "") + "" + - "" + (entry.norad_id || "") + "" + - "" + escHtml(bmName(entry.bookmark_id)) + "" + - "" + (entry.min_elevation_deg != null ? entry.min_elevation_deg + "\u00B0" : "5\u00B0") + "" + - "" + (entry.priority || 0) + "" + - '' + - '' + - '' + - ''; - tbody.appendChild(tr); - }); - tbody.querySelectorAll(".sch-sat-edit-btn").forEach(function (btn) { - btn.addEventListener("click", function () { - const i = parseInt(btn.dataset.idx, 10); - const entry = getSatelliteEntries()[i]; - if (entry) schOpenSatForm(entry, i); - }); - }); - tbody.querySelectorAll(".sch-sat-remove-btn").forEach(function (btn) { - btn.addEventListener("click", function () { - removeSatEntry(parseInt(btn.dataset.idx, 10)); - }); - }); - } - - function removeSatEntry(idx) { - const sat = ensureSatelliteConfig(); - sat.entries.splice(idx, 1); - renderSatelliteEntries(); - } - - function schOpenSatForm(entry, idx) { - schSatEditIdx = (idx != null) ? idx : null; - - const titleEl = document.getElementById("sch-sat-form-title"); - if (titleEl) titleEl.textContent = entry ? "Edit Satellite" : "Add Satellite"; - - const presetEl = document.getElementById("scheduler-sat-preset"); - const nameEl = document.getElementById("scheduler-sat-name"); - const noradEl = document.getElementById("scheduler-sat-norad"); - const bmEl = document.getElementById("scheduler-sat-bookmark"); - const minElEl = document.getElementById("scheduler-sat-min-el"); - const prioEl = document.getElementById("scheduler-sat-priority"); - const centerHzEl = document.getElementById("scheduler-sat-center-hz"); - - if (presetEl) presetEl.value = ""; - if (nameEl) nameEl.value = entry ? (entry.satellite || "") : ""; - if (noradEl) noradEl.value = entry ? (entry.norad_id || "") : ""; - if (bmEl) bmEl.value = entry ? (entry.bookmark_id || "") : ""; - if (minElEl) minElEl.value = entry && entry.min_elevation_deg != null ? entry.min_elevation_deg : 5; - if (prioEl) prioEl.value = entry && entry.priority != null ? entry.priority : 0; - if (centerHzEl) centerHzEl.value = entry && entry.center_hz ? entry.center_hz : ""; - - // Populate bookmark dropdown - renderBookmarkSelect("scheduler-sat-bookmark", entry ? entry.bookmark_id : null); - - const wrap = document.getElementById("sch-sat-form-wrap"); - if (wrap) { - wrap.style.display = "flex"; - if (nameEl) nameEl.focus(); - } - } - - function schCloseSatForm() { - const wrap = document.getElementById("sch-sat-form-wrap"); - if (wrap) wrap.style.display = "none"; - schSatEditIdx = null; - } - - function schSatFormSubmit(e) { - e.preventDefault(); - - const nameEl = document.getElementById("scheduler-sat-name"); - const noradEl = document.getElementById("scheduler-sat-norad"); - const bmEl = document.getElementById("scheduler-sat-bookmark"); - const minElEl = document.getElementById("scheduler-sat-min-el"); - const prioEl = document.getElementById("scheduler-sat-priority"); - const centerHzEl = document.getElementById("scheduler-sat-center-hz"); - - const satellite = nameEl ? nameEl.value.trim() : ""; - const noradId = noradEl ? parseInt(noradEl.value, 10) : NaN; - const bmId = bmEl ? bmEl.value : ""; - - if (!satellite) { alert("Please enter a satellite name."); return; } - if (isNaN(noradId) || noradId <= 0) { alert("Please enter a valid NORAD catalog number."); return; } - if (!bmId) { alert("Please select a bookmark."); return; } - - const minEl = minElEl ? parseFloat(minElEl.value) : 5; - const prio = prioEl ? parseInt(prioEl.value, 10) : 0; - const centerHzRaw = centerHzEl ? parseInt(centerHzEl.value, 10) : NaN; - - const sat = ensureSatelliteConfig(); - - const entryData = { - satellite: satellite, - norad_id: noradId, - bookmark_id: bmId, - min_elevation_deg: isNaN(minEl) ? 5 : minEl, - priority: isNaN(prio) ? 0 : prio, - center_hz: !isNaN(centerHzRaw) && centerHzRaw > 0 ? centerHzRaw : null, - bookmark_ids: [], - }; - - if (schSatEditIdx !== null) { - const existing = sat.entries[schSatEditIdx]; - entryData.id = existing ? existing.id : ("sat_" + Date.now().toString(36)); - sat.entries[schSatEditIdx] = entryData; - } else { - entryData.id = "sat_" + Date.now().toString(36); - sat.entries.push(entryData); - } - - schCloseSatForm(); - renderSatelliteEntries(); - } - - function wireSatPresetChange() { - const presetEl = document.getElementById("scheduler-sat-preset"); - if (!presetEl || presetEl._wired) return; - presetEl._wired = true; - presetEl.addEventListener("change", function () { - if (!presetEl.value) return; - const parts = presetEl.value.split("|"); - const nameEl = document.getElementById("scheduler-sat-name"); - const noradEl = document.getElementById("scheduler-sat-norad"); - if (nameEl) nameEl.value = parts[0] || ""; - if (noradEl) noradEl.value = parts[1] || ""; - }); + if (window.satScheduler) window.satScheduler.renderSection(); } function renderSatPassStatus() { - const el = document.getElementById("scheduler-sat-pass-status"); - if (!el) return; - const entries = getSatelliteEntries(); - if (entries.length === 0) { - el.innerHTML = ""; - return; - } - // Show active satellite from status if available. - if (currentSchedulerStatus && currentSchedulerStatus.active_satellite) { - el.innerHTML = - 'PASS ACTIVE: ' + - escHtml(currentSchedulerStatus.active_satellite) + - ''; - } else { - el.innerHTML = 'No satellite pass active. Predictions available in the SAT tab.'; - } + if (window.satScheduler) window.satScheduler.renderPassStatus(); + } + + function collectSatelliteConfig() { + return window.satScheduler + ? window.satScheduler.collectSatelliteConfig() + : { enabled: false, pretune_secs: 60, entries: [] }; } function wireSatelliteEvents() { - const enabledEl = document.getElementById("scheduler-sat-enabled"); - if (enabledEl) { - enabledEl.addEventListener("change", function () { - const bodyEl = document.getElementById("scheduler-sat-body"); - if (bodyEl) bodyEl.style.display = enabledEl.checked ? "" : "none"; - }); - } - - const addBtn = document.getElementById("scheduler-sat-add-btn"); - if (addBtn) addBtn.addEventListener("click", function () { schOpenSatForm(null, null); }); - - const satForm = document.getElementById("sch-sat-form"); - if (satForm) satForm.addEventListener("submit", schSatFormSubmit); - - const cancelBtn = document.getElementById("sch-sat-form-cancel"); - if (cancelBtn) cancelBtn.addEventListener("click", schCloseSatForm); - - wireSatPresetChange(); + // Expose bridge for sat-scheduler.js to access shared state. + window.schedulerBridge = { + getConfig: function () { return currentConfig; }, + getStatus: function () { return currentSchedulerStatus; }, + getBookmarks: function () { return bookmarkList; }, + }; + if (window.satScheduler) window.satScheduler.wireEvents(); } // ------------------------------------------------------------------------- diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs index a738066..575595e 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs @@ -1689,6 +1689,7 @@ define_gz_cache!(gz_cw_js, status::CW_JS, "cw.js"); define_gz_cache!(gz_sat_js, status::SAT_JS, "sat.js"); define_gz_cache!(gz_bookmarks_js, status::BOOKMARKS_JS, "bookmarks.js"); define_gz_cache!(gz_scheduler_js, status::SCHEDULER_JS, "scheduler.js"); +define_gz_cache!(gz_sat_scheduler_js, status::SAT_SCHEDULER_JS, "sat-scheduler.js"); define_gz_cache!(gz_background_decode_js, status::BACKGROUND_DECODE_JS, "background-decode.js"); define_gz_cache!(gz_vchan_js, status::VCHAN_JS, "vchan.js"); @@ -2249,6 +2250,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(sat_js) .service(bookmarks_js) .service(scheduler_js) + .service(sat_scheduler_js) .service(background_decode_js) .service(vchan_js) // Virtual channels @@ -2421,6 +2423,12 @@ async fn scheduler_js(req: HttpRequest) -> impl Responder { static_asset_response(&req, "application/javascript; charset=utf-8", &c.gz, &c.etag) } +#[get("/sat-scheduler.js")] +async fn sat_scheduler_js(req: HttpRequest) -> impl Responder { + let c = gz_sat_scheduler_js(); + static_asset_response(&req, "application/javascript; charset=utf-8", &c.gz, &c.etag) +} + #[get("/background-decode.js")] async fn background_decode_js(req: HttpRequest) -> impl Responder { let c = gz_background_decode_js(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/status.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/status.rs index 6bd906c..c02c3c9 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/status.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/status.rs @@ -27,6 +27,7 @@ pub const CW_JS: &str = include_str!("../assets/web/plugins/cw.js"); pub const SAT_JS: &str = include_str!("../assets/web/plugins/sat.js"); pub const BOOKMARKS_JS: &str = include_str!("../assets/web/plugins/bookmarks.js"); pub const SCHEDULER_JS: &str = include_str!("../assets/web/plugins/scheduler.js"); +pub const SAT_SCHEDULER_JS: &str = include_str!("../assets/web/plugins/sat-scheduler.js"); pub const BACKGROUND_DECODE_JS: &str = include_str!("../assets/web/plugins/background-decode.js"); pub const VCHAN_JS: &str = include_str!("../assets/web/plugins/vchan.js");