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>
This commit is contained in:
Jon Vanvik 2026-06-23 14:57:37 +02:00
parent de037c8795
commit 5e3d9271a8
7 changed files with 232 additions and 118 deletions

View file

@ -24,7 +24,7 @@ const crypto = require('crypto');
const {
McmsClient, McmsApiError, extractOnuHealth,
summarizeOltNniNetworks, planFloodChange,
summarizeOltNniNetworks, planNniEdit,
} = require('../src/mcms-api');
const { planUpgrade } = require('../src/bank-strategy');
const { summarizeDashboard } = require('../src/dashboard');
@ -454,14 +454,14 @@ const streamHandlers = {
async executeFloodChange(s, body, write) {
const {
oltIds, tagPattern, fromMode = 'private', targetMode = 'auto',
ponFloodIdValue = 0, concurrency = 3,
oltIds, tagPattern, flood = null, dhcpv4 = null, dhcpv6 = null,
concurrency = 3,
} = body;
const results = await runPool(oltIds, concurrency, async (id) => {
try {
const cfg = await s.client.getOltConfig(id);
if (!cfg) throw new Error('OLT not found');
const plan = planFloodChange(cfg, { tagPattern, fromMode, targetMode, ponFloodIdValue });
const plan = planNniEdit(cfg, { tagPattern, flood, dhcpv4, dhcpv6 });
if (plan.changes.length === 0) return { oltId: id, ok: true, noop: true, changes: [], skipped: plan.unchanged };
await s.client.putOltConfig(id, plan.newDoc);
return { oltId: id, ok: true, changes: plan.changes, skipped: plan.unchanged };