(ip, hostLogs)
| 4977 | body: JSON.stringify(body) }) |
| 4978 | .then(r => r.json()).then(d => { |
| 4979 | prog.done(); |
| 4980 | st.textContent = d.error ? ('⚠ ' + d.error) |
| 4981 | : (`✓ Trusted — baseline now covers ${d.ssids} SSID(s)` + (d.added ? ` (+${d.added} added)` : '') + '. Hit Scan to re-check.'); |
| 4982 | }).catch(() => { prog.done(); st.textContent = 'Baseline failed'; }); |
| 4983 | } |
| 4984 | |
| 4985 | function wifidefSetThreshold() { |
| 4986 | const el = document.getElementById('wifidef-flood-ssids'); |
| 4987 | const st = document.getElementById('wifidef-status'); |
| 4988 | const v = parseInt(el.value); |
| 4989 | if (!v || v < 10) return; |
| 4990 | fetch('/api/wifidef/thresholds', { method: 'POST', headers: { 'Content-Type': 'application/json' }, |
| 4991 | body: JSON.stringify({ beacon_ssids: v }) }) |
| 4992 | .then(r => r.json()).then(t => { |
| 4993 | if (t && t.beacon_ssids) el.value = t.beacon_ssids; |
| 4994 | if (st) st.textContent = `Beacon-flood threshold = ${t.beacon_ssids} SSIDs (applies on next scan)`; |
| 4995 | }).catch(() => {}); |
| 4996 | } |
| 4997 | |
| 4998 | function wifidefResetBaseline() { |
| 4999 | const st = document.getElementById('wifidef-status'); |
| 5000 | if (!confirm('Clear the trusted-AP baseline? Evil-twin detection will have nothing to compare against until you Trust again.')) return; |
| 5001 | fetch('/api/wifidef/baseline', { method: 'POST', headers: { 'Content-Type': 'application/json' }, |
| 5002 | body: JSON.stringify({ action: 'clear' }) }) |
| 5003 | .then(r => r.json()).then(() => { st.textContent = 'Baseline cleared'; }) |
| 5004 | .catch(() => { st.textContent = 'Clear failed'; }); |
| 5005 | } |
| 5006 | |
| 5007 | function wifidefAirtime() { |
| 5008 | const iface = (document.getElementById('wifidef-at-iface') || {}).value |
| 5009 | || _wifidef.iface || document.getElementById('wifidef-iface').value; |
| 5010 | if (!iface) return; |
| 5011 | const secs = document.getElementById('wifidef-at-secs').value || 10; |
| 5012 | const chRaw = (document.getElementById('wifidef-at-channel').value || '').trim(); |
| 5013 | const ch = /^\d+$/.test(chRaw) ? chRaw : ''; |
| 5014 | const st = document.getElementById('wifidef-at-status'); |
| 5015 | const btn = document.getElementById('wifidef-at-btn'); |
| 5016 | const prog = _wifidefProgress(st, parseInt(secs) || 10, |
| 5017 | 'Capturing' + (ch ? ' ch ' + ch : ' (hopping)')); |
| 5018 | if (btn) btn.disabled = true; |
| 5019 | fetch(`/api/wifidef/airtime?interface=${encodeURIComponent(iface)}&seconds=${secs}${ch ? '&channel=' + ch : ''}`) |
| 5020 | .then(r => r.json()).then(d => { |
| 5021 | prog.done(); |
| 5022 | if (btn) btn.disabled = false; |
| 5023 | if (d.error) { st.textContent = '⚠ ' + d.error; return; } |
| 5024 | st.textContent = `${d.frames} frames${d.hopping ? ' · hopping (airtime % approx)' : ''}`; |
| 5025 | _wifidef.airtime = d; // stash for the unified AI read + report |
| 5026 | wifidefRenderAirtime(d); |
| 5027 | }).catch(() => { prog.done(); if (btn) btn.disabled = false; st.textContent = 'Airtime scan failed'; }); |
| 5028 | } |
| 5029 | |
| 5030 | function wifidefRenderAirtime(d) { |
| 5031 | const fnd = document.getElementById('wifidef-at-findings'); |
| 5032 | fnd.innerHTML = (d.findings || []).map(f => { |
| 5033 | const cls = f.type === 'roaming_churn' ? 'bg-amber-600/20 text-amber-300 border-amber-700/50' |
| 5034 | : f.type === 'airtime_hog' ? 'bg-orange-600/20 text-orange-300 border-orange-700/50' |
| 5035 | : f.type === 'slow_client' ? 'bg-red-600/20 text-red-300 border-red-700/50' |
| 5036 | : (f.type === 'erp_protection' || f.type === 'ht_protection_mixed') ? 'bg-fuchsia-600/20 text-fuchsia-300 border-fuchsia-700/50' |
no test coverage detected