ponfw/renderer/index.html
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

371 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>PON Fleet Upgrader</title>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<!-- Animated neon beam that travels around the screen edge. -->
<div id="edge-glow" aria-hidden="true"></div>
<header class="topbar">
<div class="brand">PON Fleet Upgrader</div>
<nav id="tabs" class="tabs hidden">
<button class="tab" data-tab="dashboard" data-view="#view-dashboard">Dashboard</button>
<button class="tab" data-tab="onu" data-view="#view-fleet">ONU</button>
<button class="tab" data-tab="olt" data-view="#view-olts">OLT</button>
</nav>
<div class="session" id="session-label">Not connected</div>
<button id="btn-logout" class="ghost hidden">Disconnect</button>
</header>
<!-- ================= Login view ================= -->
<section id="view-login" class="view">
<div class="card">
<h1>Connect to MCMS</h1>
<p class="muted">
Session-cookie auth against a Ciena MicroClimate Management System.
The host should be the root URL of the PON Manager web UI
(e.g. <code>https://mcms.example.net</code>).
</p>
<label>Host URL<input id="in-host" type="url" placeholder="https://mcms.example.net" /></label>
<label>Email / username<input id="in-user" type="text" autocomplete="username" /></label>
<label>Password<input id="in-pass" type="password" autocomplete="current-password" /></label>
<label class="inline">
<input id="in-self-signed" type="checkbox" checked />
Accept self-signed TLS certificate
</label>
<div class="row">
<button id="btn-login" class="primary">Connect</button>
<span id="login-error" class="error"></span>
</div>
</div>
</section>
<!-- ================= Dashboard view ================= -->
<section id="view-dashboard" class="view hidden">
<div class="panel-main dash-wrap">
<div class="fleet-head">
<h2>Dashboard</h2>
<div class="row">
<span id="dash-cache" class="dash-cache" title=""></span>
<label class="inline dash-toggle">
<input id="dash-extra" type="checkbox" />
Detailed stats (Rx / FEC scan)
</label>
<button id="dash-refresh" class="ghost">↻ Refresh</button>
</div>
</div>
<p class="muted small" id="dash-status">Connect and the network overview loads here.</p>
<div class="dash-scroll">
<div class="dash-grid" id="dash-tiles"></div>
<div id="dash-sections"></div>
</div>
</div>
</section>
<!-- ================= Fleet view ================= -->
<section id="view-fleet" class="view hidden">
<div class="panel-left">
<h2>Filters</h2>
<label>Name / address contains
<input id="filter-text" type="search" placeholder="e.g. ORKANGER" />
</label>
<label>Equipment ID contains
<input id="filter-equipment" type="text" placeholder="e.g. FT-XGS2110" />
</label>
<label>PON mode
<select id="filter-pon">
<option value="">Any</option>
<option value="GPON">GPON</option>
<option value="XGS-PON">XGS-PON</option>
<option value="10G-EPON">10G-EPON</option>
</select>
</label>
<label>Active version matches
<input id="filter-version" type="text" placeholder="e.g. EV05110R" />
</label>
<label>Exclude active version
<input id="filter-exclude-version" type="text" placeholder="e.g. EV05120R (hide already-upgraded)" />
</label>
<label>Model / version family
<input id="filter-model" type="text" placeholder="e.g. EV051 (Interos Everest 5.11.x)" />
</label>
<label>Registration status
<select id="filter-status">
<option value="">Any</option>
<option value="__down__">Down (Deregistered / Dying Gasp / Disabled)</option>
<option value="Registered">Registered</option>
<option value="Deregistered">Deregistered</option>
<option value="Dying Gasp">Dying Gasp</option>
<option value="Disabled">Disabled</option>
<option value="Disallowed Admin">Disallowed Admin</option>
<option value="Disallowed Error">Disallowed Error</option>
<option value="Disallowed Reg ID">Disallowed Reg ID</option>
<option value="Unspecified">Unspecified</option>
<option value="Unprovisioned">Unprovisioned</option>
<option value="__unknown__">Unknown (no OLT state)</option>
</select>
</label>
<label>Down for at least (days)
<input id="filter-down-days" type="number" min="0" step="1" placeholder="e.g. 30" />
</label>
<button id="btn-refresh" class="primary">Load fleet</button>
<p class="muted small" id="fleet-status"></p>
<h2>Selection</h2>
<div class="selected-summary">
<div><strong id="sel-count">0</strong> ONUs selected</div>
<div class="row">
<button id="btn-select-all">Select all matching</button>
<button id="btn-select-none" class="ghost">Clear</button>
</div>
</div>
<button id="btn-to-campaign" class="primary" disabled>Continue to upgrade →</button>
<button id="btn-delete-selected" class="danger" disabled>Delete selected from MCMS…</button>
<p class="muted small" id="delete-status"></p>
<h2>After-upgrade verification</h2>
<p class="muted small">Open a saved <code>pon-upgrade-*.csv</code> to compare the recorded FEC + optical readings against the current state of those ONUs.</p>
<button id="btn-to-verify" class="ghost">Verify previous upgrade…</button>
<h2>OLT flooding mode</h2>
<p class="muted small">Bulk-check or fix PON flooding mode on OLT NNI services. Default rule: match <code>s0.c76.c0</code> with mode <code>private</code> and switch to <code>auto</code>.</p>
<button id="btn-to-olts" class="ghost">Open OLT inspector…</button>
</div>
<div class="panel-main">
<div class="fleet-head">
<h2>Fleet (<span id="fleet-count">0</span>)</h2>
<div class="row small muted">
<span>Tip: use filters to narrow to one model before selecting all.</span>
</div>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table" id="fleet-table">
<thead>
<tr>
<th><input type="checkbox" id="th-check" /></th>
<th>Serial</th>
<th>Name / address</th>
<th>Equipment ID</th>
<th>PON</th>
<th>Status</th>
<th>Last seen</th>
<th>Active bank</th>
<th>Active version</th>
<th>Inactive version</th>
</tr>
</thead>
<tbody id="fleet-tbody"></tbody>
</table>
</div>
</div>
</section>
<!-- ================= Upgrade campaign view ================= -->
<section id="view-campaign" class="view hidden">
<div class="panel-left">
<h2>Campaign</h2>
<label>Target firmware file
<select id="fw-select"></select>
</label>
<label>Target version
<input id="fw-version" type="text" placeholder="e.g. EV05120R" />
</label>
<div class="row">
<button id="btn-upload-fw" class="ghost">Upload .bin…</button>
<button id="btn-reload-fw" class="ghost">Refresh list</button>
</div>
<h2>Execution</h2>
<label>Mode
<select id="mode-select">
<option value="task">Bulk task (Procedure 8, recommended)</option>
<option value="peronu">Per-ONU PUT (Procedure 7)</option>
</select>
</label>
<label id="row-schedule">Scheduled start (UTC)
<input id="in-schedule" type="datetime-local" />
</label>
<p class="muted small">
Writes always go to the inactive bank (FW Bank Ptr toggled to <code>1 active</code>, or <code>1</code> if unset).
MCMS flips the pointer when the download completes; the ONU reboots into the new image on its next reboot.
</p>
<div class="row">
<button id="btn-back" class="ghost">← Back to fleet</button>
<button id="btn-preview" class="primary">Preview plan</button>
</div>
<div class="row">
<button id="btn-execute" class="danger" disabled>Execute upgrade</button>
</div>
<p id="exec-status" class="small muted"></p>
</div>
<div class="panel-main">
<h2>Dry-run preview</h2>
<p class="muted small">Nothing has been written yet. Review the plan before clicking Execute.</p>
<div class="fleet-table-wrap">
<table class="fleet-table">
<thead>
<tr>
<th>Serial</th>
<th>Name</th>
<th>Active → new active slot</th>
<th>Current slot 0</th>
<th>Current slot 1</th>
<th>Target version</th>
<th>FEC health</th>
<th>Status</th>
</tr>
</thead>
<tbody id="preview-tbody"></tbody>
</table>
</div>
</div>
</section>
<!-- ================= Verify view ================= -->
<section id="view-verify" class="view hidden">
<div class="panel-left">
<h2>Verify previous upgrade</h2>
<p class="muted small">
Pick a saved upgrade-plan CSV (defaults to <code>~/Downloads</code>).
The app re-fetches each ONU's current FEC counters, optical levels,
and active firmware version, then computes a before/after comparison
and saves a verification CSV alongside the original.
</p>
<button id="btn-pick-plan" class="primary">Open plan CSV…</button>
<p class="muted small" id="verify-status"></p>
<h2>Summary</h2>
<div class="selected-summary" id="verify-summary">
<div class="muted small">Open a CSV to start.</div>
</div>
<div class="row">
<button id="btn-verify-back" class="ghost">← Back to fleet</button>
<button id="btn-save-verify" class="primary" disabled>Re-save report</button>
</div>
<p id="verify-save-status" class="small muted"></p>
</div>
<div class="panel-main">
<div class="fleet-head">
<h2>Comparison (<span id="verify-count">0</span>)</h2>
<div class="row small muted">
<span>Note: ONU FEC counters typically reset on the upgrade reboot, so "after &lt; before" is normal. Look at the FEC flag and verdict columns for the actual signal.</span>
</div>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table" id="verify-table">
<thead>
<tr>
<th>Serial</th>
<th>Name</th>
<th>Target → Active now</th>
<th>Applied?</th>
<th>FEC before</th>
<th>FEC now</th>
<th>Optical (RX/TX dBm)</th>
<th>Verdict</th>
</tr>
</thead>
<tbody id="verify-tbody"></tbody>
</table>
</div>
</div>
</section>
<!-- ================= OLT inspector view ================= -->
<section id="view-olts" class="view hidden">
<div class="panel-left">
<h2>Filters</h2>
<label>NNI TAG MATCH pattern
<input id="olt-tag-pattern" type="text" value="s0.c76.c0" placeholder="e.g. s0.c76.c0 or s0.c76.*" />
</label>
<label>Flood-mode filter
<select id="olt-mode-filter">
<option value="any" selected>Any</option>
<option value="private">Private (PON FLOOD ID set)</option>
<option value="auto">Auto (no PON FLOOD ID)</option>
</select>
</label>
<label>OLT name contains
<input id="olt-name-filter" type="search" placeholder="e.g. OLT5" />
</label>
<button id="btn-load-olts" class="primary">Load OLTs</button>
<p class="muted small" id="olt-status"></p>
<h2>Selection</h2>
<div class="selected-summary">
<div><strong id="olt-sel-count">0</strong> OLT(s) selected · <strong id="olt-svc-count">0</strong> service slot(s) will change</div>
<div class="row">
<button id="btn-olt-select-all">Select all matching</button>
<button id="btn-olt-select-none" class="ghost">Clear</button>
</div>
</div>
<h2>Actions</h2>
<p class="muted small">Applied together to every matching NNI service on the selected OLTs, in one GET→edit→PUT per OLT.</p>
<label>Flooding mode
<select id="olt-flood-action">
<option value="">No change</option>
<option value="private2auto" selected>Private → Auto (remove PON FLOOD ID)</option>
<option value="auto2private">Auto → Private (set PON FLOOD ID = 0)</option>
</select>
</label>
<label>DHCPv4 filter
<select id="olt-dhcpv4-action">
<option value="">No change</option>
<option value="pass2umt" selected>pass → umt</option>
<option value="umt2pass">umt → pass</option>
</select>
</label>
<label>DHCPv6 filter
<select id="olt-dhcpv6-action">
<option value="">No change</option>
<option value="pass2umt" selected>pass → umt</option>
<option value="umt2pass">umt → pass</option>
</select>
</label>
<div class="row">
<button id="btn-olts-back" class="ghost">← Back to fleet</button>
<button id="btn-olt-execute" class="danger" disabled>Execute change…</button>
</div>
<p id="olt-exec-status" class="small muted"></p>
</div>
<div class="panel-main">
<div class="fleet-head">
<h2>OLTs (<span id="olt-count">0</span> shown, <span id="olt-total">0</span> total)</h2>
<div class="row small muted">
<span>Each row shows the matching NNI services. Selecting an OLT queues every matching service on it for change.</span>
</div>
</div>
<div class="fleet-table-wrap">
<table class="fleet-table" id="olt-table">
<thead>
<tr>
<th><input type="checkbox" id="olt-th-check" /></th>
<th>OLT MAC</th>
<th>Name</th>
<th>PON mode</th>
<th>FW</th>
<th>Matching NNI services</th>
</tr>
</thead>
<tbody id="olt-tbody"></tbody>
</table>
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>