(data)
| 3437 | else _wifiHmPersistWalls(); |
| 3438 | } |
| 3439 | _wifiHm.suppressClick = true; // swallow the click that follows this press |
| 3440 | _wifiHm.drag = null; cv.style.cursor = ''; |
| 3441 | try { cv.releasePointerCapture(e.pointerId); } catch (_) {} |
| 3442 | wifiHeatmapRender(); |
| 3443 | }; |
| 3444 | cv.addEventListener('pointerup', endDrag); |
| 3445 | cv.addEventListener('pointercancel', endDrag); |
| 3446 | } |
| 3447 | } |
| 3448 | |
| 3449 | function wifiThroughputTest() { |
| 3450 | const st = document.getElementById('wifi-hm-tp-status'); |
| 3451 | const iperf = (document.getElementById('wifi-hm-iperf').value || '').trim(); |
| 3452 | const secs = parseInt(document.getElementById('wifi-hm-tp-secs').value) || 5; |
| 3453 | st.textContent = 'Testing (~' + (iperf ? secs * 2 + 5 : secs + 3) + 's)…'; |
| 3454 | fetch('/api/net/wifi/heatmap', { method: 'POST', headers: { 'Content-Type': 'application/json' }, |
| 3455 | body: JSON.stringify({ action: 'throughput', iperf_server: iperf, seconds: secs }) }) |
| 3456 | .then(r => r.json()).then(d => { |
| 3457 | const dn = d.down_mbps != null ? d.down_mbps + '↓' : '—'; |
| 3458 | const up = d.up_mbps != null ? ' ' + d.up_mbps + '↑' : ''; |
| 3459 | const lat = d.latency_ms != null ? ` · ${d.latency_ms}ms` : ''; |
| 3460 | const loss = d.loss_pct ? ` · ${d.loss_pct}% loss` : ''; |
| 3461 | st.innerHTML = `<b>${dn}${up}</b> Mbps${lat}${loss} <span class="text-gray-600">(${d.method})</span>` |
| 3462 | + (d.error ? ` <span class="text-amber-400">⚠ ${d.error}</span>` : ''); |
| 3463 | }).catch(() => { st.textContent = 'test failed'; }); |
| 3464 | } |
| 3465 | |
| 3466 | // ---- Design / predictive coverage ------------------------------------------ |
| 3467 | function wifiHmSetMode(m) { |
| 3468 | _wifiHm.mode = m; |
| 3469 | const survey = m === 'survey'; |
| 3470 | document.getElementById('wifi-hm-mode-survey').className = |
| 3471 | 'wifi-hm-seg-btn ' + (survey ? 'wifi-hm-seg-on' : 'wifi-hm-seg-off'); |
| 3472 | document.getElementById('wifi-hm-mode-design').className = |
| 3473 | 'wifi-hm-seg-btn ' + (!survey ? 'wifi-hm-seg-on' : 'wifi-hm-seg-off'); |
| 3474 | document.getElementById('wifi-hm-design-tools').classList.toggle('hidden', survey); |
| 3475 | _wifiHm.wallStart = null; |
| 3476 | wifiHeatmapRender(); |
| 3477 | } |
| 3478 | |
| 3479 | function wifiHmSetTool(t) { |
| 3480 | _wifiHm.tool = t; |
| 3481 | _wifiHm.wallStart = null; |
| 3482 | const sel = (id, on) => { const el = document.getElementById(id); if (el) |
| 3483 | el.className = 'wifi-hm-seg-btn ' + (on ? 'wifi-hm-seg-on' : 'wifi-hm-seg-off'); }; |
| 3484 | sel('wifi-hm-tool-wall', t === 'wall'); |
| 3485 | sel('wifi-hm-tool-ap', t === 'ap'); |
| 3486 | sel('wifi-hm-tool-column', t === 'column'); |
| 3487 | const st = document.getElementById('wifi-hm-design-status'); |
| 3488 | const msg = { wall: 'Click two points to draw a wall — or drag an existing wall to move it.', |
| 3489 | ap: 'Click the plan to drop an AP node (add several for a mesh) — or drag a node to move it.', |
| 3490 | column: 'Click to drop a structural column (pillar) — drag it to move, or drag its white edge handle to resize. Columns shadow WiFi behind them.' }; |
| 3491 | if (st) st.textContent = msg[t] || ''; |
| 3492 | } |
no test coverage detected