mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Replace IBM Plex Mono, Space Mono, and JetBrains Mono with Roboto Condensed across all CSS variables, inline styles, canvas ctx.font references, and Google Fonts CDN links. Updates 28 files covering templates, stylesheets, and JS modules for consistent typography. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
510 lines
19 KiB
JavaScript
510 lines
19 KiB
JavaScript
/**
|
|
* 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');
|
|
}
|
|
const saved = localStorage.getItem('observerLocation');
|
|
if (saved) {
|
|
try {
|
|
const parsed = JSON.parse(saved);
|
|
if (parsed.lat && parsed.lon) return parsed;
|
|
} catch (e) {}
|
|
}
|
|
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') || 'dark';
|
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
html.setAttribute('data-theme', newTheme);
|
|
localStorage.setItem('intercept-theme', newTheme);
|
|
|
|
// Update button text
|
|
const btn = document.getElementById('themeToggle');
|
|
if (btn) {
|
|
btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
|
|
}
|
|
}
|
|
|
|
function loadTheme() {
|
|
const savedTheme = localStorage.getItem('intercept-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: "Roboto Condensed", "Arial Narrow", sans-serif; 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: "Roboto Condensed", "Arial Narrow", sans-serif; 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 sidebar menu sections by default, but skip headerless utility blocks.
|
|
document.querySelectorAll('.sidebar .section').forEach((section) => {
|
|
if (section.querySelector('h3')) {
|
|
section.classList.add('collapsed');
|
|
} else {
|
|
section.classList.remove('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);
|