diff --git a/app/templates/index.html b/app/templates/index.html index f6c0b32..5cc67d4 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -222,6 +222,7 @@ const allRows = [...document.querySelectorAll('#ap-table-body tr')]; let ipFilterActive = false; + let ipMatchedMacs = new Set(); // AP MACs that have a client with a .150–.155 IP function isTargetIp(ip) { if (!ip) return false; @@ -229,6 +230,8 @@ return last >= 150 && last <= 155; } + function normMac(s) { return (s || '').toUpperCase().replace(/-/g, ':'); } + function applyFilters() { const q = filterInput.value.toLowerCase().trim(); let shown = 0; @@ -240,7 +243,7 @@ let ipMatch = true; if (ipFilterActive) { const cb = row.querySelector('.ap-checkbox'); - ipMatch = isTargetIp(cb?.dataset.ip || ''); + ipMatch = ipMatchedMacs.has(normMac(cb?.dataset.mac)); } const visible = nameMatch && ipMatch; @@ -254,8 +257,9 @@ filterInput.addEventListener('input', applyFilters); // ── IP range filter button ───────────────────────────────────────────────── - const btnIpFilter = document.getElementById('btn-ip-filter'); - const ipFilterLabel = document.getElementById('ip-filter-label'); + const btnIpFilter = document.getElementById('btn-ip-filter'); + const ipFilterLabel = document.getElementById('ip-filter-label'); + const ipFilterIcon = document.getElementById('ip-filter-icon'); function setIpFilterActive(active) { ipFilterActive = active; @@ -274,9 +278,48 @@ } } - btnIpFilter?.addEventListener('click', () => { - setIpFilterActive(!ipFilterActive); - applyFilters(); + btnIpFilter?.addEventListener('click', async () => { + if (ipFilterActive) { + ipMatchedMacs.clear(); + setIpFilterActive(false); + applyFilters(); + return; + } + + btnIpFilter.disabled = true; + ipFilterLabel.textContent = 'Loading…'; + ipFilterIcon.classList.add('animate-spin'); + + try { + // Collect each AP's mac + site_key from the DOM checkboxes + const apList = []; + allRows.forEach(row => { + const cb = row.querySelector('.ap-checkbox'); + if (cb?.dataset.mac && cb?.dataset.siteKey) { + apList.push({ mac: cb.dataset.mac, siteKey: cb.dataset.siteKey }); + } + }); + + // Fetch clients for every AP in parallel (reuses the same endpoint as the popup) + const hits = await Promise.all(apList.map(async ({ mac, siteKey }) => { + try { + const res = await fetch(`/api/ap-clients?mac=${encodeURIComponent(mac)}&site_key=${encodeURIComponent(siteKey)}`); + if (!res.ok) return null; + const { clients } = await res.json(); + return (clients || []).some(c => isTargetIp(c.ip)) ? normMac(mac) : null; + } catch { return null; } + })); + + ipMatchedMacs = new Set(hits.filter(Boolean)); + setIpFilterActive(true); + applyFilters(); + } catch (e) { + showToast('Failed to load client data', 'error'); + ipFilterLabel.textContent = 'Filter .150–.155'; + } finally { + btnIpFilter.disabled = false; + ipFilterIcon.classList.remove('animate-spin'); + } }); // ── Refresh ────────────────────────────────────────────────────────────────