# Implementation Plan: Ivanti Queue Redirect ## Overview Implement a redirect action for completed Ivanti queue items. The feature adds a `POST /api/ivanti/todo-queue/:id/redirect` endpoint to the existing route module, fixes the PUT validation message, creates a RedirectModal frontend component, and wires a redirect button into the QueuePanel for completed items. Tasks are ordered: backend bug fix, backend endpoint, frontend modal, frontend integration, with property tests alongside each layer. Additionally, GRANITE is added as a fourth workflow type across the entire stack — backend validation, RedirectModal, QueuePanel grouping (Inventory section), and AddToQueue popover. ## Tasks - [x] 1. Fix PUT endpoint validation message - [x] 1.1 Update PUT `/:id` workflow_type error message in `backend/routes/ivantiTodoQueue.js` - Change `"workflow_type must be FP or Archer."` to `"workflow_type must be FP, Archer, or CARD."` - _Requirements: 5.1_ - [x] 2. Add redirect endpoint to backend - [x] 2.1 Add `POST /:id/redirect` route in `backend/routes/ivantiTodoQueue.js` - Place inside the existing `createIvantiTodoQueueRouter` factory, before the DELETE routes - Auth: `requireAuth(db)`, `requireGroup('Admin', 'Standard_User')` - Validate `workflow_type` against existing `VALID_WORKFLOW_TYPES` constant - For FP/Archer: validate vendor using existing `isValidVendor()` helper; also check length ≤ 200 - For CARD: accept without vendor - Fetch original item with `db.get()` scoped to `req.user.id`; return 404 if not found - Return 400 if original item status is not `"complete"` - INSERT new row copying finding_id, finding_title, cves_json, ip_address, hostname from original; set status `"pending"`, workflow_type and vendor from request body - Fetch the inserted row, parse cves_json, return 201 with the new item - Call `logAudit(db, ...)` fire-and-forget with action `"queue_item_redirected"`, entityType `"ivanti_todo_queue"`, entityId = original item ID, details: `{ original_workflow_type, target_workflow_type, new_item_id, vendor }` - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.1, 2.2_ - [x] 3. Checkpoint — Verify backend changes - Ensure all tests pass, ask the user if questions arise. - [x] 4. Create RedirectModal frontend component - [x] 4.1 Create `frontend/src/components/RedirectModal.js` - Props: `item` (the completed queue item), `onClose` (function), `onRedirect` (function called with new item) - Display read-only context: finding title, finding ID, current workflow type - Workflow type selector (radio buttons or select) with options FP, Archer, CARD - Vendor text input shown only when FP or Archer is selected; required for those types - Submit button calls `POST /api/ivanti/todo-queue/${item.id}/redirect` with `credentials: 'include'` - On success: call `onRedirect(newItem)`, close modal - On error: display error message from API response, keep modal open - Loading state on submit button to prevent double-clicks - Style with inline style objects following DESIGN_SYSTEM.md (dark theme, accent borders, gradient backgrounds) - Use lucide-react icons (e.g., `CornerUpRight` or `ArrowRightLeft`) - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7_ - [x] 5. Integrate redirect button and modal into QueuePanel - [x] 5.1 Add redirect button to completed items in QueuePanel (inside `frontend/src/components/pages/ReportingPage.js`) - Add a redirect icon button (lucide-react) on each completed queue item row, next to the existing delete button - Button visible only when `item.status === 'complete'`; hidden for pending items - _Requirements: 3.1, 3.2_ - [x] 5.2 Wire RedirectModal state and rendering in QueuePanel - Add `redirectItem` state (null or the item being redirected) - Clicking the redirect button sets `redirectItem` to that item, opening the modal - On successful redirect (`onRedirect` callback): append the new item to the queue items list, show a success notification, clear `redirectItem` - On close: clear `redirectItem` - Import and render `` conditionally when `redirectItem` is set - _Requirements: 3.3, 4.5, 4.7_ - [x] 6. Final checkpoint — Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. - [ ] 7. Add GRANITE to backend validation - [ ] 7.1 Update `VALID_WORKFLOW_TYPES` constant in `backend/routes/ivantiTodoQueue.js` - Change from `['FP', 'Archer', 'CARD']` to `['FP', 'Archer', 'CARD', 'GRANITE']` - _Requirements: 6.1_ - [ ] 7.2 Update vendor validation condition in POST `/` (single add) endpoint - Change `workflow_type !== 'CARD'` to `workflow_type === 'FP' || workflow_type === 'Archer'` (or `!['CARD', 'GRANITE'].includes(workflow_type)`) for the vendor-required check - Change `workflow_type === 'CARD' ? '' : vendor.trim()` to `(workflow_type === 'CARD' || workflow_type === 'GRANITE') ? '' : vendor.trim()` for vendorVal assignment - _Requirements: 6.2_ - [ ] 7.3 Update vendor validation condition in POST `/batch` endpoint - Change `workflow_type !== 'CARD'` to `workflow_type === 'FP' || workflow_type === 'Archer'` for the vendor-required check - Change `workflow_type === 'CARD' ? '' : vendor.trim()` to `(workflow_type === 'CARD' || workflow_type === 'GRANITE') ? '' : vendor.trim()` for vendorVal assignment - _Requirements: 6.2_ - [ ] 7.4 Update vendor validation condition in POST `/:id/redirect` endpoint - Change `workflow_type !== 'CARD'` to `workflow_type === 'FP' || workflow_type === 'Archer'` for the vendor-required check - Change `workflow_type === 'CARD' ? '' : vendor.trim()` to `(workflow_type === 'CARD' || workflow_type === 'GRANITE') ? '' : vendor.trim()` for vendorVal assignment - _Requirements: 6.2_ - [ ] 7.5 Update all error messages across all endpoints - Change `"workflow_type must be FP, Archer, or CARD."` to `"workflow_type must be FP, Archer, CARD, or GRANITE."` in POST `/`, POST `/batch`, PUT `/:id`, and POST `/:id/redirect` - _Requirements: 5.1, 6.3_ - [ ] 8. Add GRANITE to RedirectModal - [ ] 8.1 Update `WORKFLOW_OPTIONS` in `frontend/src/components/RedirectModal.js` - Add `{ key: 'GRANITE', label: 'GRANITE', col: '#A1887F', rgb: '161,136,127' }` as the fourth option - Vendor field already hidden for non-FP/Archer types via `needsVendor` check — no change needed there - _Requirements: 4.1, 4.3_ - [ ] 9. Update QueuePanel grouping for Inventory section - [ ] 9.1 Update the `grouped` useMemo in QueuePanel (`frontend/src/components/pages/ReportingPage.js`) - Change `items.filter((i) => i.workflow_type === 'CARD')` to filter both CARD and GRANITE into inventory items - Split inventory items into `cardItems` and `graniteItems` sub-arrays - Change `otherItems` filter from `i.workflow_type !== 'CARD'` to exclude both CARD and GRANITE - Rename group key from `__CARD__` to `__INVENTORY__`, label from `'CARD'` to `'Inventory'`, and `isCard` to `isInventory` - Include `cardItems` and `graniteItems` as separate properties on the inventory group object - _Requirements: 7.1, 7.5_ - [ ] 9.2 Update the QueuePanel rendering to handle the Inventory section - Update the `.map()` destructuring from `isCard` to `isInventory` - Update group header border and label color to use `isInventory` instead of `isCard` - For the Inventory group, render CARD items first, then a subtle sub-divider (only when both `cardItems.length > 0` and `graniteItems.length > 0`), then GRANITE items - _Requirements: 7.1, 7.2, 7.5_ - [ ] 9.3 Update the workflow type color mapping in QueuePanel item rendering - Add GRANITE to the `wfColor` ternary: `item.workflow_type === 'GRANITE' ? { col: '#A1887F', rgb: '161,136,127' }` before the default CARD fallback - _Requirements: 7.3, 7.4_ - [ ] 9.4 Update `isCardItem` to `isInventoryItem` in QueuePanel item rendering - Change `const isCardItem = item.workflow_type === 'CARD'` to `const isInventoryItem = item.workflow_type === 'CARD' || item.workflow_type === 'GRANITE'` - Update the conditional rendering that uses `isCardItem` to use `isInventoryItem` (hostname/ip_address display vs CVE display) - _Requirements: 7.1_ - [ ] 10. Add GRANITE to AddToQueuePopover - [ ] 10.1 Update workflow type buttons in `AddToQueuePopover` (`frontend/src/components/pages/ReportingPage.js`) - Add `{ key: 'GRANITE', col: '#A1887F', rgb: '161,136,127' }` as the fourth button in the workflow type toggle array - _Requirements: 8.1, 8.3_ - [ ] 10.2 Update `isCard` condition in `AddToQueuePopover` to include GRANITE - Change `const isCard = queueForm.workflowType === 'CARD'` to `const isCard = queueForm.workflowType === 'CARD' || queueForm.workflowType === 'GRANITE'` (or rename to `isInventory`) - This controls the "No vendor required" message and hides the vendor input for GRANITE - _Requirements: 8.2_ - [ ] 10.3 Update `SelectionToolbar` component to include GRANITE - Add `{ type: 'GRANITE', color: '#A1887F', rgb: '161,136,127' }` as the fourth button in the workflow type toggles array - Change `const isCard = workflowType === 'CARD'` to include GRANITE: `const isCard = workflowType === 'CARD' || workflowType === 'GRANITE'` - _Requirements: 8.1, 8.2, 8.3_ - [ ] 11. Final checkpoint — Verify all GRANITE changes - Ensure all changes compile and render correctly, ask the user if questions arise. ## Notes - Tasks 1–6 are the original redirect feature tasks, all completed - Tasks 7–11 are the new GRANITE workflow type additions - No test tasks included per user request — testing will be done manually on the dev server - Each task references specific requirements for traceability - The QueuePanel component is defined inside `ReportingPage.js`, not a separate file - The project uses plain JavaScript (no TypeScript)