(hostData)
| 4218 | body: JSON.stringify({ action: 'disable' }) }) |
| 4219 | .then(r => r.json()).then(d => { _wifidef.monitor = null; _wifidefUpdateMonBtn(); st.textContent = 'Monitor disabled'; }).catch(() => {}); |
| 4220 | return; |
| 4221 | } |
| 4222 | const iface = _wifidef.iface || document.getElementById('wifidef-iface').value; |
| 4223 | if (!iface) return; |
| 4224 | st.textContent = 'Enabling monitor…'; |
| 4225 | fetch('/api/wifidef/monitor', { method: 'POST', headers: { 'Content-Type': 'application/json' }, |
| 4226 | body: JSON.stringify({ action: 'enable', interface: iface }) }) |
| 4227 | .then(r => r.json()).then(d => { |
| 4228 | if (d.error) { st.textContent = '⚠ ' + d.error; return; } |
| 4229 | _wifidef.monitor = d.mon_iface; |
| 4230 | _wifidefUpdateMonBtn(); |
| 4231 | st.textContent = d.warning ? ('⚠ ' + d.warning) : ('Monitor up on ' + d.mon_iface + (d.mode === 'vif' ? ' (link kept)' : '')); |
| 4232 | }).catch(() => { st.textContent = 'Enable failed'; }); |
| 4233 | } |
| 4234 | |
| 4235 | // ---- Capture progress (live countdown for fixed-length captures) ----------- |
| 4236 | // Every WiFi Defense action captures for a KNOWN number of seconds, so show a |
| 4237 | // real countdown + progress bar while the request is in flight. Past the |
| 4238 | // capture window (monitor setup, channel tuning, analysis) it switches to a |
| 4239 | // pulsing "analyzing…" state, so the user always sees work happening. Returns |
| 4240 | // a handle whose done() stops the ticker; write the final status after that. |
| 4241 | function _wifidefProgress(st, secs, label) { |
| 4242 | if (!st) return { done: () => {} }; |
| 4243 | const t0 = Date.now(), total = Math.max(1, secs) * 1000; |
| 4244 | // Track colour is inline — bg-slate-700/80 isn't in the compiled Tailwind. |
| 4245 | const bar = (pct, pulse, text) => |
| 4246 | '<span class="inline-flex items-center gap-1.5 align-middle">' |
| 4247 | + '<span class="inline-block w-28 h-1.5 rounded overflow-hidden" style="background:rgba(51,65,85,.8)">' |
| 4248 | + `<span class="block h-full rounded bg-Ragnar-500${pulse ? ' animate-pulse' : ''}" style="width:${pct}%"></span></span>` |
| 4249 | + `<span>${text}</span></span>`; |
| 4250 | const render = () => { |
| 4251 | const el = Date.now() - t0; |
| 4252 | if (el < total) { |
| 4253 | const left = Math.ceil((total - el) / 1000); |
| 4254 | st.innerHTML = bar(Math.min(100, Math.round(el / total * 100)), false, |
| 4255 | `${label} · ${left}s left`); |
| 4256 | } else { |
| 4257 | st.innerHTML = bar(100, true, 'capture done — analyzing…'); |
| 4258 | } |
| 4259 | }; |
| 4260 | render(); |
| 4261 | const timer = setInterval(render, 500); |
| 4262 | return { done() { clearInterval(timer); } }; |
| 4263 | } |
| 4264 | |
| 4265 | function wifidefScan() { |
| 4266 | const iface = _wifidef.iface || document.getElementById('wifidef-iface').value; |
| 4267 | if (!iface) return Promise.resolve(); |
| 4268 | const secs = document.getElementById('wifidef-secs').value || 15; |
| 4269 | let ch = (document.getElementById('wifidef-channel').value || 'hop').trim(); |
| 4270 | const chParam = /^\d+$/.test(ch) ? ch : 'auto'; |
| 4271 | const st = document.getElementById('wifidef-status'); |
| 4272 | const btn = document.getElementById('wifidef-scan-btn'); |
| 4273 | const ctrl = new AbortController(); |
| 4274 | _wifidef.abort = ctrl; |
| 4275 | const prog = _wifidefProgress(st, parseInt(secs) || 15, |
| 4276 | (_wifidef.continuous ? 'Continuous — capturing' : 'Capturing') + (chParam !== 'auto' ? ' ch ' + chParam : '')); |
| 4277 | if (btn) btn.disabled = true; |
no test coverage detected