mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 14:50:00 -07:00
Use Terminus font across app
This commit is contained in:
@@ -1,36 +1,36 @@
|
||||
/**
|
||||
* Intercept - Core Application Logic
|
||||
* Global state, mode switching, and shared functionality
|
||||
*/
|
||||
|
||||
// ============== GLOBAL STATE ==============
|
||||
|
||||
// Mode state flags
|
||||
let eventSource = null;
|
||||
let isRunning = false;
|
||||
let isSensorRunning = false;
|
||||
let isAdsbRunning = false;
|
||||
let isWifiRunning = false;
|
||||
let isBtRunning = false;
|
||||
let currentMode = 'pager';
|
||||
|
||||
// Message counters
|
||||
let msgCount = 0;
|
||||
let pocsagCount = 0;
|
||||
let flexCount = 0;
|
||||
let sensorCount = 0;
|
||||
let filteredCount = 0;
|
||||
|
||||
// Device list (populated from server via Jinja2)
|
||||
let deviceList = [];
|
||||
|
||||
// Auto-scroll setting
|
||||
let autoScroll = localStorage.getItem('autoScroll') !== 'false';
|
||||
|
||||
// Mute setting
|
||||
let muted = localStorage.getItem('audioMuted') === 'true';
|
||||
|
||||
// Observer location (load from localStorage or default to London)
|
||||
/**
|
||||
* Intercept - Core Application Logic
|
||||
* Global state, mode switching, and shared functionality
|
||||
*/
|
||||
|
||||
// ============== GLOBAL STATE ==============
|
||||
|
||||
// Mode state flags
|
||||
let eventSource = null;
|
||||
let isRunning = false;
|
||||
let isSensorRunning = false;
|
||||
let isAdsbRunning = false;
|
||||
let isWifiRunning = false;
|
||||
let isBtRunning = false;
|
||||
let currentMode = 'pager';
|
||||
|
||||
// Message counters
|
||||
let msgCount = 0;
|
||||
let pocsagCount = 0;
|
||||
let flexCount = 0;
|
||||
let sensorCount = 0;
|
||||
let filteredCount = 0;
|
||||
|
||||
// Device list (populated from server via Jinja2)
|
||||
let deviceList = [];
|
||||
|
||||
// Auto-scroll setting
|
||||
let autoScroll = localStorage.getItem('autoScroll') !== 'false';
|
||||
|
||||
// Mute setting
|
||||
let muted = localStorage.getItem('audioMuted') === 'true';
|
||||
|
||||
// Observer location (load from localStorage or default to London)
|
||||
let observerLocation = (function() {
|
||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||
return ObserverLocation.getForModule('observerLocation');
|
||||
@@ -44,464 +44,464 @@ let observerLocation = (function() {
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
|
||||
// Message storage for export
|
||||
let allMessages = [];
|
||||
|
||||
// Track unique sensor devices
|
||||
let uniqueDevices = new Set();
|
||||
|
||||
// SDR device usage tracking
|
||||
let sdrDeviceUsage = {};
|
||||
|
||||
// ============== DISCLAIMER HANDLING ==============
|
||||
|
||||
function checkDisclaimer() {
|
||||
const accepted = localStorage.getItem('disclaimerAccepted');
|
||||
if (accepted === 'true') {
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function acceptDisclaimer() {
|
||||
localStorage.setItem('disclaimerAccepted', 'true');
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
}
|
||||
|
||||
function declineDisclaimer() {
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
document.getElementById('rejectionPage').classList.remove('disclaimer-hidden');
|
||||
}
|
||||
|
||||
// ============== HEADER CLOCK ==============
|
||||
|
||||
function updateHeaderClock() {
|
||||
const now = new Date();
|
||||
const utc = now.toISOString().substring(11, 19);
|
||||
document.getElementById('headerUtcTime').textContent = utc;
|
||||
}
|
||||
|
||||
// ============== MODE SWITCHING ==============
|
||||
|
||||
function switchMode(mode) {
|
||||
// Stop any running scans when switching modes
|
||||
if (isRunning && typeof stopDecoding === 'function') stopDecoding();
|
||||
if (isSensorRunning && typeof stopSensorDecoding === 'function') stopSensorDecoding();
|
||||
if (isWifiRunning && typeof stopWifiScan === 'function') stopWifiScan();
|
||||
if (isBtRunning && typeof stopBtScan === 'function') stopBtScan();
|
||||
if (isAdsbRunning && typeof stopAdsbScan === 'function') stopAdsbScan();
|
||||
|
||||
currentMode = mode;
|
||||
|
||||
// Remove active from all nav buttons, then add to the correct one
|
||||
document.querySelectorAll('.mode-nav-btn').forEach(btn => btn.classList.remove('active'));
|
||||
const modeMap = {
|
||||
'pager': 'pager', 'sensor': '433', 'aircraft': 'aircraft',
|
||||
'satellite': 'satellite', 'wifi': 'wifi', 'bluetooth': 'bluetooth',
|
||||
'listening': 'listening', 'meshtastic': 'meshtastic'
|
||||
};
|
||||
document.querySelectorAll('.mode-nav-btn').forEach(btn => {
|
||||
const label = btn.querySelector('.nav-label');
|
||||
if (label && label.textContent.toLowerCase().includes(modeMap[mode])) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle mode content visibility
|
||||
document.getElementById('pagerMode').classList.toggle('active', mode === 'pager');
|
||||
document.getElementById('sensorMode').classList.toggle('active', mode === 'sensor');
|
||||
document.getElementById('aircraftMode')?.classList.toggle('active', mode === 'aircraft');
|
||||
document.getElementById('satelliteMode').classList.toggle('active', mode === 'satellite');
|
||||
document.getElementById('wifiMode').classList.toggle('active', mode === 'wifi');
|
||||
document.getElementById('bluetoothMode').classList.toggle('active', mode === 'bluetooth');
|
||||
document.getElementById('listeningPostMode').classList.toggle('active', mode === 'listening');
|
||||
document.getElementById('aprsMode')?.classList.toggle('active', mode === 'aprs');
|
||||
document.getElementById('tscmMode')?.classList.toggle('active', mode === 'tscm');
|
||||
document.getElementById('rtlamrMode')?.classList.toggle('active', mode === 'rtlamr');
|
||||
document.getElementById('spystationsMode')?.classList.toggle('active', mode === 'spystations');
|
||||
document.getElementById('meshtasticMode')?.classList.toggle('active', mode === 'meshtastic');
|
||||
|
||||
// Toggle stats visibility
|
||||
document.getElementById('pagerStats').style.display = mode === 'pager' ? 'flex' : 'none';
|
||||
document.getElementById('sensorStats').style.display = mode === 'sensor' ? 'flex' : 'none';
|
||||
document.getElementById('aircraftStats').style.display = mode === 'aircraft' ? 'flex' : 'none';
|
||||
document.getElementById('satelliteStats').style.display = mode === 'satellite' ? 'flex' : 'none';
|
||||
document.getElementById('wifiStats').style.display = mode === 'wifi' ? 'flex' : 'none';
|
||||
|
||||
// Hide signal meter - individual panels show signal strength where needed
|
||||
document.getElementById('signalMeter').style.display = 'none';
|
||||
|
||||
// Show/hide dashboard buttons in nav bar
|
||||
document.getElementById('adsbDashboardBtn').style.display = mode === 'aircraft' ? 'inline-flex' : 'none';
|
||||
document.getElementById('satelliteDashboardBtn').style.display = mode === 'satellite' ? 'inline-flex' : 'none';
|
||||
|
||||
// Update active mode indicator
|
||||
const modeNames = {
|
||||
'pager': 'PAGER',
|
||||
'sensor': '433MHZ',
|
||||
'aircraft': 'AIRCRAFT',
|
||||
'satellite': 'SATELLITE',
|
||||
'wifi': 'WIFI',
|
||||
'bluetooth': 'BLUETOOTH',
|
||||
'listening': 'LISTENING POST',
|
||||
'tscm': 'TSCM',
|
||||
'aprs': 'APRS',
|
||||
'meshtastic': 'MESHTASTIC'
|
||||
};
|
||||
document.getElementById('activeModeIndicator').innerHTML = '<span class="pulse-dot"></span>' + modeNames[mode];
|
||||
|
||||
// Update mobile nav buttons
|
||||
updateMobileNavButtons(mode);
|
||||
|
||||
// Close mobile drawer when mode is switched (on mobile)
|
||||
if (window.innerWidth < 1024 && typeof window.closeMobileDrawer === 'function') {
|
||||
window.closeMobileDrawer();
|
||||
}
|
||||
|
||||
// Toggle layout containers
|
||||
document.getElementById('wifiLayoutContainer').style.display = mode === 'wifi' ? 'flex' : 'none';
|
||||
document.getElementById('btLayoutContainer').style.display = mode === 'bluetooth' ? 'flex' : 'none';
|
||||
|
||||
// Respect the "Show Radar Display" checkbox for aircraft mode
|
||||
const showRadar = document.getElementById('adsbEnableMap')?.checked;
|
||||
document.getElementById('aircraftVisuals').style.display = (mode === 'aircraft' && showRadar) ? 'grid' : 'none';
|
||||
document.getElementById('satelliteVisuals').style.display = mode === 'satellite' ? 'block' : 'none';
|
||||
document.getElementById('listeningPostVisuals').style.display = mode === 'listening' ? 'grid' : 'none';
|
||||
|
||||
// Update output panel title based on mode
|
||||
const titles = {
|
||||
'pager': 'Pager Decoder',
|
||||
'sensor': '433MHz Sensor Monitor',
|
||||
'aircraft': 'ADS-B Aircraft Tracker',
|
||||
'satellite': 'Satellite Monitor',
|
||||
'wifi': 'WiFi Scanner',
|
||||
'bluetooth': 'Bluetooth Scanner',
|
||||
'listening': 'Listening Post',
|
||||
'meshtastic': 'Meshtastic Mesh Monitor'
|
||||
};
|
||||
document.getElementById('outputTitle').textContent = titles[mode] || 'Signal Monitor';
|
||||
|
||||
// Show/hide Device Intelligence for modes that use it
|
||||
const reconBtn = document.getElementById('reconBtn');
|
||||
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');
|
||||
if (mode === 'satellite' || mode === 'aircraft' || mode === 'listening') {
|
||||
document.getElementById('reconPanel').style.display = 'none';
|
||||
if (reconBtn) reconBtn.style.display = 'none';
|
||||
if (intelBtn) intelBtn.style.display = 'none';
|
||||
} else {
|
||||
if (reconBtn) reconBtn.style.display = 'inline-block';
|
||||
if (intelBtn) intelBtn.style.display = 'inline-block';
|
||||
if (typeof reconEnabled !== 'undefined' && reconEnabled) {
|
||||
document.getElementById('reconPanel').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Show RTL-SDR device section for modes that use it
|
||||
document.getElementById('rtlDeviceSection').style.display =
|
||||
(mode === 'pager' || mode === 'sensor' || mode === 'aircraft' || mode === 'listening') ? 'block' : 'none';
|
||||
|
||||
// Toggle mode-specific tool status displays
|
||||
document.getElementById('toolStatusPager').style.display = (mode === 'pager') ? 'grid' : 'none';
|
||||
document.getElementById('toolStatusSensor').style.display = (mode === 'sensor') ? 'grid' : 'none';
|
||||
document.getElementById('toolStatusAircraft').style.display = (mode === 'aircraft') ? 'grid' : 'none';
|
||||
|
||||
// Hide waterfall and output console for modes with their own visualizations
|
||||
document.querySelector('.waterfall-container').style.display =
|
||||
(mode === 'satellite' || mode === 'listening' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
|
||||
document.getElementById('output').style.display =
|
||||
(mode === 'satellite' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
|
||||
document.querySelector('.status-bar').style.display = (mode === 'satellite' || mode === 'tscm' || mode === 'meshtastic' || mode === 'aprs' || mode === 'spystations') ? 'none' : 'flex';
|
||||
|
||||
// Load interfaces and initialize visualizations when switching modes
|
||||
if (mode === 'wifi') {
|
||||
if (typeof refreshWifiInterfaces === 'function') refreshWifiInterfaces();
|
||||
if (typeof initRadar === 'function') initRadar();
|
||||
if (typeof initWatchList === 'function') initWatchList();
|
||||
} else if (mode === 'bluetooth') {
|
||||
if (typeof refreshBtInterfaces === 'function') refreshBtInterfaces();
|
||||
if (typeof initBtRadar === 'function') initBtRadar();
|
||||
} else if (mode === 'aircraft') {
|
||||
if (typeof checkAdsbTools === 'function') checkAdsbTools();
|
||||
if (typeof initAircraftRadar === 'function') initAircraftRadar();
|
||||
} else if (mode === 'satellite') {
|
||||
if (typeof initPolarPlot === 'function') initPolarPlot();
|
||||
if (typeof initSatelliteList === 'function') initSatelliteList();
|
||||
} else if (mode === 'listening') {
|
||||
if (typeof checkScannerTools === 'function') checkScannerTools();
|
||||
if (typeof checkAudioTools === 'function') checkAudioTools();
|
||||
if (typeof populateScannerDeviceSelect === 'function') populateScannerDeviceSelect();
|
||||
if (typeof populateAudioDeviceSelect === 'function') populateAudioDeviceSelect();
|
||||
} else if (mode === 'meshtastic') {
|
||||
if (typeof Meshtastic !== 'undefined' && Meshtastic.init) Meshtastic.init();
|
||||
}
|
||||
}
|
||||
|
||||
// ============== SECTION COLLAPSE ==============
|
||||
|
||||
function toggleSection(el) {
|
||||
el.closest('.section').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
// ============== THEME MANAGEMENT ==============
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const currentTheme = html.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
|
||||
// Update button text
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
}
|
||||
|
||||
function loadTheme() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.textContent = savedTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
}
|
||||
|
||||
// ============== AUTO-SCROLL ==============
|
||||
|
||||
function toggleAutoScroll() {
|
||||
autoScroll = !autoScroll;
|
||||
localStorage.setItem('autoScroll', autoScroll);
|
||||
updateAutoScrollButton();
|
||||
}
|
||||
|
||||
function updateAutoScrollButton() {
|
||||
const btn = document.getElementById('autoScrollBtn');
|
||||
if (btn) {
|
||||
btn.innerHTML = autoScroll ? '⬇ AUTO-SCROLL ON' : '⬇ AUTO-SCROLL OFF';
|
||||
btn.classList.toggle('active', autoScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// ============== SDR DEVICE MANAGEMENT ==============
|
||||
|
||||
function getSelectedDevice() {
|
||||
return document.getElementById('deviceSelect').value;
|
||||
}
|
||||
|
||||
function getSelectedSDRType() {
|
||||
return document.getElementById('sdrTypeSelect').value;
|
||||
}
|
||||
|
||||
function reserveDevice(deviceIndex, modeId) {
|
||||
sdrDeviceUsage[modeId] = deviceIndex;
|
||||
}
|
||||
|
||||
function releaseDevice(modeId) {
|
||||
delete sdrDeviceUsage[modeId];
|
||||
}
|
||||
|
||||
function checkDeviceAvailability(requestingMode) {
|
||||
const selectedDevice = parseInt(getSelectedDevice());
|
||||
for (const [mode, device] of Object.entries(sdrDeviceUsage)) {
|
||||
if (mode !== requestingMode && device === selectedDevice) {
|
||||
alert(`Device ${selectedDevice} is currently in use by ${mode} mode. Please select a different device or stop the other scan first.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============== BIAS-T SETTINGS ==============
|
||||
|
||||
function saveBiasTSetting() {
|
||||
const enabled = document.getElementById('biasT')?.checked || false;
|
||||
localStorage.setItem('biasTEnabled', enabled);
|
||||
}
|
||||
|
||||
function getBiasTEnabled() {
|
||||
return document.getElementById('biasT')?.checked || false;
|
||||
}
|
||||
|
||||
function loadBiasTSetting() {
|
||||
const saved = localStorage.getItem('biasTEnabled');
|
||||
if (saved === 'true') {
|
||||
const checkbox = document.getElementById('biasT');
|
||||
if (checkbox) checkbox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ============== REMOTE SDR ==============
|
||||
|
||||
function toggleRemoteSDR() {
|
||||
const useRemote = document.getElementById('useRemoteSDR').checked;
|
||||
const configDiv = document.getElementById('remoteSDRConfig');
|
||||
const localControls = document.querySelectorAll('#sdrTypeSelect, #deviceSelect');
|
||||
|
||||
if (useRemote) {
|
||||
configDiv.style.display = 'block';
|
||||
localControls.forEach(el => el.disabled = true);
|
||||
} else {
|
||||
configDiv.style.display = 'none';
|
||||
localControls.forEach(el => el.disabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
function getRemoteSDRConfig() {
|
||||
const useRemote = document.getElementById('useRemoteSDR')?.checked;
|
||||
if (!useRemote) return null;
|
||||
|
||||
const host = document.getElementById('rtlTcpHost')?.value || 'localhost';
|
||||
const port = parseInt(document.getElementById('rtlTcpPort')?.value || '1234');
|
||||
|
||||
if (!host || isNaN(port)) {
|
||||
alert('Please enter valid rtl_tcp host and port');
|
||||
return false;
|
||||
}
|
||||
|
||||
return { host, port };
|
||||
}
|
||||
|
||||
// ============== OUTPUT DISPLAY ==============
|
||||
|
||||
function showInfo(text) {
|
||||
const output = document.getElementById('output');
|
||||
if (!output) return;
|
||||
|
||||
const placeholder = output.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const infoEl = document.createElement('div');
|
||||
infoEl.className = 'info-msg';
|
||||
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #888; word-break: break-all;';
|
||||
infoEl.textContent = text;
|
||||
output.insertBefore(infoEl, output.firstChild);
|
||||
}
|
||||
|
||||
function showError(text) {
|
||||
const output = document.getElementById('output');
|
||||
if (!output) return;
|
||||
|
||||
const placeholder = output.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const errorEl = document.createElement('div');
|
||||
errorEl.className = 'error-msg';
|
||||
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
|
||||
errorEl.textContent = '⚠ ' + text;
|
||||
output.insertBefore(errorEl, output.firstChild);
|
||||
}
|
||||
|
||||
// ============== INITIALIZATION ==============
|
||||
|
||||
// ============== MOBILE NAVIGATION ==============
|
||||
|
||||
function initMobileNav() {
|
||||
const hamburgerBtn = document.getElementById('hamburgerBtn');
|
||||
const sidebar = document.getElementById('mainSidebar');
|
||||
const overlay = document.getElementById('drawerOverlay');
|
||||
|
||||
if (!hamburgerBtn || !sidebar || !overlay) return;
|
||||
|
||||
function openDrawer() {
|
||||
sidebar.classList.add('open');
|
||||
overlay.classList.add('visible');
|
||||
hamburgerBtn.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
sidebar.classList.remove('open');
|
||||
overlay.classList.remove('visible');
|
||||
hamburgerBtn.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function toggleDrawer() {
|
||||
if (sidebar.classList.contains('open')) {
|
||||
closeDrawer();
|
||||
} else {
|
||||
openDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
hamburgerBtn.addEventListener('click', toggleDrawer);
|
||||
overlay.addEventListener('click', closeDrawer);
|
||||
|
||||
// Close drawer when resizing to desktop
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= 1024) {
|
||||
closeDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for external use
|
||||
window.toggleMobileDrawer = toggleDrawer;
|
||||
window.closeMobileDrawer = closeDrawer;
|
||||
}
|
||||
|
||||
function setViewportHeight() {
|
||||
// Fix for iOS Safari address bar height
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
}
|
||||
|
||||
function updateMobileNavButtons(mode) {
|
||||
// Update mobile nav bar buttons
|
||||
document.querySelectorAll('.mobile-nav-btn').forEach(btn => {
|
||||
const btnMode = btn.getAttribute('data-mode');
|
||||
btn.classList.toggle('active', btnMode === mode);
|
||||
});
|
||||
}
|
||||
|
||||
function initApp() {
|
||||
// Check disclaimer
|
||||
checkDisclaimer();
|
||||
|
||||
// Load theme
|
||||
loadTheme();
|
||||
|
||||
// Start clock
|
||||
updateHeaderClock();
|
||||
setInterval(updateHeaderClock, 1000);
|
||||
|
||||
// Load bias-T setting
|
||||
loadBiasTSetting();
|
||||
|
||||
// Initialize observer location inputs
|
||||
const adsbLatInput = document.getElementById('adsbObsLat');
|
||||
const adsbLonInput = document.getElementById('adsbObsLon');
|
||||
const obsLatInput = document.getElementById('obsLat');
|
||||
const obsLonInput = document.getElementById('obsLon');
|
||||
if (adsbLatInput) adsbLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (adsbLonInput) adsbLonInput.value = observerLocation.lon.toFixed(4);
|
||||
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
|
||||
|
||||
// Update UI state
|
||||
updateAutoScrollButton();
|
||||
|
||||
// Make sections collapsible
|
||||
document.querySelectorAll('.section h3').forEach(h3 => {
|
||||
h3.addEventListener('click', function() {
|
||||
this.parentElement.classList.toggle('collapsed');
|
||||
});
|
||||
});
|
||||
|
||||
// Collapse all sections by default (except SDR Device which is first)
|
||||
document.querySelectorAll('.section').forEach((section, index) => {
|
||||
if (index > 0) {
|
||||
section.classList.add('collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize mobile navigation
|
||||
initMobileNav();
|
||||
|
||||
// Set viewport height for mobile browsers
|
||||
setViewportHeight();
|
||||
window.addEventListener('resize', setViewportHeight);
|
||||
}
|
||||
|
||||
// Run initialization when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
|
||||
// Message storage for export
|
||||
let allMessages = [];
|
||||
|
||||
// Track unique sensor devices
|
||||
let uniqueDevices = new Set();
|
||||
|
||||
// SDR device usage tracking
|
||||
let sdrDeviceUsage = {};
|
||||
|
||||
// ============== DISCLAIMER HANDLING ==============
|
||||
|
||||
function checkDisclaimer() {
|
||||
const accepted = localStorage.getItem('disclaimerAccepted');
|
||||
if (accepted === 'true') {
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function acceptDisclaimer() {
|
||||
localStorage.setItem('disclaimerAccepted', 'true');
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
}
|
||||
|
||||
function declineDisclaimer() {
|
||||
document.getElementById('disclaimerModal').classList.add('disclaimer-hidden');
|
||||
document.getElementById('rejectionPage').classList.remove('disclaimer-hidden');
|
||||
}
|
||||
|
||||
// ============== HEADER CLOCK ==============
|
||||
|
||||
function updateHeaderClock() {
|
||||
const now = new Date();
|
||||
const utc = now.toISOString().substring(11, 19);
|
||||
document.getElementById('headerUtcTime').textContent = utc;
|
||||
}
|
||||
|
||||
// ============== MODE SWITCHING ==============
|
||||
|
||||
function switchMode(mode) {
|
||||
// Stop any running scans when switching modes
|
||||
if (isRunning && typeof stopDecoding === 'function') stopDecoding();
|
||||
if (isSensorRunning && typeof stopSensorDecoding === 'function') stopSensorDecoding();
|
||||
if (isWifiRunning && typeof stopWifiScan === 'function') stopWifiScan();
|
||||
if (isBtRunning && typeof stopBtScan === 'function') stopBtScan();
|
||||
if (isAdsbRunning && typeof stopAdsbScan === 'function') stopAdsbScan();
|
||||
|
||||
currentMode = mode;
|
||||
|
||||
// Remove active from all nav buttons, then add to the correct one
|
||||
document.querySelectorAll('.mode-nav-btn').forEach(btn => btn.classList.remove('active'));
|
||||
const modeMap = {
|
||||
'pager': 'pager', 'sensor': '433', 'aircraft': 'aircraft',
|
||||
'satellite': 'satellite', 'wifi': 'wifi', 'bluetooth': 'bluetooth',
|
||||
'listening': 'listening', 'meshtastic': 'meshtastic'
|
||||
};
|
||||
document.querySelectorAll('.mode-nav-btn').forEach(btn => {
|
||||
const label = btn.querySelector('.nav-label');
|
||||
if (label && label.textContent.toLowerCase().includes(modeMap[mode])) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle mode content visibility
|
||||
document.getElementById('pagerMode').classList.toggle('active', mode === 'pager');
|
||||
document.getElementById('sensorMode').classList.toggle('active', mode === 'sensor');
|
||||
document.getElementById('aircraftMode')?.classList.toggle('active', mode === 'aircraft');
|
||||
document.getElementById('satelliteMode').classList.toggle('active', mode === 'satellite');
|
||||
document.getElementById('wifiMode').classList.toggle('active', mode === 'wifi');
|
||||
document.getElementById('bluetoothMode').classList.toggle('active', mode === 'bluetooth');
|
||||
document.getElementById('listeningPostMode').classList.toggle('active', mode === 'listening');
|
||||
document.getElementById('aprsMode')?.classList.toggle('active', mode === 'aprs');
|
||||
document.getElementById('tscmMode')?.classList.toggle('active', mode === 'tscm');
|
||||
document.getElementById('rtlamrMode')?.classList.toggle('active', mode === 'rtlamr');
|
||||
document.getElementById('spystationsMode')?.classList.toggle('active', mode === 'spystations');
|
||||
document.getElementById('meshtasticMode')?.classList.toggle('active', mode === 'meshtastic');
|
||||
|
||||
// Toggle stats visibility
|
||||
document.getElementById('pagerStats').style.display = mode === 'pager' ? 'flex' : 'none';
|
||||
document.getElementById('sensorStats').style.display = mode === 'sensor' ? 'flex' : 'none';
|
||||
document.getElementById('aircraftStats').style.display = mode === 'aircraft' ? 'flex' : 'none';
|
||||
document.getElementById('satelliteStats').style.display = mode === 'satellite' ? 'flex' : 'none';
|
||||
document.getElementById('wifiStats').style.display = mode === 'wifi' ? 'flex' : 'none';
|
||||
|
||||
// Hide signal meter - individual panels show signal strength where needed
|
||||
document.getElementById('signalMeter').style.display = 'none';
|
||||
|
||||
// Show/hide dashboard buttons in nav bar
|
||||
document.getElementById('adsbDashboardBtn').style.display = mode === 'aircraft' ? 'inline-flex' : 'none';
|
||||
document.getElementById('satelliteDashboardBtn').style.display = mode === 'satellite' ? 'inline-flex' : 'none';
|
||||
|
||||
// Update active mode indicator
|
||||
const modeNames = {
|
||||
'pager': 'PAGER',
|
||||
'sensor': '433MHZ',
|
||||
'aircraft': 'AIRCRAFT',
|
||||
'satellite': 'SATELLITE',
|
||||
'wifi': 'WIFI',
|
||||
'bluetooth': 'BLUETOOTH',
|
||||
'listening': 'LISTENING POST',
|
||||
'tscm': 'TSCM',
|
||||
'aprs': 'APRS',
|
||||
'meshtastic': 'MESHTASTIC'
|
||||
};
|
||||
document.getElementById('activeModeIndicator').innerHTML = '<span class="pulse-dot"></span>' + modeNames[mode];
|
||||
|
||||
// Update mobile nav buttons
|
||||
updateMobileNavButtons(mode);
|
||||
|
||||
// Close mobile drawer when mode is switched (on mobile)
|
||||
if (window.innerWidth < 1024 && typeof window.closeMobileDrawer === 'function') {
|
||||
window.closeMobileDrawer();
|
||||
}
|
||||
|
||||
// Toggle layout containers
|
||||
document.getElementById('wifiLayoutContainer').style.display = mode === 'wifi' ? 'flex' : 'none';
|
||||
document.getElementById('btLayoutContainer').style.display = mode === 'bluetooth' ? 'flex' : 'none';
|
||||
|
||||
// Respect the "Show Radar Display" checkbox for aircraft mode
|
||||
const showRadar = document.getElementById('adsbEnableMap')?.checked;
|
||||
document.getElementById('aircraftVisuals').style.display = (mode === 'aircraft' && showRadar) ? 'grid' : 'none';
|
||||
document.getElementById('satelliteVisuals').style.display = mode === 'satellite' ? 'block' : 'none';
|
||||
document.getElementById('listeningPostVisuals').style.display = mode === 'listening' ? 'grid' : 'none';
|
||||
|
||||
// Update output panel title based on mode
|
||||
const titles = {
|
||||
'pager': 'Pager Decoder',
|
||||
'sensor': '433MHz Sensor Monitor',
|
||||
'aircraft': 'ADS-B Aircraft Tracker',
|
||||
'satellite': 'Satellite Monitor',
|
||||
'wifi': 'WiFi Scanner',
|
||||
'bluetooth': 'Bluetooth Scanner',
|
||||
'listening': 'Listening Post',
|
||||
'meshtastic': 'Meshtastic Mesh Monitor'
|
||||
};
|
||||
document.getElementById('outputTitle').textContent = titles[mode] || 'Signal Monitor';
|
||||
|
||||
// Show/hide Device Intelligence for modes that use it
|
||||
const reconBtn = document.getElementById('reconBtn');
|
||||
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');
|
||||
if (mode === 'satellite' || mode === 'aircraft' || mode === 'listening') {
|
||||
document.getElementById('reconPanel').style.display = 'none';
|
||||
if (reconBtn) reconBtn.style.display = 'none';
|
||||
if (intelBtn) intelBtn.style.display = 'none';
|
||||
} else {
|
||||
if (reconBtn) reconBtn.style.display = 'inline-block';
|
||||
if (intelBtn) intelBtn.style.display = 'inline-block';
|
||||
if (typeof reconEnabled !== 'undefined' && reconEnabled) {
|
||||
document.getElementById('reconPanel').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Show RTL-SDR device section for modes that use it
|
||||
document.getElementById('rtlDeviceSection').style.display =
|
||||
(mode === 'pager' || mode === 'sensor' || mode === 'aircraft' || mode === 'listening') ? 'block' : 'none';
|
||||
|
||||
// Toggle mode-specific tool status displays
|
||||
document.getElementById('toolStatusPager').style.display = (mode === 'pager') ? 'grid' : 'none';
|
||||
document.getElementById('toolStatusSensor').style.display = (mode === 'sensor') ? 'grid' : 'none';
|
||||
document.getElementById('toolStatusAircraft').style.display = (mode === 'aircraft') ? 'grid' : 'none';
|
||||
|
||||
// Hide waterfall and output console for modes with their own visualizations
|
||||
document.querySelector('.waterfall-container').style.display =
|
||||
(mode === 'satellite' || mode === 'listening' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
|
||||
document.getElementById('output').style.display =
|
||||
(mode === 'satellite' || mode === 'aircraft' || mode === 'wifi' || mode === 'bluetooth' || mode === 'meshtastic' || mode === 'aprs' || mode === 'tscm' || mode === 'spystations') ? 'none' : 'block';
|
||||
document.querySelector('.status-bar').style.display = (mode === 'satellite' || mode === 'tscm' || mode === 'meshtastic' || mode === 'aprs' || mode === 'spystations') ? 'none' : 'flex';
|
||||
|
||||
// Load interfaces and initialize visualizations when switching modes
|
||||
if (mode === 'wifi') {
|
||||
if (typeof refreshWifiInterfaces === 'function') refreshWifiInterfaces();
|
||||
if (typeof initRadar === 'function') initRadar();
|
||||
if (typeof initWatchList === 'function') initWatchList();
|
||||
} else if (mode === 'bluetooth') {
|
||||
if (typeof refreshBtInterfaces === 'function') refreshBtInterfaces();
|
||||
if (typeof initBtRadar === 'function') initBtRadar();
|
||||
} else if (mode === 'aircraft') {
|
||||
if (typeof checkAdsbTools === 'function') checkAdsbTools();
|
||||
if (typeof initAircraftRadar === 'function') initAircraftRadar();
|
||||
} else if (mode === 'satellite') {
|
||||
if (typeof initPolarPlot === 'function') initPolarPlot();
|
||||
if (typeof initSatelliteList === 'function') initSatelliteList();
|
||||
} else if (mode === 'listening') {
|
||||
if (typeof checkScannerTools === 'function') checkScannerTools();
|
||||
if (typeof checkAudioTools === 'function') checkAudioTools();
|
||||
if (typeof populateScannerDeviceSelect === 'function') populateScannerDeviceSelect();
|
||||
if (typeof populateAudioDeviceSelect === 'function') populateAudioDeviceSelect();
|
||||
} else if (mode === 'meshtastic') {
|
||||
if (typeof Meshtastic !== 'undefined' && Meshtastic.init) Meshtastic.init();
|
||||
}
|
||||
}
|
||||
|
||||
// ============== SECTION COLLAPSE ==============
|
||||
|
||||
function toggleSection(el) {
|
||||
el.closest('.section').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
// ============== THEME MANAGEMENT ==============
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const currentTheme = html.getAttribute('data-theme');
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
|
||||
// Update button text
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
}
|
||||
|
||||
function loadTheme() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.textContent = savedTheme === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
}
|
||||
|
||||
// ============== AUTO-SCROLL ==============
|
||||
|
||||
function toggleAutoScroll() {
|
||||
autoScroll = !autoScroll;
|
||||
localStorage.setItem('autoScroll', autoScroll);
|
||||
updateAutoScrollButton();
|
||||
}
|
||||
|
||||
function updateAutoScrollButton() {
|
||||
const btn = document.getElementById('autoScrollBtn');
|
||||
if (btn) {
|
||||
btn.innerHTML = autoScroll ? '⬇ AUTO-SCROLL ON' : '⬇ AUTO-SCROLL OFF';
|
||||
btn.classList.toggle('active', autoScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// ============== SDR DEVICE MANAGEMENT ==============
|
||||
|
||||
function getSelectedDevice() {
|
||||
return document.getElementById('deviceSelect').value;
|
||||
}
|
||||
|
||||
function getSelectedSDRType() {
|
||||
return document.getElementById('sdrTypeSelect').value;
|
||||
}
|
||||
|
||||
function reserveDevice(deviceIndex, modeId) {
|
||||
sdrDeviceUsage[modeId] = deviceIndex;
|
||||
}
|
||||
|
||||
function releaseDevice(modeId) {
|
||||
delete sdrDeviceUsage[modeId];
|
||||
}
|
||||
|
||||
function checkDeviceAvailability(requestingMode) {
|
||||
const selectedDevice = parseInt(getSelectedDevice());
|
||||
for (const [mode, device] of Object.entries(sdrDeviceUsage)) {
|
||||
if (mode !== requestingMode && device === selectedDevice) {
|
||||
alert(`Device ${selectedDevice} is currently in use by ${mode} mode. Please select a different device or stop the other scan first.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============== BIAS-T SETTINGS ==============
|
||||
|
||||
function saveBiasTSetting() {
|
||||
const enabled = document.getElementById('biasT')?.checked || false;
|
||||
localStorage.setItem('biasTEnabled', enabled);
|
||||
}
|
||||
|
||||
function getBiasTEnabled() {
|
||||
return document.getElementById('biasT')?.checked || false;
|
||||
}
|
||||
|
||||
function loadBiasTSetting() {
|
||||
const saved = localStorage.getItem('biasTEnabled');
|
||||
if (saved === 'true') {
|
||||
const checkbox = document.getElementById('biasT');
|
||||
if (checkbox) checkbox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ============== REMOTE SDR ==============
|
||||
|
||||
function toggleRemoteSDR() {
|
||||
const useRemote = document.getElementById('useRemoteSDR').checked;
|
||||
const configDiv = document.getElementById('remoteSDRConfig');
|
||||
const localControls = document.querySelectorAll('#sdrTypeSelect, #deviceSelect');
|
||||
|
||||
if (useRemote) {
|
||||
configDiv.style.display = 'block';
|
||||
localControls.forEach(el => el.disabled = true);
|
||||
} else {
|
||||
configDiv.style.display = 'none';
|
||||
localControls.forEach(el => el.disabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
function getRemoteSDRConfig() {
|
||||
const useRemote = document.getElementById('useRemoteSDR')?.checked;
|
||||
if (!useRemote) return null;
|
||||
|
||||
const host = document.getElementById('rtlTcpHost')?.value || 'localhost';
|
||||
const port = parseInt(document.getElementById('rtlTcpPort')?.value || '1234');
|
||||
|
||||
if (!host || isNaN(port)) {
|
||||
alert('Please enter valid rtl_tcp host and port');
|
||||
return false;
|
||||
}
|
||||
|
||||
return { host, port };
|
||||
}
|
||||
|
||||
// ============== OUTPUT DISPLAY ==============
|
||||
|
||||
function showInfo(text) {
|
||||
const output = document.getElementById('output');
|
||||
if (!output) return;
|
||||
|
||||
const placeholder = output.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const infoEl = document.createElement('div');
|
||||
infoEl.className = 'info-msg';
|
||||
infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "Terminus", monospace; font-size: 11px; color: #888; word-break: break-all;';
|
||||
infoEl.textContent = text;
|
||||
output.insertBefore(infoEl, output.firstChild);
|
||||
}
|
||||
|
||||
function showError(text) {
|
||||
const output = document.getElementById('output');
|
||||
if (!output) return;
|
||||
|
||||
const placeholder = output.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
|
||||
const errorEl = document.createElement('div');
|
||||
errorEl.className = 'error-msg';
|
||||
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "Terminus", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
|
||||
errorEl.textContent = '⚠ ' + text;
|
||||
output.insertBefore(errorEl, output.firstChild);
|
||||
}
|
||||
|
||||
// ============== INITIALIZATION ==============
|
||||
|
||||
// ============== MOBILE NAVIGATION ==============
|
||||
|
||||
function initMobileNav() {
|
||||
const hamburgerBtn = document.getElementById('hamburgerBtn');
|
||||
const sidebar = document.getElementById('mainSidebar');
|
||||
const overlay = document.getElementById('drawerOverlay');
|
||||
|
||||
if (!hamburgerBtn || !sidebar || !overlay) return;
|
||||
|
||||
function openDrawer() {
|
||||
sidebar.classList.add('open');
|
||||
overlay.classList.add('visible');
|
||||
hamburgerBtn.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
sidebar.classList.remove('open');
|
||||
overlay.classList.remove('visible');
|
||||
hamburgerBtn.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function toggleDrawer() {
|
||||
if (sidebar.classList.contains('open')) {
|
||||
closeDrawer();
|
||||
} else {
|
||||
openDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
hamburgerBtn.addEventListener('click', toggleDrawer);
|
||||
overlay.addEventListener('click', closeDrawer);
|
||||
|
||||
// Close drawer when resizing to desktop
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.innerWidth >= 1024) {
|
||||
closeDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for external use
|
||||
window.toggleMobileDrawer = toggleDrawer;
|
||||
window.closeMobileDrawer = closeDrawer;
|
||||
}
|
||||
|
||||
function setViewportHeight() {
|
||||
// Fix for iOS Safari address bar height
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
}
|
||||
|
||||
function updateMobileNavButtons(mode) {
|
||||
// Update mobile nav bar buttons
|
||||
document.querySelectorAll('.mobile-nav-btn').forEach(btn => {
|
||||
const btnMode = btn.getAttribute('data-mode');
|
||||
btn.classList.toggle('active', btnMode === mode);
|
||||
});
|
||||
}
|
||||
|
||||
function initApp() {
|
||||
// Check disclaimer
|
||||
checkDisclaimer();
|
||||
|
||||
// Load theme
|
||||
loadTheme();
|
||||
|
||||
// Start clock
|
||||
updateHeaderClock();
|
||||
setInterval(updateHeaderClock, 1000);
|
||||
|
||||
// Load bias-T setting
|
||||
loadBiasTSetting();
|
||||
|
||||
// Initialize observer location inputs
|
||||
const adsbLatInput = document.getElementById('adsbObsLat');
|
||||
const adsbLonInput = document.getElementById('adsbObsLon');
|
||||
const obsLatInput = document.getElementById('obsLat');
|
||||
const obsLonInput = document.getElementById('obsLon');
|
||||
if (adsbLatInput) adsbLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (adsbLonInput) adsbLonInput.value = observerLocation.lon.toFixed(4);
|
||||
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
|
||||
|
||||
// Update UI state
|
||||
updateAutoScrollButton();
|
||||
|
||||
// Make sections collapsible
|
||||
document.querySelectorAll('.section h3').forEach(h3 => {
|
||||
h3.addEventListener('click', function() {
|
||||
this.parentElement.classList.toggle('collapsed');
|
||||
});
|
||||
});
|
||||
|
||||
// Collapse all sections by default (except SDR Device which is first)
|
||||
document.querySelectorAll('.section').forEach((section, index) => {
|
||||
if (index > 0) {
|
||||
section.classList.add('collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize mobile navigation
|
||||
initMobileNav();
|
||||
|
||||
// Set viewport height for mobile browsers
|
||||
setViewportHeight();
|
||||
window.addEventListener('resize', setViewportHeight);
|
||||
}
|
||||
|
||||
// Run initialization when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
|
||||
Reference in New Issue
Block a user