// integrations/cisco-mpp-phone/statusJson.js // // Parser for MPP Web UI JSON endpoints (Status.json). import { coerceRows, extractIndexed } from './jsonRows.js'; /** * @param {string|object|Array} raw * @returns {object} */ export function parseStatusJson(raw) { const rows = coerceRows(raw); const fields = {}; const lines = []; let currentExt = null; for (const row of rows) { const name = typeof row?.name === 'string' ? row.name.trim() : ''; if (!name) continue; const value = row?.value == null ? '' : String(row.value).trim(); const extMatch = name.match(/^Ext (\d+) Status$/i); if (extMatch) { currentExt = Number(extMatch[1]); lines.push({ index: currentExt }); continue; } if (currentExt && isLineField(name)) { const line = lines.find((l) => l.index === currentExt); if (line) assignLineField(line, name, value); fields[`Ext ${currentExt} ${name}`] = value; continue; } if (value) fields[name] = value; } const device = { product: fields['Product Name'] || null, mac: normalizeMac(fields['MAC Address']), firmware: fields['Software Version'] || fields['Firmware Version'] || null, serial: fields['Serial Number'] || null, hostname: fields['Host Name'] || null, }; const network = { ipv4: fields['Current IP'] || null, netmask: fields['Current Netmask'] || null, gateway: fields['Current Gateway'] || null, dns1: fields['Primary DNS'] || null, dns2: fields['Secondary DNS'] || fields['Secondary DNS'] || null, vlan: fields['Operational VLAN ID'] || fields['VLAN ID'] || null, connectionType: fields['Connection Type'] || null, ipStatus: fields['IP Status'] || null, }; const rebootHistory = extractIndexed(fields, /^Reboot Reason \d+$/i); const normalizedLines = lines .filter((l) => l.registrationState || l.lastRegistrationAt || l.lastRegistrationIp) .map((l) => ({ index: l.index, registrationState: l.registrationState || null, lastRegistrationAt: l.lastRegistrationAt || null, lastRegistrationIp: l.lastRegistrationIp || null, nextRegistrationInSeconds: l.nextRegistrationInSeconds || null, messageWaiting: l.messageWaiting || null, hotelingState: l.hotelingState || null, })); const primary = normalizedLines.find((l) => l.index === 1) || normalizedLines[0] || null; const phoneStatus = { elapsedTime: fields['Elapsed Time'] || null, currentTime: fields['Current Time'] || null, linkSpeed: fields['SW Port'] || null, linkConfig: fields['SW Port Config'] || null, upgradeStatus: fields['Upgrade Status'] || null, externalIp: fields['External IP'] || null, dot1xStatus: fields['Transaction status'] || null, dot1xProtocol: fields['Protocol'] || null, ledLine1Color: fields['Line 1 LED Color'] || null, ledLine1Cadence: fields['Line 1 LED Cadence'] || null, messageWaiting: fields['Ext 1 Message Waiting'] || fields['Message Waiting'] || null, hotelingState: fields['Ext 1 Hoteling State'] || fields['Hoteling State'] || null, sipBytesSent: fields['SIP Bytes Sent'] || null, sipBytesRecv: fields['SIP Bytes Recv'] || null, vpnConnected: fields['VPN Connected'] || null, ipv6Status: fields['IP Status_IPV6'] || null, ipv6Address: fields['Current IP_IPV6'] || null, tr069Feature: fields['TR-069 Feature'] || null, multicastRx: fields['Multicast Rx Pkts'] || null, multicastTx: fields['Multicast Tx Pkts'] || null, streamingRx: fields['Streaming Rx Pkts'] || null, }; return { fields, device, network, lines: normalizedLines, rebootHistory, phoneStatus, provisioning: { latest: fields['Provisioning Status 1'] || null, firmwareUpgrade: fields['Firmware Upgrade Status 1'] || null, }, sip: { messagesSent: fields['SIP Messages Sent'] || null, messagesRecv: fields['SIP Messages Recv'] || null, }, macAddress: device.mac, firmware: device.firmware, ipAddress: network.ipv4, registration: primary?.registrationState || null, }; } export function summarizePhoneHealthFromJson(parsed) { const warnings = []; const info = []; if (!parsed?.fields || Object.keys(parsed.fields).length === 0) { return { healthy: false, warnings: ['Status.json parsed empty or missing'], info }; } info.push(`${Object.keys(parsed.fields).length} status field(s) from Status.json`); const line1 = parsed.lines?.find((l) => l.index === 1) || parsed.lines?.[0]; if (line1?.registrationState) { const reg = line1.registrationState.toLowerCase(); if (reg.includes('not registered') || reg.includes('fail')) { warnings.push(`Ext ${line1.index} registration: ${line1.registrationState}`); } else { info.push(`Ext ${line1.index} registration: ${line1.registrationState}`); if (line1.lastRegistrationAt) info.push(`last reg: ${line1.lastRegistrationAt}`); if (line1.lastRegistrationIp) info.push(`registrar IP: ${line1.lastRegistrationIp}`); } } else if (parsed.registration) { info.push(`registration: ${parsed.registration}`); } else { warnings.push('no extension registration state in Status.json'); } if (parsed.device?.firmware) info.push(`firmware: ${parsed.device.firmware}`); if (parsed.device?.product) info.push(`product: ${parsed.device.product}`); if (parsed.network?.ipv4) info.push(`ip: ${parsed.network.ipv4}`); if (parsed.network?.vlan) info.push(`vlan: ${parsed.network.vlan}`); if (parsed.rebootHistory?.length) info.push(`latest reboot: ${parsed.rebootHistory[0]}`); return { healthy: warnings.length === 0, warnings, info }; } function isLineField(name) { return [ 'Registration State', 'Last Registration At', 'Last Registration IP', 'Next Registration In Seconds', 'Mapped SIP Port', 'Extended Function Status', 'Message Waiting', 'Hoteling State', ].includes(name); } function assignLineField(line, name, value) { switch (name) { case 'Registration State': line.registrationState = value; break; case 'Last Registration At': line.lastRegistrationAt = value; break; case 'Last Registration IP': line.lastRegistrationIp = value; break; case 'Next Registration In Seconds': line.nextRegistrationInSeconds = value; break; case 'Message Waiting': line.messageWaiting = value; break; case 'Hoteling State': line.hotelingState = value; break; default: break; } } function normalizeMac(raw) { if (!raw || typeof raw !== 'string') return null; const hex = raw.replace(/[^0-9a-fA-F]/g, '').toLowerCase(); if (hex.length !== 12) return null; return hex.match(/../g).join(':'); }