- Delete unused dataMerger, formatter, Device model, dead service exports, and the empty agents/ + .gitkeep placeholders. - Extract STORE_MODES and MDM device-type filters into a shared constants.js. - Anchor bot regexes (^help|^store|^analyze) so "analyze store 305" no longer fires both handlers; replace catch-all noise. - Hoist inline require() calls in integrations to top-of-file imports. - Harden WebSocket server: Authorization header support, single-agent enforcement, bounded pending requests, server-level error handler, coalesced cache refresh in Meraki client. - Wrap Meraki/MDM network calls with withRetry; add request timeouts. - Migrate all console.* calls onto utils/logger.js (LOG_LEVEL aware); drive Webex framework logLevel from env. - Refactor storeDetail.js: shared renderClientLine + buildMdmSection helpers cut duplication roughly in half. - Refresh README structure, document LOG_LEVEL, add npm run agent script, add jest testMatch + new tests (handlers, HealthReport, Store, ws). Verified: npm run lint clean, 7 suites / 31 tests passing. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
2.8 KiB
JavaScript
100 lines
2.8 KiB
JavaScript
/**
|
|
* Device matching and Meraki client utilities.
|
|
* Centralizes the fragile name/description matching logic used across reports.
|
|
*/
|
|
|
|
/**
|
|
* Find the best matching Meraki client for a device using multiple strategies.
|
|
*
|
|
* identifiers can contain: name, deviceName, adyenName, UserName, DeviceFriendlyName, ip
|
|
*/
|
|
function findMatchingClient(merakiClients = [], identifiers = {}) {
|
|
if (!merakiClients.length) return null;
|
|
|
|
const names = [
|
|
identifiers.name,
|
|
identifiers.deviceName,
|
|
identifiers.adyenName,
|
|
identifiers.UserName,
|
|
identifiers.DeviceFriendlyName,
|
|
identifiers.printer_name,
|
|
identifiers.register_display_name,
|
|
]
|
|
.filter(Boolean)
|
|
.map(n => String(n).toLowerCase().trim());
|
|
|
|
const ipPrefix = identifiers.ip_address
|
|
? String(identifiers.ip_address).split('.')[0]
|
|
: identifiers.ip
|
|
? String(identifiers.ip).split('.')[0]
|
|
: null;
|
|
|
|
for (const client of merakiClients) {
|
|
if (!client?.description) continue;
|
|
const desc = client.description.toLowerCase().trim();
|
|
|
|
// Strategy 1: exact or substring match on any name
|
|
if (names.some(n => n && (desc === n || desc.includes(n) || n.includes(desc)))) {
|
|
return client;
|
|
}
|
|
|
|
// Strategy 2: IP prefix fallback
|
|
if (ipPrefix && desc.includes(ipPrefix)) {
|
|
return client;
|
|
}
|
|
|
|
// Strategy 3: check client.user field
|
|
if (client.user) {
|
|
const user = client.user.toLowerCase();
|
|
if (names.some(n => n && user.includes(n))) {
|
|
return client;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getClientStatus(client) {
|
|
if (!client) return '❓ Unknown';
|
|
return client.status === 'Online' ? '✅ Online' : '❌ Offline';
|
|
}
|
|
|
|
function formatLastSeen(lastSeen) {
|
|
if (!lastSeen) return 'N/A';
|
|
const now = new Date();
|
|
const seen = new Date(lastSeen);
|
|
const diffMs = now - seen;
|
|
const diffMin = Math.floor(diffMs / 60000);
|
|
|
|
if (diffMin < 1) return 'Just now';
|
|
if (diffMin < 60) return `${diffMin} min ago`;
|
|
const hours = Math.floor(diffMin / 60);
|
|
return `${hours} hr ago`;
|
|
}
|
|
|
|
/**
|
|
* Build a best-effort link to the Meraki client detail page.
|
|
* Note: This logic is environment-specific (dashboard URLs).
|
|
*/
|
|
function buildMerakiClientLink(merakiNetwork, client) {
|
|
if (!client || !merakiNetwork?.url) return '';
|
|
|
|
try {
|
|
const networkCode = merakiNetwork.url.split('/n/')[1]?.split('/')[0] || merakiNetwork.id;
|
|
const base = merakiNetwork.url.split('/n/')[0] || 'https://n976.dashboard.meraki.com';
|
|
|
|
// Different sections for switches vs APs vs clients
|
|
// Default to clients view
|
|
return `${base}/${merakiNetwork.name.replace(/ /g, '-')}/n/${networkCode}/manage/clients/${client.id}/overview`;
|
|
} catch (_e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
findMatchingClient,
|
|
getClientStatus,
|
|
formatLastSeen,
|
|
buildMerakiClientLink,
|
|
};
|