import { simpleTimeAgo } from '../utils/simple-time-ago.js'; function showPhoneModal(identifier, deviceType, fullData = {}) { let modal = document.getElementById('phoneDeviceModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'phoneDeviceModal'; modal.className = 'hidden fixed inset-0 bg-black/80 flex items-center justify-center z-[100]'; modal.innerHTML = ` `; document.body.appendChild(modal); modal.querySelector('#modalClose').addEventListener('click', () => modal.classList.add('hidden')); } const titleEl = modal.querySelector('#modalTitle'); const contentEl = modal.querySelector('#modalContent'); const isDect = deviceType === 'dect-base' || deviceType === 'dect-handset' || fullData.isDect; titleEl.innerHTML = isDect ? `DECT: ${identifier}` : `Phone: ${identifier}`; contentEl.innerHTML = getPhoneModalHTML(identifier, deviceType, fullData); modal.classList.remove('hidden'); } function getPhoneModalHTML(identifier, deviceType, data) { let html = ''; const isDectBase = deviceType === 'dect-base' || data.isDectBase; const isDectHandset = deviceType === 'dect-handset' || data.isDectHandset; const isPhone = !isDectBase && !isDectHandset; if (isPhone) { html += renderPhoneSection(data); } else if (isDectBase) { html += renderDectBaseSection(data); } else if (isDectHandset) { html += renderDectHandsetSection(data); } // Always show Meraki if present if (data.meraki) { html += renderPhoneMerakiSection(data); } // Profile / location info if top level if (data.telephonyProfile || data.person || data.dectNetwork || data.locationMainNumber) { html += renderPhoneContextSection(data); } return `
${html}
`; } function sectionCard(title, content) { return `

${title}

${content}
`; } function renderPhoneSection(phone) { const lastSeen = phone.lastSeen ? simpleTimeAgo(new Date(phone.lastSeen)) : '—'; const statusBadge = ` ${phone.status || 'Unknown'} `; const content = `
Webex Desk Phone
${phone.displayName || phone.name || 'Unknown'}
${statusBadge}
MODEL${phone.model || phone.product || '—'}
SERIAL${phone.serial || '—'}
FIRMWARE${phone.firmware || '—'}
IP${phone.ipAddress || phone.meraki?.ip || '—'}
LAST SEEN${lastSeen}
MAC${phone.mac || '—'}
SIP URL${phone.primarySipUrl || '—'}
ERRORS${phone.errorCodes?.length || 0}
`; return sectionCard('', content); } function renderDectBaseSection(base) { const lastSeen = base.lastSeen ? simpleTimeAgo(new Date(base.lastSeen)) : '—'; const statusBadge = ` ${base.status || 'Unknown'} `; const content = `
DECT Basestation
${base.name || 'Unknown Base'}
${statusBadge}
MAC${base.mac || '—'}
FIRMWARE${base.firmware || '—'}
MODEL${base.model || '—'}
IP${base.ipAddress || '—'}
LAST SEEN${lastSeen}
LINES${base.linesRegistered || 0}
`; return sectionCard('', content); } function renderDectHandsetSection(handset) { const lastReg = handset.lastRegistrationTime ? simpleTimeAgo(new Date(handset.lastRegistrationTime)) : '—'; const content = `
DECT Handset
${handset.name || 'Unknown Handset'}
MAC${handset.mac || '—'}
FIRMWARE${handset.firmware || '—'}
EXTENSION${handset.extension || '—'}
LAST REG${lastReg}
BASE ID${handset.baseStationId || '—'}
`; return sectionCard('', content); } function renderPhoneMerakiSection(device) { const m = device.meraki || {}; const client = m.client || m; const connectionType = m.connectionType || client.recentDeviceConnection || 'Unknown'; let isOnline = client?.status === 'Online' || client?.status === 'connected' || false; if (!isOnline && client?.lastSeen) { const d = new Date(client.lastSeen); if (!isNaN(d.getTime())) { const ageMins = (Date.now() - d.getTime()) / 60000; if (ageMins < 5) isOnline = true; } } const lastSeen = client?.lastSeen ? simpleTimeAgo(new Date(client.lastSeen)) : '—'; const cleanMac = client?.mac ? client.mac.toUpperCase().replace(/:/g, '') : '—'; const ip = client?.ip || '—'; // Data usage in MB let sentMB = 0, recvMB = 0, totalMB = 0; if (client?.usage) { sentMB = (client.usage.sent / 1048576).toFixed(3); recvMB = (client.usage.recv / 1048576).toFixed(3); totalMB = (client.usage.total / 1048576).toFixed(3); } const statusBadge = ` ${isOnline ? 'Online' : 'Offline'} `; const merakiLink = client?.id && (m.clientUrl || client.clientUrl) ? `View in Meraki →` : ''; let content = `

Meraki Client Information

${statusBadge} ${merakiLink}
CONNECTION TYPE ${connectionType}
MAC ADDRESS ${cleanMac}
IP ADDRESS ${ip}
LAST SEEN ${lastSeen}
`; if (client?.usage) { content += `
DATA USAGE (SINCE LAST SEEN)
SENT
${sentMB} MB
RECEIVED
${recvMB} MB
TOTAL
${totalMB} MB
`; } // Add detailed port status/config if present (like AV dashboard) const portStatus = m.portStatus || client?.switchportStatus; const portConfig = m.portConfig || client?.switchportConfig; if (portStatus || portConfig) { if (portStatus) { content += renderSwitchportStatusSection(portStatus, portConfig); } if (portConfig) { content += renderSwitchportConfigSection(portConfig); } } return sectionCard('', content); } // ====================== SWITCHPORT STATUS (copied/adapted from AV for phone nodes) function renderSwitchportStatusSection(portStatus, portConfig) { const enabledText = portStatus?.enabled !== false ? 'Enabled' : 'Disabled'; const enabledColor = enabledText === 'Enabled' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'; const statusText = portStatus?.status || 'Unknown'; const isConnected = statusText.toLowerCase() === 'connected' || statusText.toLowerCase() === 'online'; const statusColor = isConnected ? 'bg-green-600 text-white' : 'bg-red-600 text-white'; const speedDuplex = `${portStatus?.speed || '—'} / ${portStatus?.duplex || '—'}`; const poeAllocated = portStatus?.poe?.isAllocated ? 'Yes' : 'No'; const usageKB = portStatus?.usageInKb?.total ? portStatus.usageInKb.total.toLocaleString() : '—'; const trafficKbps = portStatus?.trafficInKbps?.total ? portStatus.trafficInKbps.total.toFixed(1) : '—'; const content = `
${enabledText}
${statusText}
Port ID
${portStatus?.portId || '—'}
Is Uplink
${portStatus?.isUplink ? 'Yes' : 'No'}
Speed / Duplex
${speedDuplex}
PoE Allocated
${poeAllocated}
Errors
${portStatus?.errors || 0}
Warnings
${portStatus?.warnings || 0}
Usage
${usageKB} KB
Traffic
${trafficKbps} Kbps
Power Usage
${portStatus?.powerUsageInWh || '—'} Wh
`; return sectionCard('Switchport Status', content); } // ====================== SWITCHPORT CONFIG (adapted) function renderSwitchportConfigSection(portConfig) { const enabledText = portConfig?.enabled !== false ? 'Enabled' : 'Disabled'; const enabledColor = enabledText === 'Enabled' ? 'bg-green-600 text-white' : 'bg-red-600 text-white'; const poeText = portConfig?.poeEnabled ? 'PoE Enabled' : 'PoE Disabled'; const poeColor = portConfig?.poeEnabled ? 'bg-green-600 text-white' : 'bg-gray-700 text-gray-300'; const stickyCount = (portConfig?.stickyMacAllowList || []).length; const content = `
${enabledText}
${poeText}
Port ID
${portConfig?.portId || '—'}
Name
${portConfig?.name || '—'}
Type
${portConfig?.type || 'Access'}
VLAN
${portConfig?.vlan || '—'}
Voice VLAN
${portConfig?.voiceVlan || 'None'}
Access Policy
${portConfig?.accessPolicyType || '—'}
Sticky MAC Limit
${portConfig?.stickyMacAllowListLimit || '—'}
Sticky MAC List
${stickyCount} entries
`; return sectionCard('Switchport Configuration', content); } function renderPhoneContextSection(data) { const prof = data.telephonyProfile || {}; const pers = data.person || {}; const dectNet = data.dectNetwork || {}; const mainNum = data.locationMainNumber; let content = '
'; if (prof.timeZone || mainNum) { content += `
Location / Profile
${prof.timeZone ? `
Timezone: ${prof.timeZone}
` : ''} ${mainNum ? `
Main Number: ${mainNum}
` : ''}
`; } if (dectNet.name || dectNet.locationName) { content += `
DECT Network
${dectNet.name || 'DECT Network'}
${dectNet.locationName ? `
${dectNet.locationName}
` : ''}
`; } content += '
'; return sectionCard('Context', content); } export { showPhoneModal };