'use strict' const { deepGetValue } = require('./helpers') const findWanIp = d => { const pub = extractAllInterfaces(d).find(i => i.role === 'internet') return pub ? pub.ip : '' } const classifyIpRole = (ip, name = '') => { if (ip.startsWith('192.168.') || (ip.startsWith('10.') && name === 'br0')) return 'lan' if (ip.startsWith('172.17.') || ip.startsWith('172.16.') || name.includes('_10') || name.includes('Management')) return 'management' if (ip.startsWith('10.')) return 'service' if (!ip.startsWith('169.254.')) return 'internet' return 'unknown' } const extractAllInterfaces = d => { // Device:2 model (TP-Link) const ipIfaces = d.Device?.IP?.Interface if (ipIfaces) { const results = [] for (const ifKey of Object.keys(ipIfaces)) { if (ifKey.startsWith('_')) continue const iface = ipIfaces[ifKey] if (!iface?.IPv4Address) continue const name = iface.Name?._value || '' for (const addrKey of Object.keys(iface.IPv4Address)) { if (addrKey.startsWith('_')) continue const addr = iface.IPv4Address[addrKey] const ip = addr?.IPAddress?._value if (!ip || ip === '0.0.0.0') continue const status = addr.Status?._value if (status && status !== 'Enabled') continue results.push({ iface: ifKey, name, ip, mask: addr.SubnetMask?._value || '', addrType: addr.AddressingType?._value || '', role: classifyIpRole(ip, name) }) } } if (results.length) return results } // IGD model (Raisecom) — WANDevice.1.WANConnectionDevice.*.WANIPConnection.* const wanDev = d.InternetGatewayDevice?.WANDevice?.['1']?.WANConnectionDevice if (wanDev) { const results = [] for (const cdKey of Object.keys(wanDev)) { if (cdKey.startsWith('_')) continue const cd = wanDev[cdKey] const wanIp = cd?.WANIPConnection?.['1'] if (!wanIp) continue const ip = wanIp.ExternalIPAddress?._value if (!ip || ip === '0.0.0.0') continue const name = wanIp.Name?._value || `WAN${cdKey}` const status = wanIp.ConnectionStatus?._value || '' results.push({ iface: cdKey, name, ip, mask: wanIp.SubnetMask?._value || '', addrType: wanIp.AddressingType?._value || '', role: classifyIpRole(ip, name), status }) } if (results.length) return results } return [] } const countAllWifiClients = d => { const aps = d.Device?.WiFi?.AccessPoint if (!aps) return { direct: 0, mesh: 0, total: 0, perAp: [] } let direct = 0, mesh = 0 const perAp = [] for (const k of Object.keys(aps)) { if (k.startsWith('_') || !aps[k]) continue const n = Number(aps[k].AssociatedDeviceNumberOfEntries?._value) || 0 const ssidRef = aps[k].SSIDReference?._value || '' const ssidIdx = ssidRef.match(/SSID\.(\d+)/) const idx = ssidIdx ? parseInt(ssidIdx[1]) : parseInt(k) if (idx <= 8) direct += n; else mesh += n if (n > 0) perAp.push({ ap: k, ssid: ssidRef, clients: n }) } return { direct, mesh, total: direct + mesh, perAp } } const extractMeshTopology = d => { const apDevices = d.Device?.WiFi?.MultiAP?.APDevice if (!apDevices) return null const nodes = [] let totalClients = 0 for (const dk of Object.keys(apDevices)) { if (dk.startsWith('_')) continue const dev = apDevices[dk] if (!dev?._object) continue const name = dev.X_TP_HostName?._value || '' const mac = dev.MACAddress?._value || '' let nodeClients = 0 for (const rk of Object.keys(dev.Radio || {})) { if (rk.startsWith('_')) continue for (const ak of Object.keys(dev.Radio[rk]?.AP || {})) { if (ak.startsWith('_')) continue nodeClients += Number(dev.Radio[rk].AP[ak]?.AssociatedDeviceNumberOfEntries?._value) || 0 } } totalClients += nodeClients if (name || mac) nodes.push({ id: dk, name, active: dev.X_TP_Active?._value === true, mac, ip: dev.X_TP_IPAddress?._value || '', clients: nodeClients }) } return nodes.length ? { nodes, totalClients } : null } const extractIgdWifiSSIDs = d => { const wlanCfg = d.InternetGatewayDevice?.LANDevice?.['1']?.WLANConfiguration if (!wlanCfg) return null const ssids = [] for (const k of Object.keys(wlanCfg)) { if (k.startsWith('_')) continue const cfg = wlanCfg[k] const ssid = cfg?.SSID?._value if (!ssid) continue ssids.push({ index: parseInt(k), ssid, band: ssid.endsWith('-5G') ? '5GHz' : '2.4GHz', enabled: cfg.Enable?._value === true || cfg.Enable?._value === 'true', }) } return ssids.length ? ssids : null } const summarizeDevice = d => { const g = path => deepGetValue(d, path) const s = path => { const v = g(path); return v != null ? String(v) : '' } const counts = countAllWifiClients(d) const mesh = extractMeshTopology(d) return { _id: d._id, serial: s('DeviceID.SerialNumber') || s('InternetGatewayDevice.DeviceInfo.SerialNumber') || s('Device.DeviceInfo.SerialNumber') || d.DeviceID?.SerialNumber?._value || (d._id ? decodeURIComponent(d._id.split('-').slice(2).join('-')) : '') || '', manufacturer: s('DeviceID.Manufacturer') || d.DeviceID?.Manufacturer?._value || d._deviceId?._Manufacturer || '', model: s('DeviceID.ProductClass') || d.DeviceID?.ProductClass?._value || d._deviceId?._ProductClass || s('InternetGatewayDevice.DeviceInfo.ModelName') || '', oui: s('DeviceID.OUI') || d.DeviceID?.OUI?._value || d._deviceId?._OUI || '', firmware: s('InternetGatewayDevice.DeviceInfo.SoftwareVersion') || s('Device.DeviceInfo.SoftwareVersion') || '', uptime: g('InternetGatewayDevice.DeviceInfo.UpTime') || g('Device.DeviceInfo.UpTime') || null, lastInform: d._lastInform || null, lastBoot: d._lastBootstrap || null, registered: d._registered || null, interfaces: extractAllInterfaces(d), ip: s('InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress') || findWanIp(d) || '', rxPower: g('InternetGatewayDevice.WANDevice.1.X_GponInterafceConfig.RXPower') || g('InternetGatewayDevice.WANDevice.1.WANCommonInterfaceConfig.RXPower') || g('Device.Optical.Interface.1.Stats.SignalRxPower') || g('Device.Optical.Interface.1.OpticalSignalLevel') || null, txPower: g('InternetGatewayDevice.WANDevice.1.X_GponInterafceConfig.TXPower') || g('InternetGatewayDevice.WANDevice.1.WANCommonInterfaceConfig.TXPower') || g('Device.Optical.Interface.1.Stats.SignalTxPower') || g('Device.Optical.Interface.1.TransmitOpticalLevel') || null, pppoeUser: s('InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.Username') || s('Device.PPP.Interface.1.Username') || '', ssid: s('InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID') || s('Device.WiFi.SSID.1.SSID') || '', macAddress: s('InternetGatewayDevice.WANDevice.1.WANEthernetInterfaceConfig.MACAddress') || s('Device.Ethernet.Interface.1.MACAddress') || '', tags: d._tags || [], opticalStatus: s('Device.Optical.Interface.1.Status') || null, opticalErrors: { sent: g('Device.Optical.Interface.1.Stats.ErrorsSent') || 0, received: g('Device.Optical.Interface.1.Stats.ErrorsReceived') || 0 }, wifi: { radio1: { status: s('Device.WiFi.Radio.1.Status') || null, channel: g('Device.WiFi.Radio.1.Channel') || null, bandwidth: s('Device.WiFi.Radio.1.CurrentOperatingChannelBandwidth') || null, noise: g('Device.WiFi.Radio.1.Stats.Noise') || null, clients: g('Device.WiFi.AccessPoint.1.AssociatedDeviceNumberOfEntries') || 0 }, radio2: { status: s('Device.WiFi.Radio.2.Status') || null, channel: g('Device.WiFi.Radio.2.Channel') || null, bandwidth: s('Device.WiFi.Radio.2.CurrentOperatingChannelBandwidth') || null, noise: g('Device.WiFi.Radio.2.Stats.Noise') || null, clients: g('Device.WiFi.AccessPoint.2.AssociatedDeviceNumberOfEntries') || 0 }, radio3: { status: s('Device.WiFi.Radio.3.Status') || null, channel: g('Device.WiFi.Radio.3.Channel') || null, clients: g('Device.WiFi.AccessPoint.3.AssociatedDeviceNumberOfEntries') || 0 }, totalClients: mesh ? mesh.totalClients : counts.total, directClients: counts.direct, meshClients: mesh ? (mesh.totalClients - counts.direct) : counts.mesh, }, mesh: mesh ? mesh.nodes : null, hostsCount: g('Device.Hosts.HostNumberOfEntries') || g('InternetGatewayDevice.LANDevice.1.Hosts.HostNumberOfEntries') || null, ethernet: { port1: { status: s('Device.Ethernet.Interface.1.Status') || s('InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.1.Status') || null, speed: g('Device.Ethernet.Interface.1.MaxBitRate') || null }, port2: { status: s('Device.Ethernet.Interface.2.Status') || s('InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.2.Status') || null, speed: g('Device.Ethernet.Interface.2.MaxBitRate') || null }, port3: { status: s('InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.3.Status') || null }, port4: { status: s('InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.4.Status') || null }, }, // IGD WiFi SSIDs (Raisecom — no per-radio stats, just SSIDs) wifiSSIDs: extractIgdWifiSSIDs(d), } } module.exports = { summarizeDevice }