ponfw/web
Jon Vanvik 5e3d9271a8 olt: add bulk DHCPv4/DHCPv6 filter edit alongside flood mode
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>
2026-06-23 14:57:37 +02:00
..
deploy web/deploy: add one-shot install.sh 2026-06-23 14:30:06 +02:00
public Add multi-user web server variant (browser UI behind a reverse proxy) 2026-06-23 14:07:23 +02:00
cache.js dashboard: live cache-age indicator 2026-06-23 14:25:46 +02:00
package.json Add multi-user web server variant (browser UI behind a reverse proxy) 2026-06-23 14:07:23 +02:00
README.md web/deploy: add one-shot install.sh 2026-06-23 14:30:06 +02:00
server.js olt: add bulk DHCPv4/DHCPv6 filter edit alongside flood mode 2026-06-23 14:57:37 +02:00

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.js and ../renderer/app.css as-is and rewrites ../renderer/index.html on 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 (a window.api shim: Electron IPC → fetch + NDJSON streaming, Electron file dialogs → browser file pickers, CSV-to-Downloads → Blob download), and public/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 getOnuConfig used 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 / npm if missing (and checks Node ≥ 18),
  • creates the unprivileged ponfw system user,
  • runs npm install --omit=dev (tough-cookie only; skips Electron),
  • writes /etc/pon-fleet-web.env from the template on first run,
  • installs the systemd unit with WorkingDirectory set 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 is HttpOnly, SameSite=Lax, and Secure behind 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 .bin and 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.