fix: revert clickable workflow badges, fix migration default, auto-sync submission lifecycle status from Ivanti findings
- Revert workflow badge to static (non-clickable) — queue panel is the entry point - Fix migration: use DEFAULT NULL for updated_at (SQLite disallows CURRENT_TIMESTAMP in ALTER TABLE) - Add useMemo enrichment to cross-reference submission lifecycle_status with actual Ivanti workflow state from findings data
This commit is contained in:
@@ -1061,42 +1061,22 @@ function TableCell({ colKey, finding, canWrite, onCveMouseEnter, onCveMouseLeave
|
||||
const wf = finding.workflow;
|
||||
if (!wf || !wf.id) return <td style={{ padding: '0.45rem 0.75rem', color: '#334155' }}>—</td>;
|
||||
const ws = workflowStyle(wf.state);
|
||||
const isEditable = ['reworked', 'rejected', 'expired'].includes((wf.state || '').toLowerCase());
|
||||
|
||||
const handleBadgeClick = isEditable ? () => {
|
||||
const numericId = parseInt(String(wf.id).replace(/\D/g, ''), 10);
|
||||
const sub = fpSubmissions.find(s => s.ivanti_workflow_batch_id === numericId);
|
||||
if (sub) onEditSubmission(sub);
|
||||
} : undefined;
|
||||
|
||||
return (
|
||||
<td style={{ padding: '0.45rem 0.75rem', whiteSpace: 'nowrap' }}>
|
||||
<span
|
||||
title={`${wf.id} · ${wf.state || 'Unknown'} · ${wf.type || ''}${isEditable ? ' · Click to edit' : ''}`}
|
||||
onClick={handleBadgeClick}
|
||||
title={`${wf.id} · ${wf.state || 'Unknown'} · ${wf.type || ''}`}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: '0.3rem',
|
||||
padding: '0.15rem 0.45rem', borderRadius: '0.25rem',
|
||||
background: ws.bg, border: `1px solid ${ws.border}`,
|
||||
fontFamily: 'monospace', fontSize: '0.68rem', fontWeight: '600',
|
||||
color: ws.text,
|
||||
cursor: isEditable ? 'pointer' : 'default',
|
||||
transition: 'all 0.15s',
|
||||
color: ws.text, cursor: 'default',
|
||||
}}
|
||||
onMouseEnter={isEditable ? (e) => {
|
||||
e.currentTarget.style.borderColor = ws.text;
|
||||
e.currentTarget.style.background = ws.bg.replace('0.12', '0.2');
|
||||
} : undefined}
|
||||
onMouseLeave={isEditable ? (e) => {
|
||||
e.currentTarget.style.borderColor = ws.border;
|
||||
e.currentTarget.style.background = ws.bg;
|
||||
} : undefined}
|
||||
>
|
||||
{wf.id}
|
||||
<span style={{ fontSize: '0.58rem', opacity: 0.8, textTransform: 'uppercase', letterSpacing: '0.04em' }}>
|
||||
{wf.state}
|
||||
</span>
|
||||
{isEditable && <Edit3 style={{ width: '10px', height: '10px', opacity: 0.7 }} />}
|
||||
</span>
|
||||
</td>
|
||||
);
|
||||
@@ -3159,9 +3139,33 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
const [fpModalItems, setFpModalItems] = useState([]);
|
||||
|
||||
// FP Submission editing state
|
||||
const [fpSubmissions, setFpSubmissions] = useState([]);
|
||||
const [fpSubmissionsRaw, setFpSubmissions] = useState([]);
|
||||
const [editSubmission, setEditSubmission] = useState(null);
|
||||
|
||||
// Enrich submissions with actual Ivanti workflow state from findings data
|
||||
const fpSubmissions = useMemo(() => {
|
||||
if (fpSubmissionsRaw.length === 0 || findings.length === 0) return fpSubmissionsRaw;
|
||||
const stateMap = {
|
||||
'reworked': 'rework', 'rejected': 'rejected', 'expired': 'rejected',
|
||||
'approved': 'approved', 'requested': 'submitted', 'actionable': 'submitted',
|
||||
};
|
||||
return fpSubmissionsRaw.map(sub => {
|
||||
let findingIds;
|
||||
try { findingIds = JSON.parse(sub.finding_ids_json || '[]'); } catch { return sub; }
|
||||
if (findingIds.length === 0) return sub;
|
||||
const matchedFinding = findings.find(f =>
|
||||
f.workflow && findingIds.includes(String(f.id))
|
||||
);
|
||||
if (!matchedFinding || !matchedFinding.workflow) return sub;
|
||||
const ivantiState = (matchedFinding.workflow.state || '').toLowerCase();
|
||||
const mappedStatus = stateMap[ivantiState];
|
||||
if (mappedStatus && mappedStatus !== sub.lifecycle_status) {
|
||||
return { ...sub, lifecycle_status: mappedStatus };
|
||||
}
|
||||
return sub;
|
||||
});
|
||||
}, [fpSubmissionsRaw, findings]);
|
||||
|
||||
// Queue API helpers
|
||||
const fetchQueue = useCallback(async () => {
|
||||
setQueueLoading(true);
|
||||
|
||||
Reference in New Issue
Block a user