()
| 11235 | } else { |
| 11236 | badge.className = 'inline-flex items-center mt-1 text-xs font-medium px-2 py-0.5 rounded-full bg-yellow-900/50 text-yellow-300 border border-yellow-700'; |
| 11237 | badge.innerHTML = '⚠ No monitor adapter — limited deauth detection'; |
| 11238 | } |
| 11239 | } |
| 11240 | |
| 11241 | async function runThreatSweep() { |
| 11242 | const btn = document.getElementById('threat-sweep-btn'); |
| 11243 | const panel = document.getElementById('threat-sweep-results'); |
| 11244 | if (!btn || !panel) return; |
| 11245 | |
| 11246 | // Show scanning state |
| 11247 | const origHTML = btn.innerHTML; |
| 11248 | btn.disabled = true; |
| 11249 | btn.classList.add('opacity-60'); |
| 11250 | btn.innerHTML = `<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path></svg><span>Scanning airspace…</span>`; |
| 11251 | |
| 11252 | panel.classList.remove('hidden'); |
| 11253 | panel.className = 'mb-4 rounded-lg border border-slate-700 bg-slate-900/50 p-4'; |
| 11254 | panel.innerHTML = `<div class="flex items-center space-x-2 text-gray-300 text-sm"><svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path></svg><span>Scanning WiFi airspace for rogue APs, evil twins, and suspicious devices… (~10s)</span></div>`; |
| 11255 | |
| 11256 | try { |
| 11257 | const ifSel = document.getElementById('threat-sweep-iface'); |
| 11258 | const iface = ifSel ? ifSel.value : ''; |
| 11259 | const resp = await fetch('/api/network/threat-sweep', { |
| 11260 | method: 'POST', |
| 11261 | headers: { 'Content-Type': 'application/json' }, |
| 11262 | body: JSON.stringify(iface ? { interface: iface } : {}), |
| 11263 | }); |
| 11264 | const data = await resp.json(); |
| 11265 | |
| 11266 | if (!data.success) { |
| 11267 | panel.className = 'mb-4 rounded-lg border border-yellow-800 bg-yellow-950/30 p-4'; |
| 11268 | panel.innerHTML = `<p class="text-yellow-300 text-sm">⚠ Scan failed: ${escapeHtml(data.error || 'Unknown error')}</p>`; |
| 11269 | return; |
| 11270 | } |
| 11271 | |
| 11272 | _renderThreatFindings(data, panel); |
| 11273 | _updateMonitorModeBadge(data.monitor_mode); |
| 11274 | |
| 11275 | } catch (err) { |
| 11276 | panel.className = 'mb-4 rounded-lg border border-red-800 bg-red-950/30 p-4'; |
| 11277 | panel.innerHTML = `<p class="text-red-300 text-sm">⚠ Error: ${escapeHtml(err.message || 'Request failed')}</p>`; |
| 11278 | } finally { |
| 11279 | btn.disabled = false; |
| 11280 | btn.classList.remove('opacity-60'); |
| 11281 | btn.innerHTML = origHTML; |
| 11282 | } |
| 11283 | } |
| 11284 | |
| 11285 | // --------------------------------------------------------------------------- |
| 11286 | // Continuous threat monitoring toggle + polling |
| 11287 | // --------------------------------------------------------------------------- |
| 11288 | async function toggleThreatMonitor() { |
| 11289 | const toggle = document.getElementById('threat-monitor-toggle'); |
| 11290 | const warning = document.getElementById('threat-monitor-warning'); |
| 11291 | const statusEl = document.getElementById('threat-monitor-status'); |
| 11292 | const panel = document.getElementById('threat-sweep-results'); |
| 11293 | const intervalInput = document.getElementById('threat-monitor-interval'); |
| 11294 |
nothing calls this directly
no test coverage detected