gigafibre-fsm/services/targo-hub/lib/device-hosts.js
louispaulb 320655b0a0 refactor: major cleanup — remove dead dispatch app, commit all backend code, extract client composables
- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained)
- Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked)
- Commit services/docuseal + services/legacy-db docker-compose configs
- Extract client app composables: useOTP, useAddressSearch, catalog data, format utils
- Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines
- Clean hardcoded credentials from config.js fallback values
- Add client portal: catalog, cart, checkout, OTP verification, address search
- Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal
- Add ops composables: useBestTech, useConversations, usePermissions, useScanner
- Add field app: scanner composable, docker/nginx configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 17:38:38 -04:00

102 lines
5.1 KiB
JavaScript

'use strict'
const HOST_FIELDS = ['HostName', 'IPAddress', 'PhysAddress', 'Active', 'AddressSource', 'LeaseTimeRemaining', 'Layer1Interface']
const hostParams = ['Device.Hosts.HostNumberOfEntries']
for (let i = 1; i <= 20; i++) for (const f of HOST_FIELDS) hostParams.push(`Device.Hosts.Host.${i}.${f}`)
const multiApParams = []
for (let d = 1; d <= 3; d++) {
for (let r = 1; r <= 2; r++) {
for (let a = 1; a <= 4; a++) {
multiApParams.push(`Device.WiFi.MultiAP.APDevice.${d}.Radio.${r}.AP.${a}.AssociatedDeviceNumberOfEntries`)
for (let c = 1; c <= 8; c++) {
const base = `Device.WiFi.MultiAP.APDevice.${d}.Radio.${r}.AP.${a}.AssociatedDevice.${c}`
multiApParams.push(`${base}.MACAddress`, `${base}.SignalStrength`, `${base}.X_TP_NegotiationSpeed`)
}
}
}
const base = `Device.WiFi.MultiAP.APDevice.${d}`
multiApParams.push(`${base}.X_TP_HostName`, `${base}.MACAddress`, `${base}.X_TP_Active`, `${base}.Radio.1.OperatingFrequencyBand`, `${base}.Radio.2.OperatingFrequencyBand`)
}
module.exports = function createHostsHandler ({ nbiRequest, json, deviceCache, cacheHosts, getCachedHosts }) {
return async function handleHosts (res, deviceId) {
const encId = encodeURIComponent(deviceId)
try { await nbiRequest(`/devices/${encId}/tasks?connection_request&timeout=15000`, 'POST', { name: 'getParameterValues', parameterNames: hostParams }) } catch {}
try { await nbiRequest(`/devices/${encId}/tasks?timeout=10000`, 'POST', { name: 'getParameterValues', parameterNames: multiApParams }) } catch {}
const result = await nbiRequest(`/devices/?query=${encodeURIComponent(JSON.stringify({ _id: deviceId }))}&projection=Device.Hosts,Device.WiFi.MultiAP`)
const devices = Array.isArray(result.data) ? result.data : []
if (!devices.length) return json(res, 404, { error: 'Device not found' })
const dd = devices[0]
const hostTree = dd.Device?.Hosts?.Host || {}
const hostCount = dd.Device?.Hosts?.HostNumberOfEntries?._value || 0
// Build client MAC -> node mapping from MultiAP
const clientNodeMap = new Map(), meshNodes = new Map()
const apDevices = dd.Device?.WiFi?.MultiAP?.APDevice || {}
for (const dk of Object.keys(apDevices)) {
if (dk.startsWith('_')) continue
const dev = apDevices[dk]
const nodeMac = dev.MACAddress?._value || '', nodeName = dev.X_TP_HostName?._value || ''
if (nodeMac) meshNodes.set(nodeMac.toUpperCase(), { name: nodeName, ip: dev.X_TP_IPAddress?._value || '' })
for (const rk of Object.keys(dev.Radio || {})) {
if (rk.startsWith('_')) continue
const radio = dev.Radio[rk]
if (!radio) continue
const freqBand = radio.OperatingFrequencyBand?._value || ''
const band = freqBand.includes('5') ? '5GHz' : freqBand.includes('2.4') ? '2.4GHz' : freqBand || ''
for (const ak of Object.keys(radio.AP || {})) {
if (ak.startsWith('_')) continue
for (const ck of Object.keys(radio.AP[ak]?.AssociatedDevice || {})) {
if (ck.startsWith('_')) continue
const client = radio.AP[ak].AssociatedDevice[ck]
const cMac = client?.MACAddress?._value
if (!cMac) continue
clientNodeMap.set(cMac.toUpperCase(), {
nodeName, nodeMac: nodeMac.toUpperCase(), band,
signal: client.SignalStrength?._value != null ? Number(client.SignalStrength._value) : null,
speed: client.X_TP_NegotiationSpeed?._value != null ? String(client.X_TP_NegotiationSpeed._value) : '',
})
}
}
}
}
const hosts = []
for (const k of Object.keys(hostTree)) {
if (k.startsWith('_')) continue
const h = hostTree[k]
if (!h?._object === undefined) continue
const gv = key => h[key]?._value !== undefined ? h[key]._value : null
const name = gv('HostName'), ip = gv('IPAddress'), mac = gv('PhysAddress')
if (!name && !ip && !mac) continue
const isMeshNode = mac && meshNodes.has(mac.toUpperCase())
const nodeInfo = mac ? clientNodeMap.get(mac.toUpperCase()) : null
const connType = isMeshNode ? 'mesh-node' : (!nodeInfo && !gv('Active')) ? 'unknown' : 'wifi'
hosts.push({
id: k, name: name || '', ip: ip || '', mac: mac || '',
active: gv('Active') === true, addressSource: gv('AddressSource') || '',
leaseRemaining: gv('LeaseTimeRemaining') != null ? Number(gv('LeaseTimeRemaining')) : null,
connType, band: nodeInfo?.band || '', signal: nodeInfo?.signal ?? null,
linkRate: nodeInfo?.speed || '', attachedNode: nodeInfo?.nodeName || '',
attachedMac: nodeInfo?.nodeMac || '', isMeshNode,
})
}
hosts.sort((a, b) => {
if (a.isMeshNode !== b.isMeshNode) return a.isMeshNode ? 1 : -1
if (a.active !== b.active) return a.active ? -1 : 1
return (a.name || a.ip).localeCompare(b.name || b.ip)
})
const hostsResult = { total: hostCount, hosts }
for (const [serial, entry] of deviceCache) {
if (entry.summary?._id === deviceId) { cacheHosts(serial, hostsResult); break }
}
return json(res, 200, hostsResult)
}
}