Add CARD Action Modal with full owner context

Replace inline CARD action form with a centered modal that:
- Fetches and displays the full CARD owner record (confirmed,
  unconfirmed, candidates, declined teams with scores/sources)
- Shows queue item info (hostname, IP, finding, CVEs)
- Lets user switch between Confirm/Decline/Redirect actions
- Pre-fills team dropdowns from the actual owner data
- Shows CARD API errors inline with full detail

Add GET /api/card/owner-lookup/:ip endpoint that resolves a bare
IP to a CARD asset ID and returns the structured owner record.
This commit is contained in:
Jordan Ramos
2026-05-28 14:58:27 -06:00
parent a6e455311e
commit 8224183679
3 changed files with 427 additions and 11 deletions

View File

@@ -9,6 +9,7 @@ import CveTooltip from '../CveTooltip';
import RedirectModal from '../RedirectModal';
import AtlasBadge from '../AtlasBadge';
import LoaderModal from '../LoaderModal';
import CardActionModal from '../CardActionModal';
import ConsolidationModal from '../ConsolidationModal';
import AtlasSlideOutPanel from '../AtlasSlideOutPanel';
import AtlasIcon from '../AtlasIcon';
@@ -1541,6 +1542,10 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
// Granite Loader Sheet modal state
const [showLoaderModal, setShowLoaderModal] = useState(false);
// CARD Action Modal state
const [cardModalItem, setCardModalItem] = useState(null);
const [cardModalAction, setCardModalAction] = useState('confirm');
// Create Jira modal state
const [createJiraOpen, setCreateJiraOpen] = useState(false);
const [createJiraForm, setCreateJiraForm] = useState({ summary: '', cve_id: '', vendor: '', source_context: 'ivanti_queue', description: '', project_key: '', issue_type: '' });
@@ -1628,14 +1633,13 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
setTimeout(() => setRedirectSuccess(null), 3000);
};
// CARD action handlers
// CARD action handlers — open the CardActionModal instead of inline form
const openCardAction = (itemId, type) => {
setCardAction({ itemId, type });
setCardFormTeam('');
setCardFormComment('');
setCardFormFromTeam('');
setCardFormToTeam('');
setCardActionError(null);
const targetItem = items.find(i => i.id === itemId);
if (targetItem) {
setCardModalItem(targetItem);
setCardModalAction(type);
}
};
const closeCardAction = () => {
@@ -3169,6 +3173,19 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on
return items.filter(i => ['CARD', 'GRANITE', 'DECOM'].includes(i.workflow_type)).map(i => ({ ip_address: i.ip_address || '', hostname: i.hostname || '' }));
})() : null}
/>
{/* CARD Action Modal */}
<CardActionModal
isOpen={!!cardModalItem}
onClose={() => setCardModalItem(null)}
item={cardModalItem}
initialAction={cardModalAction}
cardTeams={cardTeams}
onSuccess={(itemId, _action) => {
onUpdate(itemId, { status: 'complete' });
setCardModalItem(null);
}}
/>
</>
);
}