diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css index e7c98b5..1af7360 100644 --- a/static/css/adsb_dashboard.css +++ b/static/css/adsb_dashboard.css @@ -418,6 +418,7 @@ body { } .aircraft-item { + position: relative; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(74, 158, 255, 0.15); border-radius: 4px; diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 930054f..c7de9fd 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -100,7 +100,9 @@ Military Civil Emergency + Watchlist + ★ 50nm 100nm @@ -164,6 +166,9 @@ let alertsEnabled = true; let currentView = 'map'; // 'map' or 'radar' + // Watchlist - persisted to localStorage + let watchlist = JSON.parse(localStorage.getItem('adsb_watchlist') || '[]'); + // Aircraft trails let aircraftTrails = {}; // ICAO -> [{lat, lon, alt, time}, ...] let trailLines = {}; // ICAO -> L.polyline (array of segments) @@ -245,11 +250,16 @@ if (alertedAircraft[icao]) return; const militaryInfo = isMilitaryAircraft(icao, ac.callsign); const squawkInfo = checkSquawkCode(ac); + const onWatchlist = isOnWatchlist(ac); if (squawkInfo) { alertedAircraft[icao] = 'emergency'; playAlertSound('emergency'); showAlertBanner(`EMERGENCY: ${squawkInfo.name} - ${ac.callsign || icao}`, '#ff0000'); + } else if (onWatchlist) { + alertedAircraft[icao] = 'watchlist'; + playAlertSound('military'); // Use military sound for watchlist + showAlertBanner(`WATCHLIST: ${ac.callsign || ac.registration || icao} detected!`, '#00d4ff'); } else if (militaryInfo.military) { alertedAircraft[icao] = 'military'; playAlertSound('military'); @@ -278,6 +288,114 @@ alertsEnabled = document.getElementById('alertToggle').checked; } + // ============================================ + // WATCHLIST FUNCTIONS + // ============================================ + function saveWatchlist() { + localStorage.setItem('adsb_watchlist', JSON.stringify(watchlist)); + } + + function isOnWatchlist(aircraft) { + const icao = aircraft.icao?.toUpperCase(); + const callsign = aircraft.callsign?.toUpperCase()?.trim(); + const registration = aircraft.registration?.toUpperCase()?.trim(); + + return watchlist.some(entry => { + const val = entry.value.toUpperCase(); + if (entry.type === 'icao' && icao === val) return true; + if (entry.type === 'callsign' && callsign && callsign.includes(val)) return true; + if (entry.type === 'registration' && registration === val) return true; + if (entry.type === 'any') { + if (icao === val) return true; + if (callsign && callsign.includes(val)) return true; + if (registration === val) return true; + } + return false; + }); + } + + function addToWatchlist(value, type = 'any', note = '') { + value = value.trim().toUpperCase(); + if (!value) return false; + + // Check for duplicates + const exists = watchlist.some(e => e.value.toUpperCase() === value && e.type === type); + if (exists) return false; + + watchlist.push({ value, type, note, added: Date.now() }); + saveWatchlist(); + renderWatchlist(); + return true; + } + + function removeFromWatchlist(index) { + watchlist.splice(index, 1); + saveWatchlist(); + renderWatchlist(); + } + + function showWatchlistModal() { + renderWatchlist(); + document.getElementById('watchlistModal').classList.add('active'); + document.getElementById('watchlistInput').focus(); + } + + function closeWatchlistModal() { + document.getElementById('watchlistModal').classList.remove('active'); + } + + function renderWatchlist() { + const container = document.getElementById('watchlistEntries'); + document.getElementById('watchlistCount').textContent = watchlist.length; + + if (watchlist.length === 0) { + container.innerHTML = 'No entries. Add callsigns, registrations, or ICAO codes to watch.'; + return; + } + + container.innerHTML = watchlist.map((entry, i) => ` + + + ${entry.value} + ${entry.type} + ${entry.note ? `${entry.note}` : ''} + + × + + `).join(''); + } + + // Close watchlist modal on overlay click or Escape key + document.getElementById('watchlistModal')?.addEventListener('click', (e) => { + if (e.target.id === 'watchlistModal') closeWatchlistModal(); + }); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + closeWatchlistModal(); + closeSquawkModal(); + } + }); + + function handleWatchlistAdd() { + const input = document.getElementById('watchlistInput'); + const type = document.getElementById('watchlistType').value; + const note = document.getElementById('watchlistNote').value.trim(); + + if (addToWatchlist(input.value, type, note)) { + input.value = ''; + document.getElementById('watchlistNote').value = ''; + } + } + + function addCurrentAircraftToWatchlist() { + if (!selectedIcao || !aircraft[selectedIcao]) return; + const ac = aircraft[selectedIcao]; + const value = ac.callsign || ac.registration || ac.icao; + const type = ac.callsign ? 'callsign' : (ac.registration ? 'registration' : 'icao'); + addToWatchlist(value, type); + showAlertBanner(`Added ${value} to watchlist`, '#00d4ff'); + } + // ============================================ // MILITARY/EMERGENCY DETECTION // ============================================ @@ -1020,6 +1138,7 @@ if (currentFilter === 'military') return militaryInfo.military; if (currentFilter === 'civil') return !militaryInfo.military; if (currentFilter === 'emergency') return !!squawkInfo; + if (currentFilter === 'watchlist') return isOnWatchlist(ac); return true; } @@ -1512,7 +1631,7 @@ sudo make install sortedAircraft.forEach(ac => { const div = document.createElement('div'); - div.className = `aircraft-item ${selectedIcao === ac.icao ? 'selected' : ''}`; + div.className = `aircraft-item ${selectedIcao === ac.icao ? 'selected' : ''} ${isOnWatchlist(ac) ? 'watched' : ''}`; div.setAttribute('data-icao', ac.icao); div.onclick = () => selectAircraft(ac.icao); div.innerHTML = buildAircraftItemHTML(ac); @@ -1531,7 +1650,7 @@ sudo make install sortedAircraft.forEach(ac => { const existingItem = existingItems[ac.icao]; if (existingItem) { - existingItem.className = `aircraft-item ${selectedIcao === ac.icao ? 'selected' : ''}`; + existingItem.className = `aircraft-item ${selectedIcao === ac.icao ? 'selected' : ''} ${isOnWatchlist(ac) ? 'watched' : ''}`; existingItem.innerHTML = buildAircraftItemHTML(ac); } }); @@ -2180,6 +2299,34 @@ sudo make install + + + + + ★ WATCHLIST + × + + + + + Any match + Callsign + Registration + ICAO Hex + + + ADD + + + No entries. Add callsigns, registrations, or ICAO codes to watch. + + + + +