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:
jramos
2026-04-13 12:39:47 -06:00
parent c4aaeff2a1
commit 835fbf26e7

View File

@@ -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);