Stage dashboard startup requests

This commit is contained in:
James Smith
2026-03-18 23:53:54 +00:00
parent 03a0f13772
commit ab3c581ab0
2 changed files with 178 additions and 143 deletions
+139 -128
View File
@@ -628,6 +628,33 @@
const defaultLon = window.INTERCEPT_DEFAULT_LON || -0.1278; const defaultLon = window.INTERCEPT_DEFAULT_LON || -0.1278;
return { lat: defaultLat, lon: defaultLon }; return { lat: defaultLat, lon: defaultLon };
})(); })();
const ADSB_FETCH_TIMEOUT_MS = 8000;
function scheduleAuxDashboardInit(fn, delay = 1200) {
const run = () => setTimeout(fn, delay);
if (typeof window.requestIdleCallback === 'function') {
window.requestIdleCallback(run, { timeout: delay + 1200 });
} else {
run();
}
}
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = ADSB_FETCH_TIMEOUT_MS) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
credentials: 'same-origin',
...options,
signal: controller.signal,
});
const contentType = response.headers.get('Content-Type') || '';
if (!contentType.includes('application/json')) {
throw new Error(`Unexpected response from ${url}`);
}
return await response.json();
} finally {
clearTimeout(timeout);
}
}
let rangeRingsLayer = null; let rangeRingsLayer = null;
let observerMarker = null; let observerMarker = null;
@@ -1585,8 +1612,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
// ============================================ // ============================================
async function autoConnectGps() { async function autoConnectGps() {
try { try {
const response = await fetch('/gps/auto-connect', { method: 'POST' }); const data = await fetchJsonWithTimeout('/gps/auto-connect', { method: 'POST' });
const data = await response.json();
if (data.status === 'connected') { if (data.status === 'connected') {
gpsConnected = true; gpsConnected = true;
@@ -1737,15 +1763,14 @@ ACARS: ${r.statistics.acarsMessages} messages`;
updateClock(); updateClock();
setInterval(updateClock, 1000); setInterval(updateClock, 1000);
setInterval(cleanupOldAircraft, 10000); setInterval(cleanupOldAircraft, 10000);
checkAdsbTools();
checkAircraftDatabase();
checkDvbDriverConflict();
// Auto-connect to gpsd if available
autoConnectGps();
// Sync tracking state if ADS-B already running // Sync tracking state if ADS-B already running
syncTrackingStatus(); syncTrackingStatus();
scheduleAuxDashboardInit(() => checkAdsbTools(), 500);
scheduleAuxDashboardInit(() => checkAircraftDatabase(), 800);
scheduleAuxDashboardInit(() => checkDvbDriverConflict(), 1100);
scheduleAuxDashboardInit(() => autoConnectGps(), 1400);
}); });
// Track which device is being used for ADS-B tracking // Track which device is being used for ADS-B tracking
@@ -1753,8 +1778,7 @@ ACARS: ${r.statistics.acarsMessages} messages`;
function initDeviceSelectors() { function initDeviceSelectors() {
// Populate both ADS-B and airband device selectors // Populate both ADS-B and airband device selectors
fetch('/devices') fetchJsonWithTimeout('/devices')
.then(r => r.json())
.then(devices => { .then(devices => {
const adsbSelect = document.getElementById('adsbDeviceSelect'); const adsbSelect = document.getElementById('adsbDeviceSelect');
const airbandSelect = document.getElementById('airbandDeviceSelect'); const airbandSelect = document.getElementById('airbandDeviceSelect');
@@ -2405,18 +2429,7 @@ sudo make install</code>
} }
try { try {
const response = await fetch('/adsb/session'); const data = await fetchJsonWithTimeout('/adsb/session');
if (!response.ok) {
// No session info - only auto-start if enabled
if (window.INTERCEPT_ADSB_AUTO_START) {
console.log('[ADS-B] No session found, attempting auto-start...');
await tryAutoStartLocal();
} else {
console.log('[ADS-B] No session found; auto-start disabled');
}
return;
}
const data = await response.json();
if (data.tracking_active) { if (data.tracking_active) {
// Session is running - auto-connect to stream // Session is running - auto-connect to stream
@@ -2479,10 +2492,7 @@ sudo make install</code>
// Try to auto-start local ADS-B tracking if SDR is available // Try to auto-start local ADS-B tracking if SDR is available
try { try {
// Check if any SDR devices are available // Check if any SDR devices are available
const devResponse = await fetch('/devices'); const devices = await fetchJsonWithTimeout('/devices');
if (!devResponse.ok) return;
const devices = await devResponse.json();
if (!devices || devices.length === 0) { if (!devices || devices.length === 0) {
console.log('[ADS-B] No SDR devices found - cannot auto-start'); console.log('[ADS-B] No SDR devices found - cannot auto-start');
return; return;
@@ -3842,7 +3852,9 @@ sudo make install</code>
} }
// Initialize airband on page load // Initialize airband on page load
document.addEventListener('DOMContentLoaded', initAirband); document.addEventListener('DOMContentLoaded', () => {
scheduleAuxDashboardInit(initAirband, 1200);
});
// ============================================ // ============================================
// ACARS Functions // ACARS Functions
@@ -3876,38 +3888,35 @@ sudo make install</code>
// Initialize ACARS sidebar state and frequency checkboxes // Initialize ACARS sidebar state and frequency checkboxes
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('acarsSidebar'); scheduleAuxDashboardInit(() => {
if (sidebar && acarsSidebarCollapsed) { const sidebar = document.getElementById('acarsSidebar');
sidebar.classList.add('collapsed'); if (sidebar && acarsSidebarCollapsed) {
} sidebar.classList.add('collapsed');
updateAcarsFreqCheckboxes(); }
updateAcarsFreqCheckboxes();
// Check if ACARS is already running (e.g. after page reload) fetchJsonWithTimeout('/acars/status')
fetch('/acars/status') .then(data => {
.then(r => r.json()) if (data.running) {
.then(data => { isAcarsRunning = true;
if (data.running) { acarsMessageCount = data.message_count || 0;
isAcarsRunning = true; document.getElementById('acarsCount').textContent = acarsMessageCount;
acarsMessageCount = data.message_count || 0; document.getElementById('acarsToggleBtn').textContent = '■ STOP ACARS';
document.getElementById('acarsCount').textContent = acarsMessageCount; document.getElementById('acarsToggleBtn').classList.add('active');
document.getElementById('acarsToggleBtn').textContent = '■ STOP ACARS'; document.getElementById('acarsPanelIndicator').classList.add('active');
document.getElementById('acarsToggleBtn').classList.add('active'); startAcarsStream(false);
document.getElementById('acarsPanelIndicator').classList.add('active'); fetchJsonWithTimeout('/acars/messages?limit=50')
startAcarsStream(false); .then(msgs => {
// Reload message history from backend if (!msgs || !msgs.length) return;
fetch('/acars/messages?limit=50') for (let i = msgs.length - 1; i >= 0; i--) {
.then(r => r.json()) addAcarsMessage(msgs[i]);
.then(msgs => { }
if (!msgs || !msgs.length) return; })
// Add oldest first so newest ends up on top .catch(() => {});
for (let i = msgs.length - 1; i >= 0; i--) { }
addAcarsMessage(msgs[i]); })
} .catch(() => {});
}) }, 1600);
.catch(() => {});
}
})
.catch(() => {});
}); });
function updateAcarsFreqCheckboxes() { function updateAcarsFreqCheckboxes() {
@@ -4297,26 +4306,28 @@ sudo make install</code>
// Populate ACARS device selector // Populate ACARS device selector
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
fetch('/devices') scheduleAuxDashboardInit(() => {
.then(r => r.json()) fetchJsonWithTimeout('/devices')
.then(devices => { .then(devices => {
const select = document.getElementById('acarsDeviceSelect'); const select = document.getElementById('acarsDeviceSelect');
select.innerHTML = ''; select.innerHTML = '';
if (devices.length === 0) { if (devices.length === 0) {
select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>'; select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>';
} else { } else {
devices.forEach((d, i) => { devices.forEach((d, i) => {
const opt = document.createElement('option'); const opt = document.createElement('option');
const sdrType = d.sdr_type || 'rtlsdr'; const sdrType = d.sdr_type || 'rtlsdr';
const idx = d.index !== undefined ? d.index : i; const idx = d.index !== undefined ? d.index : i;
opt.value = `${sdrType}:${idx}`; opt.value = `${sdrType}:${idx}`;
opt.dataset.sdrType = sdrType; opt.dataset.sdrType = sdrType;
opt.dataset.index = idx; opt.dataset.index = idx;
opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`; opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`;
select.appendChild(opt); select.appendChild(opt);
}); });
} }
}); })
.catch(() => {});
}, 1800);
}); });
// ============================================ // ============================================
@@ -4357,11 +4368,13 @@ sudo make install</code>
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('vdl2Sidebar'); scheduleAuxDashboardInit(() => {
if (sidebar && vdl2SidebarCollapsed) { const sidebar = document.getElementById('vdl2Sidebar');
sidebar.classList.add('collapsed'); if (sidebar && vdl2SidebarCollapsed) {
} sidebar.classList.add('collapsed');
updateVdl2FreqCheckboxes(); }
updateVdl2FreqCheckboxes();
}, 1800);
}); });
function updateVdl2FreqCheckboxes() { function updateVdl2FreqCheckboxes() {
@@ -4846,52 +4859,50 @@ sudo make install</code>
// Populate VDL2 device selector and check running status // Populate VDL2 device selector and check running status
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
fetch('/devices') scheduleAuxDashboardInit(() => {
.then(r => r.json()) fetchJsonWithTimeout('/devices')
.then(devices => { .then(devices => {
const select = document.getElementById('vdl2DeviceSelect'); const select = document.getElementById('vdl2DeviceSelect');
select.innerHTML = ''; select.innerHTML = '';
if (devices.length === 0) { if (devices.length === 0) {
select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>'; select.innerHTML = '<option value="rtlsdr:0">No SDR detected</option>';
} else { } else {
devices.forEach((d, i) => { devices.forEach((d, i) => {
const opt = document.createElement('option'); const opt = document.createElement('option');
const sdrType = d.sdr_type || 'rtlsdr'; const sdrType = d.sdr_type || 'rtlsdr';
const idx = d.index !== undefined ? d.index : i; const idx = d.index !== undefined ? d.index : i;
opt.value = `${sdrType}:${idx}`; opt.value = `${sdrType}:${idx}`;
opt.dataset.sdrType = sdrType; opt.dataset.sdrType = sdrType;
opt.dataset.index = idx; opt.dataset.index = idx;
opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`; opt.textContent = `SDR ${idx}: ${d.name || d.type || 'SDR'}`;
select.appendChild(opt); select.appendChild(opt);
}); });
} }
}); })
.catch(() => {});
// Check if VDL2 is already running (e.g. after page reload) fetchJsonWithTimeout('/vdl2/status')
fetch('/vdl2/status') .then(data => {
.then(r => r.json()) if (data.running) {
.then(data => { isVdl2Running = true;
if (data.running) { vdl2MessageCount = data.message_count || 0;
isVdl2Running = true; document.getElementById('vdl2Count').textContent = vdl2MessageCount;
vdl2MessageCount = data.message_count || 0; document.getElementById('vdl2ToggleBtn').innerHTML = '&#9632; STOP VDL2';
document.getElementById('vdl2Count').textContent = vdl2MessageCount; document.getElementById('vdl2ToggleBtn').classList.add('active');
document.getElementById('vdl2ToggleBtn').innerHTML = '&#9632; STOP VDL2'; document.getElementById('vdl2PanelIndicator').classList.add('active');
document.getElementById('vdl2ToggleBtn').classList.add('active'); startVdl2Stream(false);
document.getElementById('vdl2PanelIndicator').classList.add('active'); fetchJsonWithTimeout('/vdl2/messages?limit=50')
startVdl2Stream(false); .then(msgs => {
// Reload message history from backend if (!msgs || !msgs.length) return;
fetch('/vdl2/messages?limit=50') for (let i = msgs.length - 1; i >= 0; i--) {
.then(r => r.json()) addVdl2Message(msgs[i]);
.then(msgs => { }
if (!msgs || !msgs.length) return; })
for (let i = msgs.length - 1; i >= 0; i--) { .catch(() => {});
addVdl2Message(msgs[i]); }
} })
}) .catch(() => {});
.catch(() => {}); }, 2000);
}
})
.catch(() => {});
}); });
// ============================================ // ============================================
+39 -15
View File
@@ -603,6 +603,7 @@
let _telemetryPollTimer = null; let _telemetryPollTimer = null;
let _passRequestId = 0; let _passRequestId = 0;
const DASHBOARD_FETCH_TIMEOUT_MS = 10000; const DASHBOARD_FETCH_TIMEOUT_MS = 10000;
const DASHBOARD_AUX_INIT_DELAY_MS = 1200;
const BUILTIN_TX_FALLBACK = { const BUILTIN_TX_FALLBACK = {
25544: [ 25544: [
{ description: 'APRS digipeater', downlink_low: 145.825, downlink_high: 145.825, uplink_low: null, uplink_high: null, mode: 'FM AX.25', baud: 1200, status: 'active', type: 'beacon', service: 'Packet' }, { description: 'APRS digipeater', downlink_low: 145.825, downlink_high: 145.825, uplink_low: null, uplink_high: null, mode: 'FM AX.25', baud: 1200, status: 'active', type: 'beacon', service: 'Packet' },
@@ -624,6 +625,34 @@
const satColors = ['#00ffff', '#9370DB', '#ff00ff', '#00ff00', '#ff6600', '#ffff00', '#ff69b4', '#7b68ee']; const satColors = ['#00ffff', '#9370DB', '#ff00ff', '#00ff00', '#ff6600', '#ffff00', '#ff69b4', '#7b68ee'];
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = DASHBOARD_FETCH_TIMEOUT_MS) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
credentials: 'same-origin',
...options,
signal: controller.signal,
});
const contentType = response.headers.get('Content-Type') || '';
if (!contentType.includes('application/json')) {
throw new Error(`Unexpected response from ${url}`);
}
return await response.json();
} finally {
clearTimeout(timeout);
}
}
function scheduleAuxInit(fn, delay = DASHBOARD_AUX_INIT_DELAY_MS) {
const run = () => setTimeout(fn, delay);
if (typeof window.requestIdleCallback === 'function') {
window.requestIdleCallback(run, { timeout: delay + 1000 });
} else {
run();
}
}
function loadDashboardSatellites() { function loadDashboardSatellites() {
const btn = document.getElementById('satRefreshBtn'); const btn = document.getElementById('satRefreshBtn');
if (btn) { if (btn) {
@@ -631,8 +660,7 @@
void btn.offsetWidth; // force reflow to restart animation void btn.offsetWidth; // force reflow to restart animation
btn.classList.add('spinning'); btn.classList.add('spinning');
} }
fetch('/satellite/tracked?enabled=true', { credentials: 'same-origin' }) fetchJsonWithTimeout('/satellite/tracked?enabled=true')
.then(r => r.json())
.then(data => { .then(data => {
const prevSelected = selectedSatellite; const prevSelected = selectedSatellite;
const newSats = { const newSats = {
@@ -926,18 +954,20 @@
startSSETracking(); startSSETracking();
} }
startTelemetryPolling(); startTelemetryPolling();
loadAgents();
loadTransmitters(selectedSatellite); loadTransmitters(selectedSatellite);
fetchCurrentTelemetry(); fetchCurrentTelemetry();
if (!usedShared) { if (!usedShared) {
getLocation(); getLocation();
} }
scheduleAuxInit(() => {
loadAgents();
if (window.gsInit) window.gsInit();
});
}); });
async function loadAgents() { async function loadAgents() {
try { try {
const response = await fetch('/controller/agents'); const data = await fetchJsonWithTimeout('/controller/agents');
const data = await response.json();
if (data.status === 'success' && data.agents) { if (data.status === 'success' && data.agents) {
agents = data.agents; agents = data.agents;
populateLocationSelector(); populateLocationSelector();
@@ -1818,14 +1848,14 @@
gsLoadRecordings(); gsLoadRecordings();
gsConnectSSE(); gsConnectSSE();
} }
window.gsInit = gsInit;
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Scheduler status // Scheduler status
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
function gsLoadStatus() { function gsLoadStatus() {
fetch('/ground_station/scheduler/status') fetchJsonWithTimeout('/ground_station/scheduler/status')
.then(r => r.json())
.then(data => { _gsEnabled = data.enabled; _applyStatus(data); }) .then(data => { _gsEnabled = data.enabled; _applyStatus(data); })
.catch(() => {}); .catch(() => {});
} }
@@ -1865,8 +1895,7 @@
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
function gsLoadProfiles() { function gsLoadProfiles() {
fetch('/ground_station/profiles') fetchJsonWithTimeout('/ground_station/profiles')
.then(r => r.json())
.then(profiles => _renderProfiles(profiles)) .then(profiles => _renderProfiles(profiles))
.catch(() => { _renderProfiles([]); }); .catch(() => { _renderProfiles([]); });
} }
@@ -2340,12 +2369,7 @@
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
} }
// Init after DOM is ready // Init is scheduled by the main dashboard boot once the primary UI is live.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', gsInit);
} else {
gsInit();
}
})(); })();
</script> </script>
</body> </body>