/** * 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, };