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> |
||
|---|---|---|
| .. | ||
| deploy | ||
| public | ||
| cache.js | ||
| package.json | ||
| README.md | ||
| server.js | ||
PON Fleet — web server
A multi-user, browser-based front end for the same tooling as the Electron PON Fleet Upgrader (Dashboard / ONU firmware upgrades / OLT PON flooding mode). Built to sit behind a TLS-terminating reverse proxy so several operators can use it at once, each with their own MCMS login.
How it relates to the Electron app
It does not duplicate the app — it reuses it:
- Core logic comes straight from
../src/(mcms-api.js,bank-strategy.js,dashboard.js). One source of truth for every MCMS quirk. - The UI is the same files. The server serves
../renderer/app.jsand../renderer/app.cssas-is and rewrites../renderer/index.htmlon the fly (adds a mobile viewport, swaps in the browser API shim + a small responsive overlay). So the Electron app and the web app never drift. - The only web-specific code is
server.js,public/api-web.js(awindow.apishim: Electron IPC →fetch+ NDJSON streaming, Electron file dialogs → browser file pickers, CSV-to-Downloads → Blob download), andpublic/web.css(responsive overlay).
Because of the ../ references this folder must live inside
pon-fleet-upgrader/ (it borrows the already-installed tough-cookie from
../node_modules). No separate npm install is needed.
Run
cd pon-fleet-upgrader/web
npm start # node server.js — listens on 127.0.0.1:8080
npm run check # node --check on server.js + api-web.js
Then browse to http://127.0.0.1:8080. Each browser logs in with its own
MCMS host + credentials; the server keeps a separate McmsClient (separate
cookie jar) per session.
Configuration (env)
| Var | Default | Meaning |
|---|---|---|
PORT |
8080 |
listen port |
HOST |
127.0.0.1 |
bind address — 0.0.0.0 if the proxy is on another host |
PFW_CACHE_TTL_SECONDS |
60 |
shared upstream cache TTL; 0 disables (see Caching) |
PFW_IDLE_MINUTES |
30 |
idle-session expiry (logs the MCMS session out) |
PFW_SECURE_COOKIE |
off | force the Secure cookie flag (else inferred from X-Forwarded-Proto: https) |
PFW_VERBOSE |
off | verbose MCMS request logging (very noisy with many users) |
Caching (fewer MCMS requests)
The server keeps a small in-memory cache so multiple operators don't each
hammer MCMS. The heavy bulk reads — ONU configs, ONU states, OLT states,
OLT configs, controller configs, firmware inventory — are cached with a TTL
(PFW_CACHE_TTL_SECONDS, default 60s) and keyed by MCMS host, so:
- Everyone pointed at the same MCMS shares one snapshot. Ten operators loading the fleet within a minute trigger one upstream fetch, not ten.
- A single-flight guard means even simultaneous cold loads collapse into one upstream request (the rest await it).
- Different MCMS hosts never share, and the dashboard's light path reuses the same OLT-STATE snapshot the fleet view already fetched.
What is not cached (always live, by design):
- Per-ONU
getOnuConfigused right before a firmware PUT (read-modify-write must see the current bank state). - The FEC pre-flight and verify per-ONU
getOnuState(operators want the reading at decision time).
Writes invalidate the affected datasets for that MCMS host, so a delete /
firmware push / flood-mode change is reflected on the next load instead of
waiting out the TTL. Tune the TTL up to cut MCMS load further, or set
PFW_CACHE_TTL_SECONDS=0 to disable caching entirely. POST /api/_cacheStats
(authenticated) shows hits/misses/coalesced; POST /api/_cacheClear drops the
caller's MCMS-host snapshots.
Deploy on a Debian 13 LXC (clone-to-run)
Files in web/deploy/: install.sh (one-shot setup), update.sh, a systemd
unit, and an env template.
Where to clone: anywhere — install.sh points the service at wherever it
finds itself. /opt/ponfw is recommended: it's the FHS spot for this kind of
software, and the hardened unit sets ProtectHome=true, which would block the
service from reading a checkout under /home.
# --- as root on the LXC, once ---
git clone ssh://forgejo@int.git.vanvikinternet.no/Svorka/ponfw.git /opt/ponfw
sudo /opt/ponfw/web/deploy/install.sh
$EDITOR /etc/pon-fleet-web.env # optional: HOST / PFW_SECURE_COOKIE / TTL
sudo systemctl restart pon-fleet-web # if you edited the env
install.sh is idempotent and:
- installs
git/nodejs/npmif missing (and checks Node ≥ 18), - creates the unprivileged
ponfwsystem user, - runs
npm install --omit=dev(tough-cookie only; skips Electron), - writes
/etc/pon-fleet-web.envfrom the template on first run, - installs the systemd unit with
WorkingDirectoryset to this checkout, then enables + starts it, - prints the LXC IP and the NPM forward target.
The checkout is root-owned and read-only to the service; the service runs as
ponfw and writes nothing to disk.
journalctl -u pon-fleet-web -f # logs
sudo /opt/ponfw/web/deploy/update.sh # update: git pull + npm install + restart
Behind Nginx Proxy Manager
Point an NPM Proxy Host at the server:
-
Forward Hostname/IP: the LXC's address · Forward Port:
8080 -
Scheme:
http(NPM terminates TLS; request an SSL cert in NPM) -
Enable Block Common Exploits; add your Access List for auth/IP allow-listing (this is your access control — the app itself has no user directory beyond MCMS login).
-
Websockets Support on is harmless. The progress streams (bulk delete, FEC pre-flight, flood change) are plain NDJSON over HTTP and already send
X-Accel-Buffering: no, which nginx/NPM honour, so they stream live with no extra config. If you ever see progress arrive only at the end, add to the host's Advanced tab:proxy_buffering off; proxy_read_timeout 300s;
If NPM runs on a different host than this LXC, set HOST=0.0.0.0 in
/etc/pon-fleet-web.env and restrict reachability to the NPM host via the
LXC firewall + the NPM access list.
Security notes
- The server holds each logged-in user's live MCMS session cookie in
memory. Run it behind HTTPS, keep
HOST=127.0.0.1, and rely on the idle timeout. The session cookie isHttpOnly,SameSite=Lax, andSecurebehind TLS. - "Accept self-signed TLS certificate" disables verification to MCMS for that session only (same as the desktop app).
- There's no app-level user directory — anyone who can reach the page and has valid MCMS credentials can log in. Restrict network access at the proxy (mTLS / SSO / IP allow-list) if you need more than MCMS auth.
- Several operators running bulk operations at once multiply load on MCMS;
the page-size-100 / timeout mitigations from the main
CLAUDE.md§5 still apply per session.
What differs from the desktop app
- CSV files download through the browser instead of auto-saving to
~/Downloads(a server can't write to each user's machine). Same filenames, same BOM/CRLF/quoted format. - Firmware
.binand plan-CSV selection use the browser file picker. - Everything else — views, filters, FEC logic, flood-mode rules, the dashboard and its drill-downs — is the shared renderer code.