Major additions accumulated over 9 days — single commit per request. Flow editor (new): - Generic visual editor for step trees, usable by project wizard + agent flows - PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain - Drag-and-drop reorder via vuedraggable with scope isolation per peer group - Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved) - Variable picker with per-applies_to catalog (Customer / Quotation / Service Contract / Issue / Subscription), insert + copy-clipboard modes - trigger_condition helper with domain-specific JSONLogic examples - Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern - Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js - ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates - depends_on chips resolve to step labels instead of opaque "s4" ids QR/OCR scanner (field app): - Camera capture → Gemini Vision via targo-hub with 8s timeout - IndexedDB offline queue retries photos when signal returns - Watcher merges late-arriving scan results into the live UI Dispatch: - Planning mode (draft → publish) with offer pool for unassigned jobs - Shared presets, recurrence selector, suggested-slots dialog - PublishScheduleModal, unassign confirmation Ops app: - ClientDetailPage composables extraction (useClientData, useDeviceStatus, useWifiDiagnostic, useModemDiagnostic) - Project wizard: shared detail sections, wizard catalog/publish composables - Address pricing composable + pricing-mock data - Settings redesign hosting flow templates Targo-hub: - Contract acceptance (JWT residential + DocuSeal commercial tracks) - Referral system - Modem-bridge diagnostic normalizer - Device extractors consolidated Migration scripts: - Invoice/quote print format setup, Jinja rendering - Additional import + fix scripts (reversals, dates, customers, payments) Docs: - Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS, FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT, APP_DESIGN_GUIDELINES - Archived legacy wizard PHP for reference - STATUS snapshots for 2026-04-18/19 Cleanup: - Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*) - .gitignore now covers invoice preview output + nested .DS_Store Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
482 lines
16 KiB
JavaScript
482 lines
16 KiB
JavaScript
'use strict'
|
|
const snmp = require('net-snmp')
|
|
const cfg = require('./config')
|
|
const { log } = require('./helpers')
|
|
|
|
const olts = new Map()
|
|
let pollTimer = null
|
|
const POLL_INTERVAL = cfg.OLT_POLL_INTERVAL || 300000
|
|
const SNMP_TIMEOUT = 4000
|
|
|
|
// --- TP-Link DeltaStream GPON OLT MIB (enterprise 11863) ---
|
|
// ONU table: .6.100.1.7.2.1.{col}.0.{port}.{onu}
|
|
const TPLINK = {
|
|
BASE: '1.3.6.1.4.1.11863.6.100.1.7.2.1',
|
|
COL_PORT_NAME: 3, COL_ONU_IDX: 4, COL_DESCRIPTION: 5, COL_SERIAL: 6,
|
|
COL_LINE_PROF: 7, COL_STATUS: 8, COL_VENDOR: 15, COL_MODEL: 16,
|
|
COL_UPTIME: 19, COL_HW_VER: 20, COL_RX_POWER: 26, COL_TX_POWER: 27,
|
|
COL_ONU_RX: 28, COL_DISTANCE: 30, COL_TEMPERATURE: 31, COL_LAST_DOWN: 32,
|
|
COL_FIRMWARE: 33, COL_OFFLINE_CAUSE: 42, COL_MAC: 43,
|
|
COL_MAP: {},
|
|
FETCH_COLS: [],
|
|
}
|
|
TPLINK.COL_MAP = {
|
|
[TPLINK.COL_PORT_NAME]: 'port', [TPLINK.COL_ONU_IDX]: 'onuIdx',
|
|
[TPLINK.COL_DESCRIPTION]: 'description', [TPLINK.COL_SERIAL]: 'serial',
|
|
[TPLINK.COL_VENDOR]: 'vendor', [TPLINK.COL_MODEL]: 'model',
|
|
[TPLINK.COL_UPTIME]: 'uptime', [TPLINK.COL_RX_POWER]: 'rxPowerOlt',
|
|
[TPLINK.COL_TX_POWER]: 'txPowerOlt', [TPLINK.COL_ONU_RX]: 'rxPowerOnu',
|
|
[TPLINK.COL_DISTANCE]: 'distance', [TPLINK.COL_TEMPERATURE]: 'temperature',
|
|
[TPLINK.COL_FIRMWARE]: 'firmware', [TPLINK.COL_OFFLINE_CAUSE]: 'lastOfflineCause',
|
|
[TPLINK.COL_MAC]: 'mac', [TPLINK.COL_LINE_PROF]: 'lineProfile',
|
|
}
|
|
TPLINK.FETCH_COLS = [
|
|
TPLINK.COL_PORT_NAME, TPLINK.COL_ONU_IDX, TPLINK.COL_DESCRIPTION, TPLINK.COL_SERIAL,
|
|
TPLINK.COL_STATUS, TPLINK.COL_VENDOR, TPLINK.COL_MODEL, TPLINK.COL_UPTIME,
|
|
TPLINK.COL_RX_POWER, TPLINK.COL_TX_POWER, TPLINK.COL_ONU_RX,
|
|
TPLINK.COL_DISTANCE, TPLINK.COL_TEMPERATURE, TPLINK.COL_FIRMWARE,
|
|
TPLINK.COL_OFFLINE_CAUSE, TPLINK.COL_MAC, TPLINK.COL_LINE_PROF,
|
|
]
|
|
|
|
// backward compat alias
|
|
const OID = TPLINK
|
|
|
|
// --- Raisecom ISCOM6800 GPON OLT MIB (enterprise 8886) ---
|
|
// ONU table: 1.3.6.1.4.1.8886.18.3.1.3.1.1.{col}.{onu_id}
|
|
// Signal table: 1.3.6.1.4.1.8886.18.3.1.3.3.1.1.{onu_id} (rx_power in dBm*10)
|
|
// onu_id encoding: (slot+32)*8388608 + port*65536 + ontid
|
|
const RAISECOM = {
|
|
BASE_TABLE: '1.3.6.1.4.1.8886.18.3.1.3.1.1',
|
|
BASE_SIGNAL: '1.3.6.1.4.1.8886.18.3.1.3.3.1.1',
|
|
COL_SERIAL: 2,
|
|
COL_DISTANCE: 6,
|
|
COL_FIRMWARE: 7,
|
|
COL_STATUS: 8,
|
|
COL_LINE_PROF: 9,
|
|
COL_DEREG_COUNT: 15,
|
|
COL_ONU_RX: 16,
|
|
FETCH_COLS: [2, 6, 7, 8, 9, 16],
|
|
COL_MAP: { 2: 'serial', 6: 'distance', 7: 'firmware', 9: 'lineProfile', 16: 'rxPowerOnu' },
|
|
}
|
|
|
|
function decodeRaisecomOnuId (onuId) {
|
|
const slot = Math.floor(onuId / 8388608) - 32
|
|
const port = Math.floor((onuId % 8388608) / 65536)
|
|
const ontid = onuId % 65536
|
|
return { slot, port, ontid }
|
|
}
|
|
|
|
function registerOlt ({ host, community, name, type }) {
|
|
if (!host || !community) return
|
|
if (olts.has(host)) return olts.get(host)
|
|
const vendor = (type || 'tplink').toLowerCase()
|
|
if (vendor !== 'tplink' && vendor !== 'raisecom') {
|
|
log(`OLT ${name || host}: unknown type "${type}", defaulting to tplink`)
|
|
}
|
|
const olt = {
|
|
host, community, name: name || host,
|
|
type: (vendor === 'raisecom') ? 'raisecom' : 'tplink',
|
|
lastPoll: null, lastError: null, lastDuration: null,
|
|
onuCount: 0, onlineCount: 0, onus: new Map(),
|
|
}
|
|
olts.set(host, olt)
|
|
log(`OLT registered: ${name || host} (${host}) [${olt.type}]`)
|
|
return olt
|
|
}
|
|
|
|
const createSession = (host, community) =>
|
|
snmp.createSession(host, community, { timeout: SNMP_TIMEOUT, retries: 1, version: snmp.Version2c })
|
|
|
|
function snmpWalk (session, oid) {
|
|
return new Promise((resolve) => {
|
|
const results = []
|
|
const timer = setTimeout(() => {
|
|
try { session.close() } catch {}
|
|
resolve(results)
|
|
}, SNMP_TIMEOUT + 2000)
|
|
|
|
session.subtree(oid, 50, (varbinds) => {
|
|
for (const vb of varbinds) {
|
|
if (!snmp.isVarbindError(vb)) results.push(vb)
|
|
}
|
|
}, (error) => {
|
|
clearTimeout(timer)
|
|
resolve(results)
|
|
})
|
|
})
|
|
}
|
|
|
|
function snmpGet (session, oid) {
|
|
return new Promise((resolve) => {
|
|
const timer = setTimeout(() => { try { session.close() } catch {} resolve(null) }, SNMP_TIMEOUT + 1000)
|
|
session.get([oid], (error, varbinds) => {
|
|
clearTimeout(timer)
|
|
if (error || !varbinds?.length || snmp.isVarbindError(varbinds[0])) return resolve(null)
|
|
resolve(varbinds[0])
|
|
})
|
|
})
|
|
}
|
|
|
|
// Raisecom OLT WAN IP OIDs: 1.3.6.1.4.1.8886.18.3.6.6.1.1.13.{olt_id}.{wan_idx}
|
|
// olt_id = slot * 10000000 + port * 100000 + ontid
|
|
const RAISECOM_WAN_IP_OID = '1.3.6.1.4.1.8886.18.3.6.6.1.1.13'
|
|
|
|
// Get management IP from Raisecom OLT via SNMP
|
|
// Can use either cached ONU data (serial lookup) or explicit slot/port/ontid params
|
|
async function getManageIp (serial, opts = {}) {
|
|
let oltHost, community, oltName, slot, port, ontId
|
|
|
|
if (serial) {
|
|
const onu = getOnuBySerial(serial)
|
|
if (onu) {
|
|
const olt = olts.get(onu.oltHost)
|
|
if (!olt || olt.type !== 'raisecom') return null
|
|
oltHost = olt.host; community = olt.community; oltName = olt.name
|
|
// Raisecom cache stores port as "0/slot/port" and onuIdx as ontid
|
|
const parts = (onu.port || '').split('/')
|
|
if (parts.length === 3) { slot = parseInt(parts[1]); port = parseInt(parts[2]) }
|
|
ontId = onu.onuIdx
|
|
}
|
|
}
|
|
|
|
// Allow explicit params (from ERPNext data when ONU not in cache yet)
|
|
if (opts.oltIp) oltHost = opts.oltIp
|
|
if (opts.community) community = opts.community
|
|
if (opts.slot != null) slot = opts.slot
|
|
if (opts.port != null) port = opts.port
|
|
if (opts.ontId != null) ontId = opts.ontId
|
|
if (!community) community = 'targosnmp'
|
|
if (!oltName) oltName = oltHost
|
|
|
|
if (!oltHost || slot == null || port == null || ontId == null) return null
|
|
|
|
const oltId = slot * 10000000 + port * 100000 + ontId
|
|
const session = createSession(oltHost, community)
|
|
const ips = []
|
|
try {
|
|
for (let wan = 1; wan <= 4; wan++) {
|
|
const vb = await snmpGet(session, `${RAISECOM_WAN_IP_OID}.${oltId}.${wan}`)
|
|
if (vb) {
|
|
const ip = extractVal(vb)
|
|
if (ip && ip !== '0.0.0.0' && ip !== '') ips.push({ wan, ip })
|
|
}
|
|
}
|
|
} finally { try { session.close() } catch {} }
|
|
|
|
if (!ips.length) return null
|
|
const mgmt = ips.find(i => i.ip.startsWith('172.17.') || i.ip.startsWith('10.'))
|
|
return { serial, oltName, oltHost, slot, port, ontId, ips, manageIp: mgmt?.ip || ips[0].ip }
|
|
}
|
|
|
|
function extractVal (vb) {
|
|
if (vb.type === snmp.ObjectType.OctetString) return vb.value.toString()
|
|
if (vb.type === snmp.ObjectType.Integer) return vb.value
|
|
return vb.value?.toString?.() || ''
|
|
}
|
|
|
|
function parseTplinkTable (varbinds) {
|
|
const byKey = new Map()
|
|
const baseLen = TPLINK.BASE.split('.').length
|
|
|
|
for (const vb of varbinds) {
|
|
const suffix = vb.oid.toString().split('.').slice(baseLen)
|
|
if (suffix.length < 3) continue
|
|
const col = parseInt(suffix[0])
|
|
const key = suffix.slice(1).join('.')
|
|
|
|
if (!byKey.has(key)) byKey.set(key, {})
|
|
const entry = byKey.get(key)
|
|
const val = extractVal(vb)
|
|
|
|
if (col === TPLINK.COL_STATUS) {
|
|
entry.status = (val === 1 || val === '1') ? 'online' : 'offline'
|
|
} else if (TPLINK.COL_MAP[col]) {
|
|
entry[TPLINK.COL_MAP[col]] = val
|
|
}
|
|
}
|
|
|
|
const onus = []
|
|
for (const [key, entry] of byKey) {
|
|
if (!entry.serial) continue
|
|
entry._key = key
|
|
onus.push(entry)
|
|
}
|
|
return onus
|
|
}
|
|
|
|
function parseRaisecomTable (tableVarbinds, signalVarbinds) {
|
|
const byId = new Map()
|
|
const tableBaseLen = RAISECOM.BASE_TABLE.split('.').length
|
|
|
|
for (const vb of tableVarbinds) {
|
|
const suffix = vb.oid.toString().split('.').slice(tableBaseLen)
|
|
if (suffix.length < 2) continue
|
|
const col = parseInt(suffix[0])
|
|
const onuId = parseInt(suffix[1])
|
|
if (isNaN(onuId)) continue
|
|
|
|
if (!byId.has(onuId)) byId.set(onuId, { _onuId: onuId })
|
|
const entry = byId.get(onuId)
|
|
const val = extractVal(vb)
|
|
|
|
if (col === RAISECOM.COL_STATUS) {
|
|
entry.status = (val === 1 || val === '1') ? 'online' : 'offline'
|
|
} else if (RAISECOM.COL_MAP[col]) {
|
|
entry[RAISECOM.COL_MAP[col]] = val
|
|
}
|
|
}
|
|
|
|
// Parse signal table for OLT-side rx power
|
|
const sigBaseLen = RAISECOM.BASE_SIGNAL.split('.').length
|
|
for (const vb of signalVarbinds) {
|
|
const suffix = vb.oid.toString().split('.').slice(sigBaseLen)
|
|
if (suffix.length < 1) continue
|
|
const onuId = parseInt(suffix[0])
|
|
if (isNaN(onuId) || !byId.has(onuId)) continue
|
|
const val = extractVal(vb)
|
|
// Raisecom reports rx_power in dBm * 10
|
|
byId.get(onuId).rxPowerOlt = typeof val === 'number' ? val / 10 : val
|
|
}
|
|
|
|
const onus = []
|
|
for (const [onuId, entry] of byId) {
|
|
if (!entry.serial) continue
|
|
const { slot, port, ontid } = decodeRaisecomOnuId(onuId)
|
|
entry.port = `0/${slot}/${port}`
|
|
entry.onuIdx = ontid
|
|
entry._key = `${slot}.${port}.${ontid}`
|
|
onus.push(entry)
|
|
}
|
|
return onus
|
|
}
|
|
|
|
// backward compat alias
|
|
const parseOnuTable = parseTplinkTable
|
|
|
|
async function pollTplink (session) {
|
|
const allVarbinds = []
|
|
for (const col of TPLINK.FETCH_COLS) {
|
|
const vbs = await snmpWalk(session, `${TPLINK.BASE}.${col}`)
|
|
allVarbinds.push(...vbs)
|
|
}
|
|
return parseTplinkTable(allVarbinds)
|
|
}
|
|
|
|
async function pollRaisecom (olt) {
|
|
// Raisecom needs a fresh session per walk (session gets closed after subtree timeout)
|
|
const tableVarbinds = []
|
|
for (const col of RAISECOM.FETCH_COLS) {
|
|
const s = createSession(olt.host, olt.community)
|
|
const vbs = await snmpWalk(s, `${RAISECOM.BASE_TABLE}.${col}`)
|
|
try { s.close() } catch {}
|
|
tableVarbinds.push(...vbs)
|
|
}
|
|
const sigSession = createSession(olt.host, olt.community)
|
|
const signalVarbinds = await snmpWalk(sigSession, RAISECOM.BASE_SIGNAL)
|
|
try { sigSession.close() } catch {}
|
|
return parseRaisecomTable(tableVarbinds, signalVarbinds)
|
|
}
|
|
|
|
async function pollOlt (olt) {
|
|
const startMs = Date.now()
|
|
let session
|
|
try {
|
|
session = createSession(olt.host, olt.community)
|
|
|
|
let onus
|
|
if (olt.type === 'raisecom') {
|
|
session.close(); session = null
|
|
onus = await pollRaisecom(olt)
|
|
} else {
|
|
onus = await pollTplink(session)
|
|
session.close(); session = null
|
|
}
|
|
const prevOnus = new Map(olt.onus)
|
|
|
|
olt.onus.clear()
|
|
let onlineCount = 0
|
|
for (const onu of onus) {
|
|
olt.onus.set(onu.serial, onu)
|
|
if (onu.status === 'online') onlineCount++
|
|
|
|
const prev = prevOnus.get(onu.serial)
|
|
if (prev && prev.status !== onu.status) {
|
|
const event = onu.status === 'offline' ? 'olt_offline' : 'olt_online'
|
|
logOnuEvent(olt, onu, event, onu.status === 'offline' ? (onu.lastOfflineCause || null) : null)
|
|
|
|
// Feed outage monitor for proactive detection
|
|
try {
|
|
const monitor = require('./outage-monitor')
|
|
if (onu.status === 'offline') monitor.onOnuOffline(olt, onu)
|
|
else monitor.onOnuOnline(olt, onu)
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
const wasDown = !!olt.lastError // was previously in error state
|
|
olt.onuCount = onus.length
|
|
olt.onlineCount = onlineCount
|
|
olt.lastPoll = new Date().toISOString()
|
|
olt.lastError = null
|
|
olt.lastDuration = Date.now() - startMs
|
|
|
|
log(`OLT ${olt.name}: ${onus.length} ONUs (${onlineCount} online) in ${olt.lastDuration}ms`)
|
|
|
|
// If OLT was down and is now back, resolve the OLT-level incident
|
|
if (wasDown) {
|
|
try {
|
|
const monitor = require('./outage-monitor')
|
|
monitor.onOltUp(olt.name, 'snmp')
|
|
} catch (_) {}
|
|
}
|
|
} catch (e) {
|
|
const wasOk = !olt.lastError // first failure after success
|
|
olt.lastError = { message: e.message, ts: new Date().toISOString() }
|
|
olt.lastDuration = Date.now() - startMs
|
|
log(`OLT ${olt.name} SNMP FAILED (${olt.lastDuration}ms): ${e.message} — resuming next`)
|
|
|
|
// Trigger OLT-level outage synthesis (only on first failure, not repeated)
|
|
if (wasOk) {
|
|
try {
|
|
const monitor = require('./outage-monitor')
|
|
monitor.onOltDown(olt.name, 'snmp', { customerCount: olt.onuCount, error: e.message })
|
|
} catch (_) {}
|
|
}
|
|
} finally {
|
|
if (session) try { session.close() } catch {}
|
|
}
|
|
}
|
|
|
|
async function logOnuEvent (olt, onu, event, reason) {
|
|
log(`ONU ${onu.serial} on ${olt.name}: ${event}${reason ? ' (' + reason + ')' : ''}`)
|
|
try {
|
|
const { logDeviceEvent } = require('./devices')
|
|
if (logDeviceEvent) {
|
|
await logDeviceEvent(onu.serial, event, reason, {
|
|
olt: olt.name, port: onu.port, mac: onu.mac,
|
|
rxPowerOlt: onu.rxPowerOlt, rxPowerOnu: onu.rxPowerOnu,
|
|
distance: onu.distance, lastOfflineCause: onu.lastOfflineCause,
|
|
})
|
|
}
|
|
} catch (e) {
|
|
log(`Failed to log ONU event: ${e.message}`)
|
|
}
|
|
}
|
|
|
|
async function pollAllOlts () {
|
|
if (!olts.size) return
|
|
const startMs = Date.now()
|
|
let ok = 0, failed = 0
|
|
for (const [, olt] of olts) {
|
|
await pollOlt(olt)
|
|
olt.lastError ? failed++ : ok++
|
|
}
|
|
if (failed > 0) log(`OLT poll complete: ${ok} OK, ${failed} FAILED in ${Date.now() - startMs}ms`)
|
|
}
|
|
|
|
function getOnuBySerial (serial) {
|
|
if (!serial) return null
|
|
const normalized = serial.replace(/^(TPLG)([A-F0-9])/i, '$1-$2').toUpperCase()
|
|
for (const [, olt] of olts) {
|
|
const enrich = (o) => ({ ...o, oltName: olt.name, oltHost: olt.host })
|
|
let onu = olt.onus.get(serial) || olt.onus.get(normalized)
|
|
if (onu) return enrich(onu)
|
|
for (const [s, o] of olt.onus) {
|
|
if (s.includes(serial) || serial.includes(s.replace(/-/g, ''))) return enrich(o)
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
// Get all ONUs on the same OLT port as a given serial
|
|
function getPortNeighbors (serial) {
|
|
const target = getOnuBySerial(serial)
|
|
if (!target || !target.port) return null
|
|
|
|
const neighbors = []
|
|
for (const [, olt] of olts) {
|
|
if (olt.name !== target.oltName) continue
|
|
for (const [, onu] of olt.onus) {
|
|
if (onu.port === target.port && onu.serial !== target.serial) {
|
|
neighbors.push({ ...onu, oltName: olt.name })
|
|
}
|
|
}
|
|
break
|
|
}
|
|
return { target, port: target.port, oltName: target.oltName, neighbors }
|
|
}
|
|
|
|
// Get aggregated port health stats
|
|
function getPortHealth (oltName, port) {
|
|
for (const [, olt] of olts) {
|
|
if (olt.name !== oltName) continue
|
|
const portOnus = []
|
|
for (const [, onu] of olt.onus) {
|
|
if (onu.port === port) portOnus.push({ ...onu, oltName: olt.name })
|
|
}
|
|
const total = portOnus.length
|
|
const online = portOnus.filter(o => o.status === 'online').length
|
|
const offline = portOnus.filter(o => o.status === 'offline')
|
|
|
|
// Classify offline causes
|
|
const causes = {}
|
|
for (const o of offline) {
|
|
const cause = classifyOfflineCause(o.lastOfflineCause)
|
|
causes[cause] = (causes[cause] || 0) + 1
|
|
}
|
|
|
|
return { oltName, port, total, online, offline: total - online, causes, onus: portOnus }
|
|
}
|
|
return null
|
|
}
|
|
|
|
function classifyOfflineCause (raw) {
|
|
if (!raw) return 'unknown'
|
|
const lc = (raw + '').toLowerCase()
|
|
if (lc.includes('dying') || lc.includes('gasp')) return 'dying_gasp'
|
|
if (lc.includes('branch') || lc.includes('fiber cut') || lc.includes('fibre')) return 'branch_fiber_cut'
|
|
if (lc.includes('losi') || lc.includes('los')) return 'loss_of_signal'
|
|
if (lc.includes('lobi') || lc.includes('lob')) return 'loss_of_burst'
|
|
return 'other'
|
|
}
|
|
|
|
const getOltStats = () => [...olts.entries()].map(([host, olt]) => ({
|
|
host, name: olt.name, type: olt.type, onuCount: olt.onuCount, onlineCount: olt.onlineCount,
|
|
lastPoll: olt.lastPoll, lastDuration: olt.lastDuration, lastError: olt.lastError,
|
|
}))
|
|
|
|
const getAllOnus = () => {
|
|
const all = []
|
|
for (const [, olt] of olts) {
|
|
for (const [, onu] of olt.onus) all.push({ ...onu, oltName: olt.name })
|
|
}
|
|
return all
|
|
}
|
|
|
|
function startOltPoller () {
|
|
for (const o of (cfg.OLT_LIST || [])) registerOlt(o)
|
|
if (cfg.OLT_HOST && !olts.has(cfg.OLT_HOST)) {
|
|
registerOlt({ host: cfg.OLT_HOST, community: cfg.OLT_COMMUNITY || 'public', name: cfg.OLT_NAME || cfg.OLT_HOST, type: cfg.OLT_TYPE })
|
|
}
|
|
|
|
if (!olts.size) return log('OLT SNMP poller: no OLTs configured')
|
|
|
|
log(`OLT SNMP poller: ${olts.size} OLT(s), polling every ${POLL_INTERVAL / 1000}s`)
|
|
setTimeout(() => {
|
|
pollAllOlts().catch(e => log('OLT poll error:', e.message))
|
|
pollTimer = setInterval(() => {
|
|
pollAllOlts().catch(e => log('OLT poll error:', e.message))
|
|
}, POLL_INTERVAL)
|
|
}, 10000)
|
|
}
|
|
|
|
function stopOltPoller () {
|
|
if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
|
|
}
|
|
|
|
module.exports = {
|
|
registerOlt, startOltPoller, stopOltPoller,
|
|
getOnuBySerial, getPortNeighbors, getPortHealth, classifyOfflineCause,
|
|
getOltStats, getAllOnus, pollAllOlts, getManageIp,
|
|
}
|