- Add GRANITE to VALID_WORKFLOW_TYPES in backend (no vendor required, same as CARD) - Update vendor validation and error messages across all endpoints (single add, batch, PUT, redirect) - Add GRANITE option to RedirectModal with warm slate color (#A1887F) - Rename QueuePanel CARD section to Inventory, group CARD + GRANITE with sub-divider - Add GRANITE to AddToQueuePopover and SelectionToolbar - Update spec docs (requirements, design, tasks)
9.8 KiB
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
-
1. Fix PUT endpoint validation message
- 1.1 Update PUT
/:idworkflow_type error message inbackend/routes/ivantiTodoQueue.js- Change
"workflow_type must be FP or Archer."to"workflow_type must be FP, Archer, or CARD." - Requirements: 5.1
- Change
- 1.1 Update PUT
-
2. Add redirect endpoint to backend
- 2.1 Add
POST /:id/redirectroute inbackend/routes/ivantiTodoQueue.js- Place inside the existing
createIvantiTodoQueueRouterfactory, before the DELETE routes - Auth:
requireAuth(db),requireGroup('Admin', 'Standard_User') - Validate
workflow_typeagainst existingVALID_WORKFLOW_TYPESconstant - 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 toreq.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
- Place inside the existing
- 2.1 Add
-
3. Checkpoint — Verify backend changes
- Ensure all tests pass, ask the user if questions arise.
-
4. Create RedirectModal frontend component
- 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}/redirectwithcredentials: '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.,
CornerUpRightorArrowRightLeft) - Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
- Props:
- 4.1 Create
-
5. Integrate redirect button and modal into QueuePanel
-
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
-
5.2 Wire RedirectModal state and rendering in QueuePanel
- Add
redirectItemstate (null or the item being redirected) - Clicking the redirect button sets
redirectItemto that item, opening the modal - On successful redirect (
onRedirectcallback): append the new item to the queue items list, show a success notification, clearredirectItem - On close: clear
redirectItem - Import and render
<RedirectModal>conditionally whenredirectItemis set - Requirements: 3.3, 4.5, 4.7
- Add
-
-
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_TYPESconstant inbackend/routes/ivantiTodoQueue.js- Change from
['FP', 'Archer', 'CARD']to['FP', 'Archer', 'CARD', 'GRANITE'] - Requirements: 6.1
- Change from
-
7.2 Update vendor validation condition in POST
/(single add) endpoint- Change
workflow_type !== 'CARD'toworkflow_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
- Change
-
7.3 Update vendor validation condition in POST
/batchendpoint- Change
workflow_type !== 'CARD'toworkflow_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
- Change
-
7.4 Update vendor validation condition in POST
/:id/redirectendpoint- Change
workflow_type !== 'CARD'toworkflow_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
- Change
-
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
- Change
-
-
8. Add GRANITE to RedirectModal
- 8.1 Update
WORKFLOW_OPTIONSinfrontend/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
needsVendorcheck — no change needed there - Requirements: 4.1, 4.3
- Add
- 8.1 Update
-
9. Update QueuePanel grouping for Inventory section
-
9.1 Update the
groupeduseMemo 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
cardItemsandgraniteItemssub-arrays - Change
otherItemsfilter fromi.workflow_type !== 'CARD'to exclude both CARD and GRANITE - Rename group key from
__CARD__to__INVENTORY__, label from'CARD'to'Inventory', andisCardtoisInventory - Include
cardItemsandgraniteItemsas separate properties on the inventory group object - Requirements: 7.1, 7.5
- Change
-
9.2 Update the QueuePanel rendering to handle the Inventory section
- Update the
.map()destructuring fromisCardtoisInventory - Update group header border and label color to use
isInventoryinstead ofisCard - For the Inventory group, render CARD items first, then a subtle sub-divider (only when both
cardItems.length > 0andgraniteItems.length > 0), then GRANITE items - Requirements: 7.1, 7.2, 7.5
- Update the
-
9.3 Update the workflow type color mapping in QueuePanel item rendering
- Add GRANITE to the
wfColorternary:item.workflow_type === 'GRANITE' ? { col: '#A1887F', rgb: '161,136,127' }before the default CARD fallback - Requirements: 7.3, 7.4
- Add GRANITE to the
-
9.4 Update
isCardItemtoisInventoryItemin QueuePanel item rendering- Change
const isCardItem = item.workflow_type === 'CARD'toconst isInventoryItem = item.workflow_type === 'CARD' || item.workflow_type === 'GRANITE' - Update the conditional rendering that uses
isCardItemto useisInventoryItem(hostname/ip_address display vs CVE display) - Requirements: 7.1
- Change
-
-
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
- Add
-
10.2 Update
isCardcondition inAddToQueuePopoverto include GRANITE- Change
const isCard = queueForm.workflowType === 'CARD'toconst isCard = queueForm.workflowType === 'CARD' || queueForm.workflowType === 'GRANITE'(or rename toisInventory) - This controls the "No vendor required" message and hides the vendor input for GRANITE
- Requirements: 8.2
- Change
-
10.3 Update
SelectionToolbarcomponent 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
- Add
-
-
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)