threat-intel: master 'select all' checkbox in toolbar
UX request 2026-04-30: when sorting by threat in the threat intel panel, ban-selected required clicking each per-row checkbox individually. Pages with 20-50 threats made bulk-ban tedious. Adds a master `[ ] all` checkbox to the toolbar (right of the Sort buttons, left of the existing 'N selected' counter) that toggles every per-row .ip-check on the page in one click. Then 'Ban Selected' / 'Unban Selected' work over the whole set. Three-state: unchecked (none selected) / checked (all) / indeterminate (partial — browsers render this as a "half-tick" so operators get visual feedback when they've toggled some rows manually after using master). updateSelCount keeps the master in sync as individual rows toggle so the visual is always truthful. No backend change — `/api/admin/security/mass-ban` already accepts an arbitrary IP list. This is purely a frontend ergonomics improvement on top of the existing mass-action infrastructure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b09b73c409
commit
2575842f7b
@ -1462,6 +1462,22 @@ async function loadThreats() {
|
||||
});
|
||||
// Mass action buttons
|
||||
var spacer = document.createElement('div'); spacer.style.flex = '1'; toolbar.appendChild(spacer);
|
||||
// Master "select all on this page" checkbox (2026-04-30 J UX request).
|
||||
// Mirrors the per-row .ip-check style; toggles every visible row.
|
||||
// Three-state: unchecked (none selected), checked (all selected),
|
||||
// indeterminate (partial). updateSelCount keeps it in sync as
|
||||
// individual rows are toggled.
|
||||
var selAllWrap = document.createElement('label');
|
||||
selAllWrap.style.cssText = 'display:flex;align-items:center;gap:6px;font-family:JetBrains Mono,monospace;font-size:9px;text-transform:uppercase;letter-spacing:0.5px;color:#7a7872;cursor:pointer';
|
||||
selAllWrap.title = 'Toggle every IP on this page';
|
||||
var selAll = document.createElement('input'); selAll.type = 'checkbox';
|
||||
selAll.id = 'sel-all';
|
||||
selAll.style.cssText = 'width:16px;height:16px;cursor:pointer;accent-color:#e2b55a';
|
||||
selAll.onchange = function(){ toggleAllChecks(this.checked); };
|
||||
selAllWrap.appendChild(selAll);
|
||||
var selAllLabel = document.createElement('span'); selAllLabel.textContent = 'all';
|
||||
selAllWrap.appendChild(selAllLabel);
|
||||
toolbar.appendChild(selAllWrap);
|
||||
var selCount = document.createElement('span'); selCount.id = 'sel-count';
|
||||
selCount.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;color:#7a7872';
|
||||
toolbar.appendChild(selCount);
|
||||
@ -1585,9 +1601,33 @@ async function loadThreats() {
|
||||
var currentSort = 'hits';
|
||||
|
||||
function updateSelCount() {
|
||||
var checks = document.querySelectorAll('.ip-check:checked');
|
||||
var all = document.querySelectorAll('.ip-check');
|
||||
var checked = document.querySelectorAll('.ip-check:checked');
|
||||
var el = document.getElementById('sel-count');
|
||||
if (el) el.textContent = checks.length ? checks.length + ' selected' : '';
|
||||
if (el) el.textContent = checked.length ? checked.length + ' selected' : '';
|
||||
// Sync the master "all" checkbox to reflect the page's actual state.
|
||||
// Three states: none → unchecked, all → checked, partial → indeterminate.
|
||||
// Indeterminate is the visual "half-tick" most browsers render — gives
|
||||
// operators a clear "you've got some but not all selected" hint.
|
||||
var master = document.getElementById('sel-all');
|
||||
if (master) {
|
||||
if (checked.length === 0) {
|
||||
master.checked = false; master.indeterminate = false;
|
||||
} else if (checked.length === all.length) {
|
||||
master.checked = true; master.indeterminate = false;
|
||||
} else {
|
||||
master.indeterminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAllChecks(checked) {
|
||||
// Master "select all" handler — flips every per-row checkbox on the
|
||||
// page to match the master's state. Used by the toolbar's `[ ] all`
|
||||
// checkbox so operators don't have to click each threat individually
|
||||
// before hitting Ban Selected. (2026-04-30 J UX request.)
|
||||
document.querySelectorAll('.ip-check').forEach(function(cb){ cb.checked = checked; });
|
||||
updateSelCount();
|
||||
}
|
||||
|
||||
async function massAction(action) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user