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>
Adds an ONU info page and splits the ONU tab into two sub-tabs (Info / stats,
Fleet & upgrade). The ONU tab now lands on Info; campaign/verify keep the
Fleet sub-tab lit.
ONU info page (#view-onu-info): master-detail over the already-loaded fleet
(search + status filter, no new bulk fetch). Per-ONU detail shows identity,
link/optical + FEC (read from the merged ONU-STATE), firmware, and a CPE card.
CPE (customer router):
- McmsClient.getCpe — GET /api/cpe/onu/<id>/ (unversioned, [status,doc] tuple).
- src/cpe.js (pure, shared): summarizeCpe + leaseHealth. Renewal rule —
<50% of the lease remaining => "renewal overdue" (amber), past end =>
expired. v4 expiry = DHCP "Remove Time"; v6 spans Last Success ->
Preferred Lifetime (T1 midpoint), with Prefix Delegation surfaced.
- api:getOnuCpe (main.js + web/server.js, exposed in both bridges).
OUI vendor (src/oui.js, pure, shared): lazy fetch of the IEEE oui.csv,
prefix->org map, cached in memory ~30 days + best-effort to disk
(PFW_CACHE_DIR / temp; PrivateTmp makes /tmp writable). Resolved server-side
in getOnuCpe; every failure degrades to "unknown OUI". CPE itself is not
TTL-cached (lease must be live); only the OUI list is.
Dashboard abnormal Rx/Tx rows are now clickable -> deep-link to that ONU's
info page (showListModal gained per-item onClick).
Docs: CLAUDE.md §17 + IPC table + intro; README.
Verified: node --check; cpe.js/oui.js unit-tested (15/15: lease ok/warn/
expired/unknown + 50% boundary, v6 prefix parse, OUI quoted-CSV parse +
lookup); ONU info page + CPE card rendered offscreen over HTTP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalises the OLT bulk tool from flood-mode-only to arbitrary NNI-service
edits. DHCP modes live nested under each NNI entry's Filter object
(Filter.DHCPv4 / Filter.DHCPv6, e.g. "pass" <-> "umt"), confirmed from a
real OLT-CFG paste-back.
- src/mcms-api.js: planFloodChange -> planNniEdit(oltCfg, {tagPattern, flood,
dhcpv4, dhcpv6}); each concern optional. DHCP is value-replacement,
replace-only-when-present (never invents the key; leaves EAPOL/PPPoE/NDP).
Still non-mutating (clones the entry AND the Filter). Returns changes with
per-entry edits[]. summarizeOltNniNetworks surfaces dhcpv4/dhcpv6; new
dhcpModeOfNni helper.
- main.js + web/server.js: executeFloodChange handler passes flood/dhcpv4/
dhcpv6 ops through (channel name kept for wire compat).
- renderer: OLT inspector now has three action dropdowns (Flooding / DHCPv4 /
DHCPv6, defaults flood private->auto + DHCP pass->umt); table shows DHCP
chips + per-service "will change (flood, DHCPv4, …)"; flood-mode filter
defaults to Any so DHCP-on-pass auto-flood OLTs still show; CSV report
lists every per-NNI edit (pon-olt-nni-*.csv).
Verified: node --check; planNniEdit unit-tested against the real fixture +
a pass variant (13/13: no-mutation, EAPOL/PPPoE/NDP preserved,
replace-when-present, flood/DHCP independence); OLT view rendered offscreen
showing correct per-service "will change" markers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds web/deploy/install.sh — full LXC setup in one command. It derives the
repo path from its own location (clone anywhere; /opt/ponfw recommended),
installs git/node/npm if missing (checks Node >= 18), creates the ponfw
service user, runs npm install --omit=dev, writes /etc/pon-fleet-web.env on
first run, and installs+enables the systemd unit with WorkingDirectory set to
the checkout. Idempotent; warns if cloned under /home (ProtectHome blocks it).
README + CLAUDE.md §16 updated to lead with install.sh and clarify the clone
location.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web server's dashboardStats now reports how stale its shared cache
snapshot is — data.cache = { enabled, ttlMs, ageMs }, using the age of the
oldest dataset feeding the view (OLT-STATE, plus ONU-STATE under detailed
scan). cache.js gains ageOf(key).
The dashboard renders a live-ticking badge from it (renderCacheBadge):
- within TTL -> "◷ cached Ns ago" (info)
- past TTL -> same, warn-coloured (next load will refetch)
- no cache -> "● live" (Electron app, or PFW_CACHE_TTL_SECONDS=0)
The Dashboard Refresh button now sends fresh:true to bypass the cache and
reset the age; the detailed-stats toggle and auto-loads use the cache.
Shared renderer change, so the Electron app shows "live" (it sends no cache
field). Verified offscreen over HTTP: cached / stale / live states render
with the right text + colour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Caching (reduce MCMS API load with multiple operators):
- web/cache.js: in-memory TTL cache with single-flight, keyed by MCMS host.
Concurrent identical bulk reads collapse into ONE upstream fetch; everyone
on the same MCMS shares the snapshot.
- Wired into the bulk reads only (ONU/OLT config+state lists, controllers,
firmware). Per-ONU read-modify-write and the FEC pre-flight stay live.
- Writes (delete / per-ONU + bulk upgrade / flood change / firmware upload)
invalidate the affected datasets for that host, so changes show on the
next load instead of waiting out the TTL.
- PFW_CACHE_TTL_SECONDS (default 60, 0 disables). Authenticated
_cacheStats / _cacheClear endpoints for ops.
Deploy (Debian 13 LXC, clone-to-run) in web/deploy/:
- pon-fleet-web.service (runs as unprivileged ponfw, hardened, repo
read-only, no disk writes), pon-fleet-web.env.example, update.sh
(git pull + npm install --omit=dev + restart; uses install not ci since
package-lock.json is gitignored).
- README: full LXC setup, Nginx Proxy Manager proxy-host + access-list
notes. Streams already send X-Accel-Buffering: no so NPM/nginx don't
buffer the NDJSON progress.
Verified: node --check; cache unit-tested (single-flight, TTL hit/expiry,
fresh, invalidate, key isolation, disable — 9/9); server boots and reports
the cache TTL; _cacheStats gated to logged-in sessions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New web/ subfolder: a Node http server that serves the SAME tooling as the
Electron app to any browser, with per-session MCMS credentials so several
operators can use it at once behind a TLS reverse proxy. Mobile-responsive.
Not a fork — it reuses the core and the UI:
- serves renderer/app.js + app.css verbatim and rewrites index.html on the
fly (mobile viewport + browser window.api shim + responsive overlay)
- web/public/api-web.js: a drop-in window.api over fetch + NDJSON streaming;
Electron file dialogs -> <input type=file>, CSV-to-Downloads -> Blob
- web/server.js: per-session McmsClient (cookie pfw_sid), idle expiry,
routes mirroring the IPC handlers, NDJSON for the 6 streaming ops
- pure Node http, no new deps (borrows ../node_modules tough-cookie)
Shared improvements (benefit both apps):
- src/dashboard.js: extracted, pure dashboard aggregation (main.js now uses
it); adds abnormal-Tx detection alongside abnormal-Rx
- renderer: dashboard health stats are now clickable — abnormal Rx / Tx /
lasers-off pop a list of the offending devices (like the iOS app), via a
new showListModal
Docs: web/README.md (run + reverse-proxy + security), CLAUDE.md §16.
Verified: node --check all; started the server and confirmed transformed
index, shared-asset serving, 401 auth gate, login (per-session client), and
NDJSON wiring; rendered login + dashboard drilldown + mobile over HTTP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>