Fix EQUIP_INST_ID extraction fallbacks and minor cleanups

- Add additional fallback sources for EQUIP_INST_ID in CARD extractGraniteFields
  (card_flags, ivanti_assets, top-level asset fields)
- Add CARD_DEBUG env var gate for troubleshooting extraction logic
- Clarify anomaly/latest endpoint comment (only significant rows returned)
- Remove stale convention comment in LoaderModal.js
This commit is contained in:
Jordan Ramos
2026-07-06 15:18:56 -06:00
parent ffaaf961e8
commit e8a5bdc196
3 changed files with 29 additions and 2 deletions

View File

@@ -1215,6 +1215,16 @@ function extractGraniteFields(asset, ip) {
const qualys = asset.qualys_hosts || [];
const ivanti = asset.ivanti_assets || [];
// Debug: log available data sources for troubleshooting equip_inst_id extraction
if (process.env.CARD_DEBUG === 'true') {
console.log(`[card-api] extractGraniteFields for ${ip}:`);
console.log(` ncim_discovery: ${ncim.length} records, keys: ${ncim.length > 0 ? Object.keys(ncim[0]).join(',') : 'none'}`);
console.log(` netops_granite_allips: ${granite.length} records, keys: ${granite.length > 0 ? Object.keys(granite[0]).join(',') : 'none'}`);
console.log(` ise_granite_equipment: ${iseGranite.length} records`);
console.log(` card_flags keys: ${Object.keys(flags).join(',')}`);
console.log(` top-level asset keys: ${Object.keys(asset).filter(k => k.toLowerCase().includes('equip') || k.toLowerCase().includes('granite')).join(',')}`);
}
// EQUIP_INST_ID — from ncim_discovery or granite (only available from team assets endpoint)
let equip_inst_id = null;
let site_name = null;
@@ -1238,6 +1248,22 @@ function extractGraniteFields(asset, ip) {
equip_inst_id = iseGranite[0].EQUIP_INST_ID || null;
}
// Additional fallback: check card_flags for EQUIP_INST_ID (some assets store it here)
if (!equip_inst_id && flags.EQUIP_INST_ID) {
equip_inst_id = flags.EQUIP_INST_ID;
}
// Fallback: check ivanti_assets which sometimes carry the EQUIP_INST_ID
if (!equip_inst_id && ivanti.length > 0) {
equip_inst_id = ivanti[0].EQUIP_INST_ID || ivanti[0].equip_inst_id || null;
}
// Fallback: top-level asset field (some CARD response formats)
if (!equip_inst_id && asset.equip_inst_id) {
equip_inst_id = asset.equip_inst_id;
}
if (!equip_inst_id && asset.EQUIP_INST_ID) {
equip_inst_id = asset.EQUIP_INST_ID;
}
// Hostname from card_flags (primary source from owner endpoint)
if (!hostname && flags.CARD_HOSTNAME) {
const hostnames = Array.isArray(flags.CARD_HOSTNAME) ? flags.CARD_HOSTNAME : [flags.CARD_HOSTNAME];

View File

@@ -1377,7 +1377,9 @@ function createIvantiFindingsRouter(db, requireAuth) {
/**
* GET /api/ivanti/findings/anomaly/latest
*
* Return the most recent anomaly summary row from ivanti_sync_anomaly_log.
* Return the most recent significant anomaly summary row from ivanti_sync_anomaly_log.
* Only rows where is_significant = true are considered — the UI banner only displays
* significant events.
*
* @returns {Object} 200 - { anomaly: Object|null }
* @returns {Object} 500 - { error: string } on database error

View File

@@ -18,7 +18,6 @@ import { generateLoaderXlsx, generateFilename } from '../utils/graniteLoaderExpo
import { COLUMN_PICKLISTS } from '../utils/graniteLoaderPicklists';
import SearchableSelect from './SearchableSelect';
// ⚠️ CONVENTION: Use relative API paths (no absolute URLs). The fallback 'http://localhost:3001/api' should be '/api' instead.
// ⚠️ CONVENTION: Fallback should be '/api' (relative), not an absolute URL with host:port
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';