- 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>
102 lines
5.1 KiB
JavaScript
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)
|
|
}
|
|
}
|