# Implementation Plan: Batch Finding Disposition ## Overview Add multi-select capability to the Vulnerability Triage findings table with a batch-add-to-queue API endpoint. The backend gets a new `POST /api/ivanti/todo-queue/batch` route in `ivantiTodoQueue.js`. The frontend gets selection state, checkbox dual-mode logic, a SelectionToolbar component, shift-click range select, select-all, and Escape-to-clear — all within `ReportingPage.js`. ## Tasks - [x] 1. Add `POST /api/ivanti/todo-queue/batch` endpoint - [x] 1.1 Add batch route handler to `backend/routes/ivantiTodoQueue.js` - Add `POST /batch` route inside `createIvantiTodoQueueRouter`, before the `POST /` route - Apply `requireAuth(db)` and `requireGroup('Admin', 'Standard_User')` middleware - Validate request body: `findings` array (1–200 items), each with non-empty `finding_id` string - Validate `workflow_type` is one of `FP`, `Archer`, `CARD` - Validate `vendor`: required non-empty string ≤200 chars for FP/Archer; ignored for CARD - If any validation fails, return 400 with descriptive error message and reject entire batch - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.8, 3.10_ - [x] 1.2 Implement transactional batch insert with SQLite - Use `db.serialize()` with `BEGIN TRANSACTION` / `COMMIT` to insert all findings atomically - For each finding: insert row into `ivanti_todo_queue` with `user_id`, `finding_id`, `finding_title`, `cves_json`, `ip_address`, `vendor`, `workflow_type` - On success: fetch all inserted rows, parse `cves_json` back to arrays, return 201 with `{ items: [...] }` - On any DB error: `ROLLBACK` the transaction and return 500 - _Requirements: 3.7, 3.8, 3.9, 3.11_ - [x] 1.3 Add audit logging for batch additions - After successful commit, call `logAudit(db, { ... })` with action `'batch_add_to_queue'`, entityType `'ivanti_todo_queue'`, and details including the count and workflow_type - Import `logAudit` from `../helpers/auditLog` - _Requirements: 3.7_ - [x] 2. Checkpoint — Verify backend endpoint - Ensure the batch endpoint is syntactically correct and the route file has no errors. Ask the user if questions arise. - [x] 3. Add multi-select state and checkbox dual-mode logic to `ReportingPage.js` - [x] 3.1 Add selection state variables to `VulnerabilityTriagePage` - Add `selectedIds` (`new Set()`), `lastClickedId` (null), `batchSubmitting` (false), `batchError` (null), `batchWorkflowType` ('FP'), `batchVendor` ('') as new `useState` hooks - _Requirements: 1.1, 2.1_ - [x] 3.2 Implement checkbox dual-mode click handler - Replace the existing `` onClick in the checkbox cell with new logic: - If finding is already queued → no-op (existing behavior) - If `selectedIds.size === 0` AND not shift-click → open `AddToQueuePopover` (preserves single-select flow) - If shift-click AND `lastClickedId` exists → range-select all visible non-queued findings between `lastClickedId` and current finding in the `sorted` array - Otherwise → toggle finding.id in `selectedIds` - Always update `lastClickedId` when toggling selection - _Requirements: 1.1, 1.2, 5.1, 5.2, 6.1_ - [x] 3.3 Add visual highlighting for selected rows - When a finding's ID is in `selectedIds`, apply a highlighted background (e.g. `rgba(14,165,233,0.12)`) to the row - Override the existing alternating row background and hover for selected rows - _Requirements: 1.3_ - [x] 3.4 Disable checkbox for already-queued findings - Keep existing behavior: queued findings show checked + disabled checkbox, preventing re-selection - Ensure queued findings are excluded from shift-click range select and select-all - _Requirements: 1.5_ - [x] 4. Implement Select All / Deselect All in column header - Modify the checkbox column `` to render a clickable "Select All" checkbox when `selectedIds.size > 0` or when the user interacts with it - Click behavior: if not all visible non-queued findings are selected → select all visible non-queued; if all are selected → deselect all - _Requirements: 1.6, 1.7_ - [x] 5. Add selection pruning on filter changes - Add a `useEffect` that watches `filtered` (the filtered findings array) and prunes `selectedIds` to only include IDs still present in the filtered set - This ensures selection stays consistent when `columnFilters`, `actionFilter`, or `excFilter` change - _Requirements: 1.4_ - [x] 6. Implement SelectionToolbar component - [x] 6.1 Create the `SelectionToolbar` inline component in `ReportingPage.js` - Render between the panel header controls and the `` element, only when `selectedIds.size > 0` - Use `position: sticky` with appropriate `top` value to stay visible during scroll - Follow the dark theme design system: monospace fonts, dark gradient background, accent-colored borders - _Requirements: 2.1, 2.8_ - [x] 6.2 Add toolbar controls: count badge, Clear Selection, workflow toggles, vendor input, submit button - Display selected count badge (e.g. "12 selected") - "Clear Selection" button that empties `selectedIds` and hides toolbar - Workflow type toggle buttons (FP / Archer / CARD) using existing color scheme: FP = amber (`#F59E0B`), Archer = blue (`#0EA5E9`), CARD = green (`#10B981`) - Vendor text input (hidden when CARD is selected, show "No vendor required" indicator for CARD) - "Add to Queue" submit button — enabled only when workflow_type is CARD, or vendor is non-empty - _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6, 2.7_ - [x] 7. Implement batch submission flow - [x] 7.1 Add `submitBatch` async function to `VulnerabilityTriagePage` - Build request payload from `selectedIds` (map each ID to its finding object from `sorted`/`filtered` for `finding_id`, `finding_title`, `cves`, `ip_address`), plus `batchWorkflowType` and `batchVendor` - POST to `${API_BASE}/ivanti/todo-queue/batch` with `credentials: 'include'` - Set `batchSubmitting = true` before request, `false` after - _Requirements: 4.1, 4.2_ - [x] 7.2 Handle batch success response - On 201: merge returned items into `queueItems` state (sorted by vendor then id, matching existing pattern) - Clear `selectedIds`, reset `batchWorkflowType` to 'FP', reset `batchVendor` to '', clear `batchError` - The newly queued findings will automatically show as checked+disabled via the existing `isQueued()` helper - _Requirements: 4.3, 4.4, 4.6_ - [x] 7.3 Handle batch error response - On 4xx/5xx: parse error message from response JSON, set `batchError` to display in toolbar - On network failure: set `batchError` to "Network error — please try again" - Keep selection intact on error so user can retry - _Requirements: 4.5_ - [x] 8. Add Escape key handler to clear selection - Add a `useEffect` with a `keydown` listener for Escape that clears `selectedIds` when the SelectionToolbar is visible (i.e. `selectedIds.size > 0`) - Ensure it doesn't conflict with the existing Escape handler on `AddToQueuePopover` - _Requirements: 6.3_ - [x] 9. Ensure keyboard Tab accessibility for SelectionToolbar - Verify all interactive elements in the toolbar (workflow buttons, vendor input, submit button, clear button) are focusable via Tab key - Use native `