feat(reporting): store and display IP address on CARD queue items

Adds ip_address column to ivanti_todo_queue so CARD entries carry the
host IP needed to locate the asset in CARD.

- Migration: ALTER TABLE ADD COLUMN ip_address TEXT (safe to re-run)
- Backend: accepts ip_address in POST body, stores up to 64 chars
- Frontend: captures finding.ipAddress when adding to queue; CARD items
  in the queue panel show the IP in green instead of the CVE list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 15:01:32 -06:00
parent 6bf6371e51
commit 89b1f57ef4
4 changed files with 58 additions and 16 deletions

View File

@@ -1361,6 +1361,7 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onClearCompleted
const cveDisplay = cves.length > 0
? cves.slice(0, 3).join(', ') + (cves.length > 3 ? ` +${cves.length - 3}` : '')
: '—';
const isCardItem = item.workflow_type === 'CARD';
return (
<div
key={item.id}
@@ -1393,16 +1394,29 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onClearCompleted
}} title={item.finding_id}>
{item.finding_id}
</div>
{cves.length > 0 && (
<div style={{
fontFamily: 'monospace', fontSize: '0.62rem',
color: done ? '#334155' : '#64748B',
textDecoration: done ? 'line-through' : 'none',
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
marginTop: '1px',
}} title={cves.join(', ')}>
{cveDisplay}
</div>
{isCardItem ? (
item.ip_address && (
<div style={{
fontFamily: 'monospace', fontSize: '0.68rem', fontWeight: '600',
color: done ? '#334155' : '#10B981',
textDecoration: done ? 'line-through' : 'none',
marginTop: '2px',
}}>
{item.ip_address}
</div>
)
) : (
cves.length > 0 && (
<div style={{
fontFamily: 'monospace', fontSize: '0.62rem',
color: done ? '#334155' : '#64748B',
textDecoration: done ? 'line-through' : 'none',
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
marginTop: '1px',
}} title={cves.join(', ')}>
{cveDisplay}
</div>
)
)}
</div>
@@ -1673,8 +1687,9 @@ export default function ReportingPage({ filterDate, filterEXC }) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
finding_id: finding.id,
finding_title: finding.title || null,
cves: finding.cves || [],
finding_title: finding.title || null,
cves: finding.cves || [],
ip_address: finding.ipAddress || null,
vendor: queueForm.vendor.trim(),
workflow_type: queueForm.workflowType,
}),