diff --git a/templates/index.html b/templates/index.html
index a509e2a..1974eb6 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -4039,6 +4039,43 @@
'/satellite/dashboard',
]);
+ // Shared module destroy map — closes SSE EventSources, timers, etc.
+ // Used by both switchMode() and dashboard navigation cleanup.
+ function getModuleDestroyFn(mode) {
+ const moduleDestroyMap = {
+ subghz: () => typeof SubGhz !== 'undefined' && SubGhz.destroy(),
+ morse: () => typeof MorseMode !== 'undefined' && MorseMode.destroy?.(),
+ spaceweather: () => typeof SpaceWeather !== 'undefined' && SpaceWeather.destroy?.(),
+ weathersat: () => typeof WeatherSat !== 'undefined' && WeatherSat.suspend?.(),
+ wefax: () => typeof WeFax !== 'undefined' && WeFax.destroy?.(),
+ system: () => typeof SystemHealth !== 'undefined' && SystemHealth.destroy?.(),
+ waterfall: () => typeof Waterfall !== 'undefined' && Waterfall.destroy?.(),
+ gps: () => typeof GPS !== 'undefined' && GPS.destroy?.(),
+ meshtastic: () => typeof Meshtastic !== 'undefined' && Meshtastic.destroy?.(),
+ bluetooth: () => typeof BluetoothMode !== 'undefined' && BluetoothMode.destroy?.(),
+ wifi: () => typeof WiFiMode !== 'undefined' && WiFiMode.destroy?.(),
+ bt_locate: () => typeof BtLocate !== 'undefined' && BtLocate.destroy?.(),
+ sstv: () => typeof SSTV !== 'undefined' && SSTV.destroy?.(),
+ sstv_general: () => typeof SSTVGeneral !== 'undefined' && SSTVGeneral.destroy?.(),
+ websdr: () => typeof WebSDR !== 'undefined' && WebSDR.destroy?.(),
+ spystations: () => typeof SpyStations !== 'undefined' && SpyStations.destroy?.(),
+ ais: () => { if (aisEventSource) { aisEventSource.close(); aisEventSource = null; } },
+ acars: () => { if (acarsMainEventSource) { acarsMainEventSource.close(); acarsMainEventSource = null; } },
+ vdl2: () => { if (vdl2MainEventSource) { vdl2MainEventSource.close(); vdl2MainEventSource = null; } },
+ radiosonde: () => { if (radiosondeEventSource) { radiosondeEventSource.close(); radiosondeEventSource = null; } },
+ meteor: () => typeof MeteorScatter !== 'undefined' && MeteorScatter.destroy?.(),
+ };
+ return moduleDestroyMap[mode] || null;
+ }
+
+ function destroyCurrentMode() {
+ if (!currentMode) return;
+ const destroyFn = getModuleDestroyFn(currentMode);
+ if (destroyFn) {
+ try { destroyFn(); } catch(e) { console.warn(`[destroyCurrentMode] destroy ${currentMode} failed:`, e); }
+ }
+ }
+
function getActiveScanSummary() {
return {
pager: Boolean(isRunning),
@@ -4100,6 +4137,29 @@
if (typeof isTscmRunning !== 'undefined' && isTscmRunning && typeof stopTscmSweep === 'function') {
Promise.resolve(stopTscmSweep()).catch(() => { });
}
+
+ // Additional modes with server-side processes that need stopping
+ if (typeof WeFax !== 'undefined' && typeof WeFax.stop === 'function') {
+ Promise.resolve(WeFax.stop()).catch(() => { });
+ }
+ if (typeof WeatherSat !== 'undefined' && typeof WeatherSat.stop === 'function') {
+ Promise.resolve(WeatherSat.stop()).catch(() => { });
+ }
+ if (typeof SSTV !== 'undefined' && typeof SSTV.stop === 'function') {
+ Promise.resolve(SSTV.stop()).catch(() => { });
+ }
+ if (typeof SSTVGeneral !== 'undefined' && typeof SSTVGeneral.stop === 'function') {
+ Promise.resolve(SSTVGeneral.stop()).catch(() => { });
+ }
+ if (typeof SubGhz !== 'undefined' && typeof SubGhz.stop === 'function') {
+ Promise.resolve(SubGhz.stop()).catch(() => { });
+ }
+ if (typeof Meshtastic !== 'undefined' && typeof Meshtastic.stop === 'function') {
+ Promise.resolve(Meshtastic.stop()).catch(() => { });
+ }
+ if (typeof GPS !== 'undefined' && typeof GPS.stop === 'function') {
+ Promise.resolve(GPS.stop()).catch(() => { });
+ }
}
if (!window._dashboardNavigationStopHookBound) {
@@ -4125,6 +4185,7 @@
activeScans: getActiveScanSummary(),
});
}
+ destroyCurrentMode();
stopActiveLocalScansForNavigation();
} catch (_) {
// Ignore malformed hrefs.
@@ -4239,31 +4300,11 @@
await styleReadyPromise;
// Generic module cleanup — destroy previous mode's timers, SSE, etc.
- const moduleDestroyMap = {
- subghz: () => typeof SubGhz !== 'undefined' && SubGhz.destroy(),
- morse: () => typeof MorseMode !== 'undefined' && MorseMode.destroy?.(),
- spaceweather: () => typeof SpaceWeather !== 'undefined' && SpaceWeather.destroy?.(),
- weathersat: () => typeof WeatherSat !== 'undefined' && WeatherSat.suspend?.(),
- wefax: () => typeof WeFax !== 'undefined' && WeFax.destroy?.(),
- system: () => typeof SystemHealth !== 'undefined' && SystemHealth.destroy?.(),
- waterfall: () => typeof Waterfall !== 'undefined' && Waterfall.destroy?.(),
- gps: () => typeof GPS !== 'undefined' && GPS.destroy?.(),
- meshtastic: () => typeof Meshtastic !== 'undefined' && Meshtastic.destroy?.(),
- bluetooth: () => typeof BluetoothMode !== 'undefined' && BluetoothMode.destroy?.(),
- wifi: () => typeof WiFiMode !== 'undefined' && WiFiMode.destroy?.(),
- bt_locate: () => typeof BtLocate !== 'undefined' && BtLocate.destroy?.(),
- sstv: () => typeof SSTV !== 'undefined' && SSTV.destroy?.(),
- sstv_general: () => typeof SSTVGeneral !== 'undefined' && SSTVGeneral.destroy?.(),
- websdr: () => typeof WebSDR !== 'undefined' && WebSDR.destroy?.(),
- spystations: () => typeof SpyStations !== 'undefined' && SpyStations.destroy?.(),
- ais: () => { if (aisEventSource) { aisEventSource.close(); aisEventSource = null; } },
- acars: () => { if (acarsMainEventSource) { acarsMainEventSource.close(); acarsMainEventSource = null; } },
- vdl2: () => { if (vdl2MainEventSource) { vdl2MainEventSource.close(); vdl2MainEventSource = null; } },
- radiosonde: () => { if (radiosondeEventSource) { radiosondeEventSource.close(); radiosondeEventSource = null; } },
- meteor: () => typeof MeteorScatter !== 'undefined' && MeteorScatter.destroy?.(),
- };
- if (previousMode && previousMode !== mode && moduleDestroyMap[previousMode]) {
- try { moduleDestroyMap[previousMode](); } catch(e) { console.warn(`[switchMode] destroy ${previousMode} failed:`, e); }
+ if (previousMode && previousMode !== mode) {
+ const destroyFn = getModuleDestroyFn(previousMode);
+ if (destroyFn) {
+ try { destroyFn(); } catch(e) { console.warn(`[switchMode] destroy ${previousMode} failed:`, e); }
+ }
}
currentMode = mode;
@@ -11687,6 +11728,7 @@
tscmBaselineComparison = null;
tscmIdentityClusters = [];
tscmIdentitySummary = null;
+ tscmHighInterestDevices = [];
updateTscmDisplays();
updateTscmThreatCounts();
@@ -12522,7 +12564,7 @@
const exists = tscmWifiDevices.some(d => d.bssid === device.bssid);
if (!exists) {
tscmWifiDevices.push(device);
- updateTscmDisplays();
+ debouncedUpdateTscmDisplays();
updateTscmThreatCounts();
// Add to findings panel if score >= 3 (review level or higher)
if (device.score >= 3) {
@@ -12551,7 +12593,7 @@
if (!client.mac) client.mac = mac;
client.is_client = true;
tscmWifiClients.push(client);
- updateTscmDisplays();
+ debouncedUpdateTscmDisplays();
updateTscmThreatCounts();
if (client.score >= 3) {
addHighInterestDevice(client, 'wifi');
@@ -12573,7 +12615,7 @@
if (!exists) {
if (!device.mac && mac) device.mac = mac;
tscmBtDevices.push(device);
- updateTscmDisplays();
+ debouncedUpdateTscmDisplays();
updateTscmThreatCounts();
// Add to threats panel if score >= 3 (review level or higher)
if (device.score >= 3) {
@@ -12607,7 +12649,7 @@
: 3;
if (!exists) {
tscmRfSignals.push(signal);
- updateTscmDisplays();
+ debouncedUpdateTscmDisplays();
updateTscmThreatCounts();
// Add to findings panel if score >= 3 (review level or higher)
if (signal.score >= 3) {
@@ -12649,6 +12691,18 @@
// If there are signals, updateTscmDisplays() will handle the display
}
+ // Debounced versions of expensive display updates to batch rapid-fire device additions
+ let _tscmDisplayTimer = null;
+ function debouncedUpdateTscmDisplays() {
+ if (_tscmDisplayTimer) clearTimeout(_tscmDisplayTimer);
+ _tscmDisplayTimer = setTimeout(() => { _tscmDisplayTimer = null; updateTscmDisplays(); }, 250);
+ }
+ let _tscmHighInterestTimer = null;
+ function debouncedUpdateHighInterestPanel() {
+ if (_tscmHighInterestTimer) clearTimeout(_tscmHighInterestTimer);
+ _tscmHighInterestTimer = setTimeout(() => { _tscmHighInterestTimer = null; updateHighInterestPanel(); }, 250);
+ }
+
// Track high-interest devices for the threats panel
let tscmHighInterestDevices = [];
function addHighInterestDevice(device, protocol) {
@@ -12662,10 +12716,9 @@
score: device.score,
classification: device.classification,
indicators: device.indicators || [],
- recommended_action: device.recommended_action,
- device: device
+ recommended_action: device.recommended_action
});
- updateHighInterestPanel();
+ debouncedUpdateHighInterestPanel();
}
}
@@ -12730,7 +12783,7 @@
// Update dashboard counts
updateTscmThreatCounts();
- updateTscmDisplays();
+ debouncedUpdateTscmDisplays();
}
function readTscmFilters() {