Fix Enrich from CARD for items without IP — use host_id lookup
The enrich-batch endpoint now accepts a host_ids array alongside ips. When queue items have no IP address but have a host_id (from ivanti_findings), the frontend sends host_ids and the backend resolves them via CARD asset-search. Results include the resolved IP so it populates the IPV4_ADDRESS column. The LoaderModal now carries _host_id from initialDevices through to the enrich call.
This commit is contained in:
@@ -909,17 +909,20 @@ function createCardApiRouter() {
|
||||
/**
|
||||
* POST /enrich-batch
|
||||
*
|
||||
* Batch lookup IPs in CARD to extract Granite loader fields. Fetches team
|
||||
* assets (paginated, across confirmed, unconfirmed, and candidate
|
||||
* dispositions) and matches against the provided IPs. When no team is
|
||||
* specified, searches both NTS-AEO-STEAM and NTS-AEO-ACCESS-ENG.
|
||||
* Batch lookup IPs and/or Ivanti host IDs in CARD to extract Granite loader
|
||||
* fields. Accepts an array of IPs, an array of host_ids, or both. For IPs,
|
||||
* fetches team assets (paginated, across confirmed, unconfirmed, and
|
||||
* candidate dispositions) and matches against the provided IPs. For host_ids,
|
||||
* performs direct CARD asset-search lookups. When no team is specified,
|
||||
* searches both NTS-AEO-STEAM and NTS-AEO-ACCESS-ENG.
|
||||
* Returns enrichment results for each IP.
|
||||
*
|
||||
* @body {string[]} ips - Non-empty array of IP address strings (max 200)
|
||||
* @body {string[]} [ips] - Array of IP address strings (max 200). At least one of ips or host_ids is required.
|
||||
* @body {string[]} [host_ids] - Array of Ivanti Host ID strings (max 200). At least one of ips or host_ids is required.
|
||||
* @body {string} [team] - Team name to search assets under. Defaults to both NTS-AEO-STEAM and NTS-AEO-ACCESS-ENG if omitted.
|
||||
* @response 200 - { results: object[], enriched_count: number, not_found_count: number, total: number }
|
||||
* Each result: { ip: string, found: boolean, equip_inst_id: string|null, hostname: string|null, site_name?: string|null, mgmt_ip_asn?: string|null, responsible_team?: string|null, equipment_class?: string, equip_template?: string|null, equip_status?: string|null, serial_number?: string|null, error?: string }
|
||||
* @response 400 - { error: string } — invalid or empty ips array, or exceeds 200
|
||||
* @response 400 - { error: string } — neither ips nor host_ids provided, or exceeds 200 items
|
||||
* @response 503 - { error: string, missingVars: string[] } — CARD not configured
|
||||
*/
|
||||
router.post('/enrich-batch', requireAuth(), requireGroup('Admin', 'Standard_User'), async (req, res) => {
|
||||
@@ -927,18 +930,43 @@ function createCardApiRouter() {
|
||||
return res.status(503).json({ error: 'CARD API is not configured.', missingVars });
|
||||
}
|
||||
|
||||
const { ips, team } = req.body || {};
|
||||
if (!Array.isArray(ips) || ips.length === 0) {
|
||||
return res.status(400).json({ error: 'ips must be a non-empty array of IP address strings.' });
|
||||
const { ips, host_ids, team } = req.body || {};
|
||||
|
||||
// Accept either ips array, host_ids array, or both
|
||||
const hasIps = Array.isArray(ips) && ips.length > 0;
|
||||
const hasHostIds = Array.isArray(host_ids) && host_ids.length > 0;
|
||||
|
||||
if (!hasIps && !hasHostIds) {
|
||||
return res.status(400).json({ error: 'ips or host_ids array is required.' });
|
||||
}
|
||||
if (ips.length > 200) {
|
||||
return res.status(400).json({ error: 'Maximum 200 IPs per request.' });
|
||||
if ((ips && ips.length > 200) || (host_ids && host_ids.length > 200)) {
|
||||
return res.status(400).json({ error: 'Maximum 200 items per request.' });
|
||||
}
|
||||
|
||||
// Build a set of IPs we're looking for
|
||||
const targetIps = new Set(ips.map(ip => (ip || '').trim()).filter(Boolean));
|
||||
const targetIps = new Set((ips || []).map(ip => (ip || '').trim()).filter(Boolean));
|
||||
const resultMap = {};
|
||||
|
||||
// Direct host_id lookups — for items that have no IP but have a host_id
|
||||
if (hasHostIds) {
|
||||
for (const hostId of host_ids) {
|
||||
if (!hostId) continue;
|
||||
const key = `hostId:${hostId}`;
|
||||
try {
|
||||
const searchResult = await searchByIvantiHostId(hostId);
|
||||
if (searchResult.ok) {
|
||||
const searchData = JSON.parse(searchResult.body);
|
||||
const assets = searchData.assets || [];
|
||||
if (assets.length > 0) {
|
||||
const asset = assets[0];
|
||||
const assetIp = (asset._id || '').replace(/-[A-Z]+$/i, '');
|
||||
resultMap[key] = { ...extractGraniteFields(asset, assetIp), ip: assetIp };
|
||||
}
|
||||
}
|
||||
} catch (_) { /* skip */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Fast path: look up host_ids from ivanti_findings and use asset-search
|
||||
// The asset-search endpoint returns the full enriched record (same as team assets).
|
||||
const ipsArray = [...targetIps];
|
||||
@@ -1032,7 +1060,7 @@ function createCardApiRouter() {
|
||||
let enrichedCount = 0;
|
||||
let notFoundCount = 0;
|
||||
|
||||
for (const ip of ips) {
|
||||
for (const ip of (ips || [])) {
|
||||
const trimmedIp = (ip || '').trim();
|
||||
if (!trimmedIp) {
|
||||
results.push({ ip: '', found: false, equip_inst_id: null, hostname: null, error: 'Invalid IP' });
|
||||
@@ -1049,7 +1077,23 @@ function createCardApiRouter() {
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ results, enriched_count: enrichedCount, not_found_count: notFoundCount, total: ips.length });
|
||||
// Include results for host_id lookups (items without IPs)
|
||||
const hostIdResults = [];
|
||||
if (hasHostIds) {
|
||||
for (const hostId of host_ids) {
|
||||
if (!hostId) continue;
|
||||
const key = `hostId:${hostId}`;
|
||||
if (resultMap[key]) {
|
||||
hostIdResults.push({ host_id: hostId, found: true, ...resultMap[key] });
|
||||
enrichedCount++;
|
||||
} else {
|
||||
hostIdResults.push({ host_id: hostId, found: false, equip_inst_id: null, hostname: null, error: 'Host ID not found in CARD' });
|
||||
notFoundCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ results, host_id_results: hostIdResults, enriched_count: enrichedCount, not_found_count: notFoundCount, total: (ips || []).length + (host_ids || []).length });
|
||||
});
|
||||
|
||||
return router;
|
||||
|
||||
Reference in New Issue
Block a user