Add sortable columns for Name, Site, IP Address
Clicking a column header sorts ascending; clicking again reverses to descending. Active column shows ↑/↓ in blue; inactive columns show ↕ in gray. IP addresses are compared numerically (octet by octet) so .9 sorts before .10. Sort composes with the name filter and IP filter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,9 +72,18 @@
|
|||||||
<input type="checkbox" id="chk-all"
|
<input type="checkbox" id="chk-all"
|
||||||
class="rounded border-gray-300 bg-white text-blue-500 focus:ring-blue-500 cursor-pointer" />
|
class="rounded border-gray-300 bg-white text-blue-500 focus:ring-blue-500 cursor-pointer" />
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Name</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs cursor-pointer select-none hover:text-gray-700"
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Site</th>
|
data-sort="name">
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">IP Address</th>
|
<span class="flex items-center gap-1">Name <span class="sort-icon text-gray-300">↕</span></span>
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs cursor-pointer select-none hover:text-gray-700"
|
||||||
|
data-sort="site">
|
||||||
|
<span class="flex items-center gap-1">Site <span class="sort-icon text-gray-300">↕</span></span>
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs cursor-pointer select-none hover:text-gray-700"
|
||||||
|
data-sort="ip">
|
||||||
|
<span class="flex items-center gap-1">IP Address <span class="sort-icon text-gray-300">↕</span></span>
|
||||||
|
</th>
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">MAC Address</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">MAC Address</th>
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Model</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Model</th>
|
||||||
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Status</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-500 uppercase tracking-wide text-xs">Status</th>
|
||||||
@@ -256,6 +265,69 @@
|
|||||||
|
|
||||||
filterInput.addEventListener('input', applyFilters);
|
filterInput.addEventListener('input', applyFilters);
|
||||||
|
|
||||||
|
// ── Column sort ────────────────────────────────────────────────────────────
|
||||||
|
let sortCol = null;
|
||||||
|
let sortDir = 'asc';
|
||||||
|
|
||||||
|
const colIndex = { name: 2, site: 3, ip: 4 };
|
||||||
|
|
||||||
|
function ipToNum(ip) {
|
||||||
|
return (ip || '').split('.').reduce((acc, p) => {
|
||||||
|
const n = parseInt(p, 10);
|
||||||
|
return acc * 256 + (isNaN(n) ? 0 : n);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellText(row, idx) {
|
||||||
|
return (row.querySelector(`td:nth-child(${idx})`)?.textContent || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSortIcons() {
|
||||||
|
document.querySelectorAll('[data-sort]').forEach(th => {
|
||||||
|
const icon = th.querySelector('.sort-icon');
|
||||||
|
if (!icon) return;
|
||||||
|
if (th.dataset.sort === sortCol) {
|
||||||
|
icon.textContent = sortDir === 'asc' ? '↑' : '↓';
|
||||||
|
icon.className = 'sort-icon text-blue-500';
|
||||||
|
} else {
|
||||||
|
icon.textContent = '↕';
|
||||||
|
icon.className = 'sort-icon text-gray-300';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-sort]').forEach(th => {
|
||||||
|
th.addEventListener('click', () => {
|
||||||
|
const col = th.dataset.sort;
|
||||||
|
if (sortCol === col) {
|
||||||
|
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
sortCol = col;
|
||||||
|
sortDir = 'asc';
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = colIndex[col];
|
||||||
|
const tbody = document.getElementById('ap-table-body');
|
||||||
|
const rows = [...tbody.querySelectorAll('tr')];
|
||||||
|
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const av = getCellText(a, idx);
|
||||||
|
const bv = getCellText(b, idx);
|
||||||
|
let cmp;
|
||||||
|
if (col === 'ip') {
|
||||||
|
cmp = ipToNum(av) - ipToNum(bv);
|
||||||
|
} else {
|
||||||
|
cmp = av.localeCompare(bv, undefined, { sensitivity: 'base' });
|
||||||
|
}
|
||||||
|
return sortDir === 'asc' ? cmp : -cmp;
|
||||||
|
});
|
||||||
|
|
||||||
|
rows.forEach(row => tbody.appendChild(row));
|
||||||
|
updateSortIcons();
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ── IP range filter button ─────────────────────────────────────────────────
|
// ── IP range filter button ─────────────────────────────────────────────────
|
||||||
const btnIpFilter = document.getElementById('btn-ip-filter');
|
const btnIpFilter = document.getElementById('btn-ip-filter');
|
||||||
const ipFilterLabel = document.getElementById('ip-filter-label');
|
const ipFilterLabel = document.getElementById('ip-filter-label');
|
||||||
|
|||||||
Reference in New Issue
Block a user