From 2575842f7b10accb3a1020565bc603424c62e79a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Apr 2026 03:27:20 -0500 Subject: [PATCH] threat-intel: master 'select all' checkbox in toolbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- llm_team_ui.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/llm_team_ui.py b/llm_team_ui.py index c79232b..6cefebc 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -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) {