ponfw/renderer/index.html
Jon Vanvik 2046fad0f8 onu detail polish + clickable OLT detail modal
- Fix alarm card overflow: alarm rows stack (severity+text on one line that
  wraps, meta below) instead of squeezing the text to ~0 width next to the
  nowrap meta — which had made long alarm text render one char per line in
  narrow columns. Also min-width:0 on cards/kv so long IPv6/descr strings
  don't blow out the grid track.
- Combine Parent OLT + Upstream switch into one "Parent OLT & uplink" card.
- ONU list/search de-compacted: add equipment/version + PON-mode filters;
  two-line rows (serial+status, then equipment · version · last-seen).
- UNI card labels the speed as configured ("cfg Auto/Auto"); negotiated
  speed isn't in the payloads — link up/down still comes from LAN-LOS.

Clickable parent OLT -> OLT detail modal (showOltModal):
- new src/oltdetail.js (pure, shared; reuses summarizeAlarms) + getOltState
  / getOltAlarms client methods + api:getOltDetail (main + web + bridges).
- shows identity, traffic + util, ASIC temperature, ONU counts, laser,
  alarms, upstream switch; plus a connected-ONU list with per-ONU
  FEC/optical built from the loaded fleet (filtered by _status.oltMac),
  each row clickable back into that ONU's detail.

Docs: CLAUDE.md §17 + IPC table.

Verified: node --check; oltdetail.js unit-tested 9/9 (fw active-bank,
traffic down=TX/up=RX, util, ASIC temp, ONU counts, laser, LLDP, alarms);
rendered the OLT modal + richer list offscreen and confirmed the alarm-text
wrap fix (alarm head ~1-2 lines, not vertical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:10:34 +02:00

420 lines
16 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-onu-info">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>
<!-- ================= ONU info / stats view ================= -->
<section id="view-onu-info" class="view hidden">
<div class="panel-left">
<nav class="subtabs">
<button class="subtab" data-subview="#view-onu-info">Info / stats</button>
<button class="subtab" data-subview="#view-fleet">Fleet &amp; upgrade</button>
</nav>
<h2>Find ONU</h2>
<label>Search (name / address / serial)
<input id="onu-info-search" type="search" placeholder="e.g. ORKANGER or GNXS05…" />
</label>
<label>Equipment / version contains
<input id="onu-info-eq" type="search" placeholder="e.g. FT-XGS2110 or EV051" />
</label>
<label>PON mode
<select id="onu-info-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>Registration status
<select id="onu-info-status">
<option value="">Any</option>
<option value="__down__">Down (Dereg / 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="__unknown__">Unknown (no OLT state)</option>
</select>
</label>
<button id="onu-info-load" class="primary">Load fleet</button>
<p class="muted small" id="onu-info-status-line">Load the fleet, then pick an ONU.</p>
<div class="onu-info-list" id="onu-info-list"></div>
</div>
<div class="panel-main">
<div id="onu-info-detail" class="onu-detail">
<p class="muted">Select an ONU on the left to see its stats and the CPE (customer router) behind it.</p>
</div>
</div>
</section>
<!-- ================= Fleet view ================= -->
<section id="view-fleet" class="view hidden">
<div class="panel-left">
<nav class="subtabs">
<button class="subtab" data-subview="#view-onu-info">Info / stats</button>
<button class="subtab" data-subview="#view-fleet">Fleet &amp; upgrade</button>
</nav>
<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>