From 978e6cdaea13e0f61699f18bea3c549ab8848762 Mon Sep 17 00:00:00 2001 From: Smittix Date: Fri, 30 Jan 2026 15:40:10 +0000 Subject: [PATCH] fix: Resolve TSCM function strip visibility and clipping issues - Fix function strip content being clipped by changing overflow to visible - Add min-height and increased padding to function strip - Add explicit colors for TSCM strip stat values and labels - Fix output-panel overflow for TSCM mode using :has() selector - Add CSS variables --bg-dark and --bg-panel aliases - Clean up sidebar section margins for consistent spacing - Add unique IDs to WiFi/Bluetooth export sections for restore function Co-Authored-By: Claude Opus 4.5 --- static/css/components/function-strip.css | 38 +- static/css/core/layout.css | 2 +- static/css/core/variables.css | 8 + static/css/index.css | 96 +++++ static/css/modes/tscm.css | 9 +- static/js/modes/listening-post.js | 92 +++++ static/js/modes/wifi.js | 446 ++++++++++++++++++++++- templates/adsb_dashboard.html | 2 - templates/agents.html | 35 -- templates/ais_dashboard.html | 2 - templates/index.html | 6 +- templates/layout/base_dashboard.html | 2 - templates/network_monitor.html | 2 - templates/partials/modes/bluetooth.html | 5 +- templates/partials/modes/tscm.html | 12 +- templates/partials/modes/wifi.html | 37 +- templates/satellite_dashboard.html | 5 - 17 files changed, 704 insertions(+), 95 deletions(-) diff --git a/static/css/components/function-strip.css b/static/css/components/function-strip.css index 458c42f..a1a944f 100644 --- a/static/css/components/function-strip.css +++ b/static/css/components/function-strip.css @@ -6,9 +6,10 @@ background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%); border: 1px solid var(--border-color); border-radius: 6px; - padding: 6px 12px; + padding: 8px 12px; margin-bottom: 10px; - overflow-x: auto; + overflow: visible; + min-height: 44px; } .function-strip-inner { @@ -23,7 +24,7 @@ display: flex; flex-direction: column; align-items: center; - padding: 4px 10px; + padding: 6px 10px; background: rgba(74, 158, 255, 0.05); border: 1px solid rgba(74, 158, 255, 0.15); border-radius: 4px; @@ -307,16 +308,37 @@ color: var(--accent-orange); } +.function-strip.tscm-strip { + margin-top: 4px; /* Extra clearance to prevent top clipping */ +} + .function-strip.tscm-strip .strip-stat { - background: rgba(255, 59, 48, 0.05); - border-color: rgba(255, 59, 48, 0.15); + background: rgba(255, 59, 48, 0.15); + border: 1px solid rgba(255, 59, 48, 0.4); } .function-strip.tscm-strip .strip-stat:hover { - background: rgba(255, 59, 48, 0.1); - border-color: rgba(255, 59, 48, 0.3); + background: rgba(255, 59, 48, 0.25); + border-color: rgba(255, 59, 48, 0.6); } .function-strip.tscm-strip .strip-value { - color: var(--accent-red); + color: #ef4444; /* Explicit red color */ +} +.function-strip.tscm-strip .strip-label { + color: #9ca3af; /* Explicit light gray */ +} +.function-strip.tscm-strip .strip-select { + color: #e8eaed; /* Explicit white for selects */ + background: rgba(0, 0, 0, 0.4); +} +.function-strip.tscm-strip .strip-btn { + color: #e8eaed; /* Explicit white for buttons */ +} +.function-strip.tscm-strip .strip-tool { + color: #e8eaed; /* Explicit white for tool indicators */ +} +.function-strip.tscm-strip .strip-time, +.function-strip.tscm-strip .strip-status span { + color: #9ca3af; /* Explicit gray for status/time */ } .function-strip.rtlamr-strip .strip-stat { diff --git a/static/css/core/layout.css b/static/css/core/layout.css index a2a9845..e969223 100644 --- a/static/css/core/layout.css +++ b/static/css/core/layout.css @@ -593,7 +593,7 @@ border-bottom: 1px solid var(--border-color); padding: 0 20px; position: relative; - z-index: 10; + z-index: 100; } @media (min-width: 1024px) { diff --git a/static/css/core/variables.css b/static/css/core/variables.css index 67ffa54..32b79a0 100644 --- a/static/css/core/variables.css +++ b/static/css/core/variables.css @@ -17,6 +17,10 @@ --bg-elevated: #1a202c; --bg-overlay: rgba(0, 0, 0, 0.7); + /* Background aliases for components */ + --bg-dark: var(--bg-primary); + --bg-panel: var(--bg-secondary); + /* Accent colors */ --accent-cyan: #4a9eff; --accent-cyan-dim: rgba(74, 158, 255, 0.15); @@ -150,6 +154,10 @@ --bg-elevated: #f8fafc; --bg-overlay: rgba(255, 255, 255, 0.9); + /* Background aliases for components */ + --bg-dark: var(--bg-primary); + --bg-panel: var(--bg-secondary); + --accent-cyan: #2563eb; --accent-cyan-dim: rgba(37, 99, 235, 0.1); --accent-cyan-hover: #1d4ed8; diff --git a/static/css/index.css b/static/css/index.css index bb9952b..c171786 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -1414,6 +1414,7 @@ header h1 .tagline { overflow: visible; padding: 12px; position: relative; + margin: 0; /* Reset any inherited margins - spacing handled by parent gap */ } .section h3 { @@ -1590,6 +1591,101 @@ header h1 .tagline { border-color: var(--accent-cyan); } +/* WiFi Mode Tab Buttons */ +.wifi-mode-tab { + flex: 1; + padding: 8px; + font-size: 11px; + font-family: 'JetBrains Mono', monospace; + text-transform: uppercase; + letter-spacing: 0.05em; + background: var(--bg-tertiary); + color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; +} + +.wifi-mode-tab:hover { + background: var(--bg-secondary); + color: var(--text-primary); + border-color: var(--accent-cyan); +} + +.wifi-mode-tab.active { + background: var(--accent-green); + color: #000; + border-color: var(--accent-green); +} + +.wifi-mode-tab.active:hover { + background: #1db954; + border-color: #1db954; +} + +/* WiFi Start/Stop Buttons */ +.wifi-start-btn { + background: var(--accent-green) !important; + color: #000 !important; + border-color: var(--accent-green) !important; +} + +.wifi-start-btn:hover { + background: #1db954 !important; + border-color: #1db954 !important; + box-shadow: 0 2px 8px rgba(34, 197, 94, 0.3); +} + +.wifi-stop-btn { + background: var(--accent-red) !important; + color: #fff !important; + border-color: var(--accent-red) !important; +} + +.wifi-stop-btn:hover { + background: #e62e50 !important; + border-color: #e62e50 !important; + box-shadow: 0 2px 8px rgba(255, 51, 102, 0.3); +} + +/* WiFi Monitor Mode Buttons */ +.wifi-monitor-btn { + background: var(--accent-green) !important; + color: #000 !important; + border-color: var(--accent-green) !important; +} + +.wifi-monitor-btn:hover { + background: #1db954 !important; + border-color: #1db954 !important; + box-shadow: 0 2px 8px rgba(34, 197, 94, 0.3); +} + +.wifi-monitor-stop-btn { + background: var(--accent-orange) !important; + color: #000 !important; + border-color: var(--accent-orange) !important; +} + +.wifi-monitor-stop-btn:hover { + background: #e68a00 !important; + border-color: #e68a00 !important; + box-shadow: 0 2px 8px rgba(255, 159, 28, 0.3); +} + +/* WiFi Danger/Attack Buttons */ +.wifi-danger-btn { + border-color: var(--accent-red) !important; + color: var(--accent-red) !important; +} + +.wifi-danger-btn:hover { + background: var(--accent-red) !important; + color: #fff !important; + box-shadow: 0 2px 8px rgba(255, 51, 102, 0.3); +} + .run-btn { width: 100%; padding: 12px; diff --git a/static/css/modes/tscm.css b/static/css/modes/tscm.css index 981e103..bfe0509 100644 --- a/static/css/modes/tscm.css +++ b/static/css/modes/tscm.css @@ -32,12 +32,17 @@ .threat-card.low.active { background: rgba(0,255,136,0.2); } /* TSCM Dashboard */ +/* Ensure output-panel doesn't clip TSCM content */ +.output-panel:has(.tscm-dashboard) { + overflow: visible; +} + .tscm-dashboard { display: flex; flex-direction: column; gap: 16px; - overflow-y: auto; - padding-bottom: 80px; /* Space for status bar */ + overflow: visible; + padding: 20px 16px 80px 16px; /* Extra top padding for function strip visibility */ } .tscm-threat-banner { display: flex; diff --git a/static/js/modes/listening-post.js b/static/js/modes/listening-post.js index 1f991f4..ceb5a2b 100644 --- a/static/js/modes/listening-post.js +++ b/static/js/modes/listening-post.js @@ -227,6 +227,9 @@ function startScanner() { isScannerPaused = false; scannerSignalActive = false; + // Update listening strip + updateListeningStripRunning(true); + // Update controls (with null checks) const startBtn = document.getElementById('scannerStartBtn'); if (startBtn) { @@ -289,6 +292,9 @@ function stopScanner() { scannerSignalActive = false; currentSignalLevel = 0; + // Update listening strip + updateListeningStripRunning(false); + // Re-enable listen button (will be in local mode after stop) updateListenButtonState(false); @@ -572,6 +578,10 @@ function handleFrequencyUpdate(data) { const mainFreq = document.getElementById('mainScannerFreq'); if (mainFreq) mainFreq.textContent = freqStr; + // Update function strip frequency + const stripFreq = document.getElementById('listeningStripFreq'); + if (stripFreq) stripFreq.textContent = freqStr; + // Update progress bar const progress = ((data.frequency - scannerStartFreq) / (scannerEndFreq - scannerStartFreq)) * 100; const progressBar = document.getElementById('scannerProgressBar'); @@ -622,6 +632,10 @@ function handleSignalFound(data) { const mainSignalCount = document.getElementById('mainSignalCount'); if (mainSignalCount) mainSignalCount.textContent = scannerSignalCount; + // Update function strip signal count + const stripSignals = document.getElementById('listeningStripSignals'); + if (stripSignals) stripSignals.textContent = scannerSignalCount; + // Update sidebar updateScannerDisplay('SIGNAL FOUND', 'var(--accent-green)'); const signalPanel = document.getElementById('scannerSignalPanel'); @@ -2597,3 +2611,81 @@ window.tuneToFrequency = tuneToFrequency; window.clearScannerLog = clearScannerLog; window.exportScannerLog = exportScannerLog; +// ============== FUNCTION STRIP SUPPORT ============== + +/** + * Update the listening post function strip running state + */ +function updateListeningStripRunning(running) { + const listeningStripDot = document.getElementById('listeningStripDot'); + const listeningStripStatus = document.getElementById('listeningStripStatus'); + const listeningStripStartBtn = document.getElementById('listeningStripStartBtn'); + const listeningStripStopBtn = document.getElementById('listeningStripStopBtn'); + const listeningStripFreqInput = document.getElementById('listeningStripFreqInput'); + const listeningStripMode = document.getElementById('listeningStripMode'); + const listeningStripGain = document.getElementById('listeningStripGain'); + + if (listeningStripDot) listeningStripDot.className = 'status-dot ' + (running ? 'scanning' : 'inactive'); + if (listeningStripStatus) { + listeningStripStatus.textContent = running ? 'SCANNING' : 'STANDBY'; + listeningStripStatus.style.color = running ? 'var(--accent-cyan)' : ''; + } + if (listeningStripStartBtn) listeningStripStartBtn.style.display = running ? 'none' : 'inline-block'; + if (listeningStripStopBtn) listeningStripStopBtn.style.display = running ? 'inline-block' : 'none'; + if (listeningStripFreqInput) listeningStripFreqInput.disabled = running; + if (listeningStripMode) listeningStripMode.disabled = running; + if (listeningStripGain) listeningStripGain.disabled = running; +} + +/** + * Update listening strip stats + */ +function updateListeningStrip(freq, bandwidth, signalCount) { + const freqEl = document.getElementById('listeningStripFreq'); + const bwEl = document.getElementById('listeningStripBW'); + const signalsEl = document.getElementById('listeningStripSignals'); + + if (freqEl && freq !== undefined) freqEl.textContent = freq; + if (bwEl && bandwidth !== undefined) bwEl.textContent = bandwidth; + if (signalsEl && signalCount !== undefined) signalsEl.textContent = signalCount; +} + +/** + * Start listening from the function strip + */ +function startListeningFromStrip() { + // Get values from strip + const freq = document.getElementById('listeningStripFreqInput')?.value; + const mode = document.getElementById('listeningStripMode')?.value; + const gain = document.getElementById('listeningStripGain')?.value; + + // Update the main controls if they exist + if (freq) { + const mainFreqInput = document.getElementById('radioScanStart'); + if (mainFreqInput) mainFreqInput.value = freq; + } + if (mode) { + currentModulation = mode.toLowerCase(); + } + if (gain) { + const gainValueEl = document.getElementById('radioGainValue'); + if (gainValueEl) gainValueEl.textContent = gain; + } + + // Start the scanner + startScanner(); +} + +/** + * Stop listening from the function strip + */ +function stopListening() { + stopScanner(); +} + +// Export strip functions +window.updateListeningStripRunning = updateListeningStripRunning; +window.updateListeningStrip = updateListeningStrip; +window.startListeningFromStrip = startListeningFromStrip; +window.stopListening = stopListening; + diff --git a/static/js/modes/wifi.js b/static/js/modes/wifi.js index 5dd2920..8eb2bbf 100644 --- a/static/js/modes/wifi.js +++ b/static/js/modes/wifi.js @@ -336,6 +336,9 @@ const WiFiMode = (function() { if (elements.scanModeDeep) { elements.scanModeDeep.addEventListener('click', () => setScanMode('deep')); } + + // Initialize button visibility (default to quick mode) + setScanMode('quick'); } function setScanMode(mode) { @@ -349,6 +352,21 @@ const WiFiMode = (function() { elements.scanModeDeep.classList.toggle('active', mode === 'deep'); } + // Update button visibility based on mode + if (!isScanning) { + if (elements.quickScanBtn) { + elements.quickScanBtn.style.display = mode === 'quick' ? 'inline-block' : 'none'; + elements.quickScanBtn.textContent = 'Start Quick Scan'; + } + if (elements.deepScanBtn) { + elements.deepScanBtn.style.display = mode === 'deep' ? 'inline-block' : 'none'; + elements.deepScanBtn.textContent = 'Start Deep Scan'; + } + if (elements.stopScanBtn) { + elements.stopScanBtn.style.display = 'none'; + } + } + console.log('[WiFiMode] Scan mode set to:', mode); } @@ -357,7 +375,10 @@ const WiFiMode = (function() { // ========================================================================== async function startQuickScan() { - if (isScanning) return; + if (isScanning) { + showInfo('Scan already in progress'); + return; + } // Check for agent mode conflicts if (!checkAgentConflicts()) { @@ -365,6 +386,7 @@ const WiFiMode = (function() { } console.log('[WiFiMode] Starting quick scan...'); + showInfo('Starting quick scan...'); setScanning(true, 'quick'); try { @@ -436,6 +458,9 @@ const WiFiMode = (function() { // Process results processQuickScanResult({ ...scanResult, access_points: accessPoints }); + // Show success notification + showInfo(`Found ${accessPoints.length} network${accessPoints.length !== 1 ? 's' : ''}`); + // For quick scan, we're done after one scan // But keep polling if user wants continuous updates if (scanMode === 'quick') { @@ -449,7 +474,10 @@ const WiFiMode = (function() { } async function startDeepScan() { - if (isScanning) return; + if (isScanning) { + showInfo('Scan already in progress'); + return; + } // Check for agent mode conflicts if (!checkAgentConflicts()) { @@ -457,6 +485,7 @@ const WiFiMode = (function() { } console.log('[WiFiMode] Starting deep scan...'); + showInfo('Starting deep scan (requires monitor mode)...'); setScanning(true, 'deep'); try { @@ -516,6 +545,7 @@ const WiFiMode = (function() { async function stopScan() { console.log('[WiFiMode] Stopping scan...'); + showInfo('Stopping scan...'); // Stop polling if (pollTimer) { @@ -538,6 +568,7 @@ const WiFiMode = (function() { } else if (scanMode === 'deep') { await fetch(`${CONFIG.apiBase}/scan/stop`, { method: 'POST' }); } + showInfo('Scan stopped'); } catch (error) { console.warn('[WiFiMode] Error stopping scan:', error); } @@ -549,15 +580,21 @@ const WiFiMode = (function() { isScanning = scanning; if (mode) scanMode = mode; - // Update buttons - if (elements.quickScanBtn) { - elements.quickScanBtn.style.display = scanning ? 'none' : 'inline-block'; - } - if (elements.deepScanBtn) { - elements.deepScanBtn.style.display = scanning ? 'none' : 'inline-block'; - } - if (elements.stopScanBtn) { - elements.stopScanBtn.style.display = scanning ? 'inline-block' : 'none'; + // Update buttons based on scanning state and current mode + if (scanning) { + // Scanning: hide start buttons, show stop button + if (elements.quickScanBtn) elements.quickScanBtn.style.display = 'none'; + if (elements.deepScanBtn) elements.deepScanBtn.style.display = 'none'; + if (elements.stopScanBtn) elements.stopScanBtn.style.display = 'inline-block'; + } else { + // Not scanning: show appropriate start button based on mode, hide stop + if (elements.quickScanBtn) { + elements.quickScanBtn.style.display = scanMode === 'quick' ? 'inline-block' : 'none'; + } + if (elements.deepScanBtn) { + elements.deepScanBtn.style.display = scanMode === 'deep' ? 'inline-block' : 'none'; + } + if (elements.stopScanBtn) elements.stopScanBtn.style.display = 'none'; } // Update status @@ -1426,3 +1463,390 @@ document.addEventListener('DOMContentLoaded', () => { WiFiMode.init(); } }); + +// ============================================================================= +// WiFi Helper Functions +// Implements UI helper functions for Monitor Mode, Deauth, Watch List, etc. +// ============================================================================= + +const WiFiHelpers = (function() { + 'use strict'; + + // Note: Monitor mode and attack endpoints are in /wifi/ (v1), not /wifi/v2/ + const CONFIG = { + apiBase: '/wifi/v2', + apiV1Base: '/wifi', + }; + + // Watch list state + let watchList = []; + + // ========================================================================== + // Monitor Mode + // ========================================================================== + + async function enableMonitorMode() { + const iface = document.getElementById('wifiInterfaceSelect')?.value; + const killProcesses = document.getElementById('killProcesses')?.checked || false; + const startBtn = document.getElementById('monitorStartBtn'); + const stopBtn = document.getElementById('monitorStopBtn'); + const statusEl = document.getElementById('monitorStatus'); + + // Provide immediate feedback + console.log('[WiFiHelpers] Enable monitor mode clicked'); + showNotification('Monitor Mode', 'Enabling monitor mode...', 'info'); + + if (!iface) { + showNotification('Monitor Mode', 'No interface selected. Please wait for interface detection.', 'error'); + return; + } + + // Update UI to show processing + if (startBtn) { + startBtn.disabled = true; + startBtn.textContent = 'Enabling...'; + } + + try { + // Use v1 API which has monitor mode support + const response = await fetch(`${CONFIG.apiV1Base}/monitor`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + interface: iface, + action: 'enable', + kill_processes: killProcesses + }), + }); + + const result = await response.json(); + + if (!response.ok || result.error) { + throw new Error(result.error || result.message || 'Failed to enable monitor mode'); + } + + // Success - update UI + if (startBtn) { + startBtn.style.display = 'none'; + startBtn.disabled = false; + startBtn.textContent = 'Enable Monitor'; + } + if (stopBtn) stopBtn.style.display = 'inline-block'; + if (statusEl) { + statusEl.innerHTML = `Monitor mode: Active (${result.monitor_interface || iface})`; + } + + showNotification('Monitor Mode', `Enabled on ${result.monitor_interface || iface}`, 'success'); + } catch (error) { + console.error('[WiFiHelpers] Enable monitor mode error:', error); + showNotification('Monitor Mode', error.message, 'error'); + + // Reset button + if (startBtn) { + startBtn.disabled = false; + startBtn.textContent = 'Enable Monitor'; + } + } + } + + async function disableMonitorMode() { + const iface = document.getElementById('wifiInterfaceSelect')?.value; + const startBtn = document.getElementById('monitorStartBtn'); + const stopBtn = document.getElementById('monitorStopBtn'); + const statusEl = document.getElementById('monitorStatus'); + + console.log('[WiFiHelpers] Disable monitor mode clicked'); + showNotification('Monitor Mode', 'Disabling monitor mode...', 'info'); + + if (stopBtn) { + stopBtn.disabled = true; + stopBtn.textContent = 'Disabling...'; + } + + try { + // Use v1 API which has monitor mode support + const response = await fetch(`${CONFIG.apiV1Base}/monitor`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + interface: iface, + action: 'disable' + }), + }); + + const result = await response.json(); + + if (!response.ok || result.error) { + throw new Error(result.error || result.message || 'Failed to disable monitor mode'); + } + + // Success - update UI + if (stopBtn) { + stopBtn.style.display = 'none'; + stopBtn.disabled = false; + stopBtn.textContent = 'Disable Monitor'; + } + if (startBtn) startBtn.style.display = 'inline-block'; + if (statusEl) { + statusEl.innerHTML = `Monitor mode: Inactive`; + } + + showNotification('Monitor Mode', 'Disabled successfully', 'info'); + } catch (error) { + console.error('[WiFiHelpers] Disable monitor mode error:', error); + showNotification('Monitor Mode', error.message, 'error'); + + // Reset button + if (stopBtn) { + stopBtn.disabled = false; + stopBtn.textContent = 'Disable Monitor'; + } + } + } + + // ========================================================================== + // Deauth Attack + // ========================================================================== + + async function sendDeauth() { + const bssid = document.getElementById('targetBssid')?.value?.trim(); + const client = document.getElementById('targetClient')?.value?.trim() || 'FF:FF:FF:FF:FF:FF'; + const count = parseInt(document.getElementById('deauthCount')?.value) || 5; + const iface = document.getElementById('wifiInterfaceSelect')?.value; + + console.log('[WiFiHelpers] Send deauth clicked'); + + if (!bssid || !/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/.test(bssid)) { + showNotification('Deauth Attack', 'Please enter a valid target BSSID (e.g., AA:BB:CC:DD:EE:FF)', 'error'); + return; + } + + // Confirm action + if (!confirm(`Send ${count} deauth frames to ${bssid}?\n\nWARNING: Only use on networks you are authorized to test.`)) { + return; + } + + showNotification('Deauth Attack', `Sending ${count} deauth frames...`, 'info'); + + try { + // Use v1 API which has deauth support + const response = await fetch(`${CONFIG.apiV1Base}/deauth`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + bssid: bssid, + client: client, + count: count, + interface: iface + }), + }); + + const result = await response.json(); + + if (!response.ok || result.error) { + throw new Error(result.error || result.message || 'Deauth attack failed'); + } + + showNotification('Deauth Attack', `Sent ${count} deauth frames to ${bssid}`, 'success'); + } catch (error) { + console.error('[WiFiHelpers] Deauth error:', error); + showNotification('Deauth Attack', error.message, 'error'); + } + } + + // ========================================================================== + // Watch List (Proximity Alerts) + // ========================================================================== + + function addWatchMac() { + const input = document.getElementById('watchMacInput'); + const mac = input?.value?.trim().toUpperCase(); + + console.log('[WiFiHelpers] Add watch MAC clicked, value:', mac); + + if (!mac) { + showNotification('Watch List', 'Please enter a MAC address', 'error'); + return; + } + + if (!/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/.test(mac)) { + showNotification('Watch List', 'Invalid format. Use XX:XX:XX:XX:XX:XX', 'error'); + return; + } + + if (watchList.includes(mac)) { + showNotification('Watch List', 'MAC address already in watch list', 'info'); + return; + } + + watchList.push(mac); + input.value = ''; + updateWatchListDisplay(); + showNotification('Watch List', `Added ${mac} to watch list`, 'success'); + + // Save to localStorage + try { + localStorage.setItem('wifiWatchList', JSON.stringify(watchList)); + } catch (e) { + console.warn('[WiFiHelpers] Could not save watch list to localStorage'); + } + } + + function removeWatchMac(mac) { + watchList = watchList.filter(m => m !== mac); + updateWatchListDisplay(); + + // Save to localStorage + try { + localStorage.setItem('wifiWatchList', JSON.stringify(watchList)); + } catch (e) { + console.warn('[WiFiHelpers] Could not save watch list to localStorage'); + } + } + + function updateWatchListDisplay() { + const container = document.getElementById('watchList'); + if (!container) return; + + if (watchList.length === 0) { + container.innerHTML = 'No MAC addresses in watch list'; + return; + } + + container.innerHTML = watchList.map(mac => ` +
+ ${mac} + +
+ `).join(''); + } + + function loadWatchList() { + try { + const saved = localStorage.getItem('wifiWatchList'); + if (saved) { + watchList = JSON.parse(saved); + updateWatchListDisplay(); + } + } catch (e) { + console.warn('[WiFiHelpers] Could not load watch list from localStorage'); + } + } + + // ========================================================================== + // Handshake Capture + // ========================================================================== + + async function checkCaptureStatus() { + const statusEl = document.getElementById('captureStatus'); + const bssid = document.getElementById('captureTargetBssid')?.textContent; + + console.log('[WiFiHelpers] Check capture status clicked'); + showNotification('Handshake Capture', 'Checking capture status...', 'info'); + + try { + // Use v1 API which has handshake capture support + const response = await fetch(`${CONFIG.apiV1Base}/handshake/status`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ bssid: bssid }), + }); + const result = await response.json(); + + if (statusEl) { + if (result.capturing || result.running) { + statusEl.textContent = result.handshake_found ? 'Handshake captured!' : 'Capturing...'; + statusEl.style.color = result.handshake_found ? 'var(--accent-green)' : 'var(--accent-orange)'; + } else { + statusEl.textContent = 'Not capturing'; + statusEl.style.color = 'var(--text-secondary)'; + } + } + + if (result.handshake_found) { + showNotification('Handshake Capture', `Handshake captured! File: ${result.capture_file || 'check captures folder'}`, 'success'); + } else if (result.capturing || result.running) { + showNotification('Handshake Capture', 'Still capturing, no handshake yet...', 'info'); + } else { + showNotification('Handshake Capture', 'No active capture', 'info'); + } + } catch (error) { + console.error('[WiFiHelpers] Check capture status error:', error); + showNotification('Handshake Capture', error.message, 'error'); + if (statusEl) { + statusEl.textContent = 'Error checking status'; + statusEl.style.color = 'var(--accent-red)'; + } + } + } + + async function stopHandshakeCapture() { + const panel = document.getElementById('captureStatusPanel'); + const statusEl = document.getElementById('captureStatus'); + + console.log('[WiFiHelpers] Stop handshake capture clicked'); + showNotification('Handshake Capture', 'Stopping capture...', 'info'); + + try { + // Use v1 API - handshake stop endpoint + const response = await fetch(`${CONFIG.apiV1Base}/scan/stop`, { + method: 'POST', + }); + + const result = await response.json(); + + if (!response.ok || result.error) { + throw new Error(result.error || result.message || 'Failed to stop capture'); + } + + if (statusEl) { + statusEl.textContent = 'Stopped'; + statusEl.style.color = 'var(--text-secondary)'; + } + + // Hide panel after delay + setTimeout(() => { + if (panel) panel.style.display = 'none'; + }, 2000); + + showNotification('Handshake Capture', 'Capture stopped', 'success'); + } catch (error) { + console.error('[WiFiHelpers] Stop capture error:', error); + showNotification('Handshake Capture', error.message, 'error'); + } + } + + // ========================================================================== + // Utility + // ========================================================================== + + function showNotification(title, message, type) { + if (typeof window.showNotification === 'function') { + window.showNotification(title, message, type); + } else { + console.log(`[${type.toUpperCase()}] ${title}: ${message}`); + } + } + + // ========================================================================== + // Initialize + // ========================================================================== + + // Load watch list on page load + document.addEventListener('DOMContentLoaded', loadWatchList); + + // ========================================================================== + // Public API + // ========================================================================== + + return { + enableMonitorMode, + disableMonitorMode, + sendDeauth, + addWatchMac, + removeWatchMac, + checkCaptureStatus, + stopHandshakeCapture, + getWatchList: () => [...watchList], + }; +})(); diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 560525b..c21ba2f 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -47,8 +47,6 @@ All - Back - Main Dashboard diff --git a/templates/agents.html b/templates/agents.html index 87a7a0f..6c803fb 100644 --- a/templates/agents.html +++ b/templates/agents.html @@ -223,26 +223,6 @@ opacity: 0.5; } - /* Navigation links */ - .nav-links { - display: flex; - gap: 16px; - margin-bottom: 20px; - } - - .back-link { - display: inline-flex; - align-items: center; - gap: 8px; - color: var(--accent-cyan); - text-decoration: none; - font-size: 14px; - } - - .back-link:hover { - text-decoration: underline; - } - /* Toast notifications */ .toast { position: fixed; @@ -301,21 +281,6 @@
-

Remote Agents

diff --git a/templates/ais_dashboard.html b/templates/ais_dashboard.html index c346882..a4959a7 100644 --- a/templates/ais_dashboard.html +++ b/templates/ais_dashboard.html @@ -48,8 +48,6 @@ All
- Back - Main Dashboard
diff --git a/templates/index.html b/templates/index.html index 60295e4..841ee38 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1500,7 +1500,7 @@ -