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 ────────────────────────────────────────────────────────────────