Fix IP filter to check connected client IPs

Fetches clients for every AP in parallel using the existing /api/ap-clients
endpoint, then keeps only APs where at least one client has an IP with last
octet 150–155. Shows a loading spinner while fetching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 15:55:49 +02:00
parent b455b2fd15
commit 139499839f

View File

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