ponfw/renderer/index.html
Jon Vanvik 6f4a315947 mobile: fix edge beam (transform-rotate) + ONU master-detail layout
Edge glow: iOS Safari won't animate an @property-driven conic-gradient
angle, so the beam sat static / off the border on mobile. Reworked to the
robust technique: a .beam child masked to the border ring contains an
oversized conic-gradient ::before spun with transform: rotate 0→360deg; the
glow halo is a drop-shadow on the parent (survives the child mask). Works on
iOS/Safari; honors prefers-reduced-motion.

ONU info on narrow screens: was stacking the picker above the detail, so
selecting an ONU meant a long scroll to find it. Now master-detail — show
the picker OR the detail (not both), toggled by .show-detail on
#view-onu-info via a shared @media(max-width:860px) rule; selecting shows the
detail with a "← ONU list" back button, tab/sub-tab nav resets to the
picker. (Earlier commit already de-compacted the list: extra filters +
two-line rows.)

Docs: CLAUDE.md §15 (edge glow technique) + §17 (master-detail).

Verified offscreen at 414px: beam element present; panel-left/​panel-main
display toggles correctly on select/back; back button visible on mobile;
rendered both states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:33:07 +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 class="beam"></div></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>