Add BU Lookup tool to Admin panel

Backend:
- GET /api/ivanti/findings/bu-lookup?q=<hostname|ip> — queries the
  live Ivanti API to discover which BU a host is assigned to. Returns
  deduplicated results with hostName, ipAddress, BU, and hostId.
  Admin-only endpoint.

Frontend:
- Add 'BU Lookup' tab to Admin page with search input and results table
- Shows BU assignment badge (blue for tagged, amber for untagged)
- Supports Enter key to search, loading state, error display
- Useful for verifying team assignments when onboarding new users
This commit is contained in:
Jordan Ramos
2026-06-26 09:20:53 -06:00
parent dab3784742
commit 58996cf4cf
2 changed files with 211 additions and 1 deletions

View File

@@ -1689,6 +1689,83 @@ function createIvantiFindingsRouter(db, requireAuth) {
}
});
/**
* GET /api/ivanti/findings/bu-lookup
*
* Look up the BU assignment for a hostname or IP in the Ivanti API.
* Queries the live Ivanti platform (not the local cache) to discover
* which BU a given host is tagged to. Useful for verifying user team
* assignments before onboarding.
* Requires Admin group.
*
* @query {string} q - Hostname or IP address to look up
* @returns {Object} 200 - { query, findings: [{ hostId, hostName, ipAddress, bu, severity, title }] }
* @returns {Object} 400 - { error } when query is missing
* @returns {Object} 500 - { error } on API failure
*/
router.get('/bu-lookup', requireGroup('Admin'), async (req, res) => {
const q = (req.query.q || '').trim();
if (!q) return res.status(400).json({ error: 'q parameter is required (hostname or IP)' });
try {
const clientId = process.env.IVANTI_CLIENT_ID || '1550';
const apiKey = process.env.IVANTI_API_KEY;
if (!apiKey) return res.status(503).json({ error: 'Ivanti API key not configured' });
// Search by hostname or IP
const filters = [
{
field: q.match(/^\d+\./) ? 'host.ipAddress' : 'host.hostName',
operator: 'WILDCARD',
value: `*${q}*`,
exclusive: false,
orWithPrevious: false,
implicitFilters: [],
caseSensitive: false
}
];
const result = await ivantiPost(`/client/${clientId}/hostFinding/search`, {
filters,
projection: 'detail',
sort: [{ field: 'severity', direction: 'DESC' }],
page: 0,
size: 20
});
if (!result.ok) {
return res.status(502).json({ error: 'Ivanti API request failed', status: result.status });
}
const data = JSON.parse(result.body);
const records = data._embedded?.records || [];
// Extract BU info from each finding
const findings = records.map(r => ({
hostId: r.host?.hostId || null,
hostName: r.host?.hostName || null,
ipAddress: r.host?.ipAddress || null,
bu: r.assetCustomAttributes?.['1550_host_1']?.[0] || 'Untagged',
severity: r.severity || null,
title: r.title || null,
}));
// Deduplicate by hostId+bu
const seen = new Set();
const unique = findings.filter(f => {
const key = `${f.hostId}-${f.bu}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
res.json({ query: q, total: data.page?.totalElements || 0, findings: unique });
} catch (err) {
console.error('[Ivanti] BU lookup error:', err.message);
res.status(500).json({ error: 'BU lookup failed' });
}
});
return router;
}