onu detail: parent OLT + LLDP switch + traffic history + alarms + UNI; single-ONU upgrade

De-compacts the ONU info page into a responsive card grid and enriches it
with one new endpoint (api:getOnuDetail) summarized by the pure, shared
src/onudetail.js:
- Parent OLT (name/model/FW/PON) — follows the OLT MAC from the ONU
  alarm-history doc to its OLT-CFG.
- Upstream switch (LLDP neighbour of the OLT NNI) from the alarm-history
  Switch block: system name, port, IPv4, chassis.
- Traffic (last ~hour) with current + peak down/up and inline SVG
  sparklines, from /onus/stats/ (down = OLT TX rate, up = OLT RX rate).
- UNI-ETH ports (speed/duplex/enable) + last Ethernet LOS (LAN-LOS alarm).
- Alarms: active-first, severity-sorted with raised counts + last raised.
- CPE card folded into the same single fetch.

New client methods getOnuAlarms + getOnuStatsSeries; api:getOnuDetail in
main.js + web/server.js, exposed in both bridges. Not TTL-cached (live).

Single-ONU upgrade: the Firmware card gets a firmware picker + "Upgrade this
ONU" button reusing the bulk per-ONU path (executePerOnu with one id,
Procedure 7 inactive-bank write), behind a typed UPGRADE confirm; refreshes
the detail on success.

Docs: CLAUDE.md §17 + §5.11 (stats endpoint now used for the sparkline) +
IPC table.

Verified: node --check; onudetail.js unit-tested 11/11 against the real
alarm-history / cfg / stats / OLT-cfg fixtures (firmware banks, UNI, alarm
sort, LAN-LOS, OLT active-bank FW, LLDP switch, traffic skip-nontraffic +
down/up mapping + current/peak); card grid rendered offscreen (9 cards, 2
sparklines, upgrade button).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon Vanvik 2026-06-23 15:47:50 +02:00
parent 55e95ed467
commit 276c3e9faa
9 changed files with 474 additions and 46 deletions

27
main.js
View file

@ -15,6 +15,7 @@ const {
const { summarizeDashboard } = require('./src/dashboard');
const { summarizeCpe } = require('./src/cpe');
const { lookupOui } = require('./src/oui');
const { summarizeOnuDetail } = require('./src/onudetail');
const { planUpgrade, activeBankPtr, inactiveBank } = require('./src/bank-strategy');
/** @type {McmsClient | null} */
@ -238,6 +239,32 @@ ipcMain.handle('api:getOnuCpe', async (_ev, { onuId }) => {
}
});
/**
* Rich per-ONU detail for the info page: firmware banks, UNI-ETH ports +
* last Ethernet LOS, alarms, parent OLT, upstream switch (LLDP), a traffic
* time-series, and the CPE summary one round of fetches summarized via
* src/onudetail.js + src/cpe.js.
*/
ipcMain.handle('api:getOnuDetail', async (_ev, { onuId, windowMin = 60 }) => {
try {
const c = requireClient();
const [cfg, alarmsDoc, statsSeries] = await Promise.all([
c.getOnuConfig(onuId).catch(() => null),
c.getOnuAlarms(onuId).catch(() => null),
c.getOnuStatsSeries(onuId, { minutes: windowMin }).catch(() => []),
]);
const oltMac = alarmsDoc?.OLT?.['MAC Address'];
const oltCfg = oltMac ? await c.getOltConfig(oltMac).catch(() => null) : null;
const detail = summarizeOnuDetail({ cfg, alarmsDoc, statsSeries, oltCfg, windowMin });
const cpeDoc = await c.getCpe(onuId).catch(() => null);
detail.cpe = summarizeCpe(cpeDoc);
if (detail.cpe) detail.cpe.cpeVendor = await lookupOui(detail.cpe.cpeMac);
return { ok: true, data: detail };
} catch (err) {
return { ok: false, error: toWireError(err) };
}
});
ipcMain.handle('api:listOnuFirmware', async () => {
try {
const c = requireClient();