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) {