()
| 2495 | <polyline points="${pts}" fill="none" stroke="${_wifiSignalColor(last)}" stroke-width="1" stroke-linejoin="round"/></svg>`; |
| 2496 | } |
| 2497 | |
| 2498 | function wifiRenderChanges() { |
| 2499 | const el = document.getElementById('wifi-changes'); |
| 2500 | if (!el) return; |
| 2501 | const c = (_wifiState.data && _wifiState.data.changes) || {}; |
| 2502 | const chips = []; |
| 2503 | (c.new_aps || []).forEach(a => |
| 2504 | chips.push(`<span class="px-2 py-1 rounded bg-emerald-600/20 text-emerald-300 border border-emerald-700/50" title="${a.bssid}">+ new: ${a.ssid || '<hidden>'} <span class="opacity-60">${a.band}G ch${a.channel} ${a.signal}dBm</span></span>`)); |
| 2505 | (c.gone_aps || []).forEach(a => |
| 2506 | chips.push(`<span class="px-2 py-1 rounded bg-slate-700/40 text-slate-400 border border-slate-600/50" title="${a.bssid}">- gone: ${a.ssid || '<hidden>'}</span>`)); |
| 2507 | (c.weakened || []).forEach(a => |
| 2508 | chips.push(`<span class="px-2 py-1 rounded bg-amber-600/20 text-amber-300 border border-amber-700/50" title="${a.bssid} — ${a.drop}dB below its peak ${a.rssi_max}dBm">▼ weakened: ${a.ssid || '<hidden>'} <span class="opacity-60">${a.signal}dBm (−${a.drop})</span></span>`)); |
| 2509 | el.innerHTML = chips.length |
| 2510 | ? `<span class="text-gray-500 self-center mr-1">Since last scan:</span>${chips.join('')}` |
| 2511 | : ''; |
| 2512 | } |
| 2513 | |
| 2514 | function wifiSetApView(v) { |
| 2515 | _wifiState.apView = v; |
| 2516 | document.getElementById('wifi-view-aps').classList.toggle('bg-Ragnar-600', v === 'aps'); |
| 2517 | document.getElementById('wifi-view-aps').classList.toggle('text-white', v === 'aps'); |
| 2518 | document.getElementById('wifi-view-nets').classList.toggle('bg-Ragnar-600', v === 'nets'); |
| 2519 | document.getElementById('wifi-view-nets').classList.toggle('text-white', v === 'nets'); |
| 2520 | document.getElementById('wifi-ap-table').classList.toggle('hidden', v !== 'aps'); |
| 2521 | document.getElementById('wifi-nets-view').classList.toggle('hidden', v !== 'nets'); |
| 2522 | wifiRenderTable(); |
| 2523 | } |
| 2524 | |
| 2525 | function wifiSort(key) { |
| 2526 | if (_wifiState.sortKey === key) _wifiState.sortDir *= -1; |
| 2527 | else { _wifiState.sortKey = key; _wifiState.sortDir = (key === 'ssid' || key === 'vendor') ? 1 : -1; } |
| 2528 | wifiRenderTable(); |
| 2529 | _wifiFsRenderList(); |
| 2530 | } |
| 2531 | |
| 2532 | // Filter + sort the survey the same way for every list that shows it (the |
| 2533 | // in-page table and the full-screen console). |
| 2534 | function _wifiFilterAps(q, std, issuesOnly) { |
| 2535 | const d = _wifiState.data; if (!d) return []; |
| 2536 | q = (q || '').toLowerCase(); |
| 2537 | const aps = d.aps.filter(a => |
| 2538 | (!q || (a.ssid || '').toLowerCase().includes(q) || (a.vendor || '').toLowerCase().includes(q) || a.bssid.includes(q)) |
| 2539 | && (!issuesOnly || (a.security_findings && a.security_findings.length)) |
| 2540 | && (!std || a.standard === std)); |
| 2541 | const k = _wifiState.sortKey || 'signal', dir = _wifiState.sortDir || -1; |
| 2542 | return aps.slice().sort((x, y) => { |
| 2543 | let a = x[k], b = y[k]; |
| 2544 | if (a == null) a = (typeof b === 'number') ? -9999 : ''; |
| 2545 | if (b == null) b = (typeof a === 'number') ? -9999 : ''; |
| 2546 | return (a < b ? -1 : a > b ? 1 : 0) * dir; |
| 2547 | }); |
| 2548 | } |
| 2549 | |
| 2550 | function wifiRenderTable() { |
| 2551 | const d = _wifiState.data; if (!d) return; |
| 2552 | if (_wifiState.apView === 'nets') return wifiRenderNets(); |
| 2553 | const aps = _wifiFilterAps( |
| 2554 | document.getElementById('wifi-ap-search').value, |
no test coverage detected