feat: add Ivanti Queue redirect for completed items
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { RefreshCw, Loader, AlertCircle, PieChart, ChevronUp, ChevronDown, ChevronsUpDown, Settings2, GripVertical, Eye, EyeOff, Filter, Download, RotateCcw, Trash2, X, ListTodo, Upload, FileText, Check, AlertTriangle } from 'lucide-react';
|
||||
import { RefreshCw, Loader, AlertCircle, PieChart, ChevronUp, ChevronDown, ChevronsUpDown, Settings2, GripVertical, Eye, EyeOff, Filter, Download, RotateCcw, Trash2, X, ListTodo, Upload, FileText, Check, AlertTriangle, CornerUpRight } from 'lucide-react';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import IvantiCountsChart from './IvantiCountsChart';
|
||||
import CveTooltip from '../CveTooltip';
|
||||
import RedirectModal from '../RedirectModal';
|
||||
|
||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||
const STORAGE_KEY = 'steam_findings_columns_v2';
|
||||
@@ -1248,11 +1249,13 @@ function AddToQueuePopover({ finding, anchorRect, queueForm, setQueueForm, onAdd
|
||||
// ---------------------------------------------------------------------------
|
||||
// QueuePanel — fixed slide-out panel showing the user's Ivanti queue
|
||||
// ---------------------------------------------------------------------------
|
||||
function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, onClearCompleted, onCreateFpWorkflow, canWrite }) {
|
||||
function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, onClearCompleted, onCreateFpWorkflow, onRedirectComplete, canWrite }) {
|
||||
const pendingCount = items.filter((i) => i.status === 'pending').length;
|
||||
const completedCount = items.filter((i) => i.status === 'complete').length;
|
||||
|
||||
const [selectedIds, setSelectedIds] = useState(new Set());
|
||||
const [redirectItem, setRedirectItem] = useState(null);
|
||||
const [redirectSuccess, setRedirectSuccess] = useState(null);
|
||||
|
||||
// Drop any selected IDs that no longer exist in items
|
||||
useEffect(() => {
|
||||
@@ -1277,6 +1280,13 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
|
||||
setSelectedIds(new Set());
|
||||
};
|
||||
|
||||
const handleRedirectSuccess = (newItem) => {
|
||||
if (onRedirectComplete) onRedirectComplete(newItem);
|
||||
setRedirectItem(null);
|
||||
setRedirectSuccess(`Redirected to ${newItem.workflow_type}`);
|
||||
setTimeout(() => setRedirectSuccess(null), 3000);
|
||||
};
|
||||
|
||||
// CARD items are their own top section; everything else groups by vendor
|
||||
const grouped = useMemo(() => {
|
||||
const cardItems = items.filter((i) => i.workflow_type === 'CARD');
|
||||
@@ -1508,6 +1518,19 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
|
||||
{item.workflow_type}
|
||||
</span>
|
||||
|
||||
{/* Redirect button — completed items only */}
|
||||
{canWrite && done && (
|
||||
<button
|
||||
onClick={() => setRedirectItem(item)}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#334155', padding: '1px', lineHeight: 1, flexShrink: 0 }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.color = '#0EA5E9'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.color = '#334155'}
|
||||
title="Redirect to another workflow"
|
||||
>
|
||||
<CornerUpRight style={{ width: '13px', height: '13px' }} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Delete button */}
|
||||
<button
|
||||
onClick={() => onDelete(item.id)}
|
||||
@@ -1594,6 +1617,35 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Redirect success notification */}
|
||||
{redirectSuccess && (
|
||||
<div style={{
|
||||
position: 'fixed', top: '1rem', right: '440px',
|
||||
zIndex: 10001,
|
||||
display: 'flex', alignItems: 'center', gap: '0.5rem',
|
||||
padding: '0.5rem 1rem',
|
||||
background: 'rgba(16, 185, 129, 0.15)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.4)',
|
||||
borderRadius: '0.375rem',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.4)',
|
||||
fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: '0.75rem', fontWeight: '600',
|
||||
color: '#10B981',
|
||||
}}>
|
||||
<Check style={{ width: '14px', height: '14px' }} />
|
||||
{redirectSuccess}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Redirect modal */}
|
||||
{redirectItem && (
|
||||
<RedirectModal
|
||||
item={redirectItem}
|
||||
onClose={() => setRedirectItem(null)}
|
||||
onRedirect={handleRedirectSuccess}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3268,6 +3320,11 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
onDeleteMany={deleteQueueItems}
|
||||
onClearCompleted={clearCompleted}
|
||||
onCreateFpWorkflow={handleCreateFpWorkflow}
|
||||
onRedirectComplete={(newItem) => {
|
||||
setQueueItems((prev) => [...prev, newItem].sort((a, b) =>
|
||||
(a.vendor || '').localeCompare(b.vendor || '') || a.id - b.id
|
||||
));
|
||||
}}
|
||||
canWrite={canWrite}
|
||||
/>
|
||||
<FpWorkflowModal
|
||||
|
||||
Reference in New Issue
Block a user