Files
cve-dashboard/.kiro/specs/ivanti-queue-redirect/tasks.md
jramos 0fe8e94d51 feat: add GRANITE as fourth workflow type in Ivanti queue
- 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)
2026-04-14 15:38:22 -06:00

9.8 KiB
Raw Blame History

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 /: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
  • 2. Add redirect endpoint to backend

    • 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
  • 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}/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
  • 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 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 <RedirectModal> conditionally when redirectItem is set
      • Requirements: 3.3, 4.5, 4.7
  • 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 16 are the original redirect feature tasks, all completed
  • Tasks 711 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)