Commit Graph
2 Commits
Author SHA1 Message Date
2GT-RichandClaude Opus 5 82efcaa371 fix(wireless,topology): CAPsMAN write guards, MAC neighbour resolution, unified panel (v0.23.0-beta)
Closes #95. Addresses the second round of #94 feedback. Includes a regression
introduced in v0.22.0.

Wireless write paths were not CAPsMAN-aware (regression)
  v0.22.0 widened getAP() and the bulk-SSID selector to include cap/controller
  roles so a controller — normally tagged a router — would stop being invisible to
  the wireless section. Those same helpers gate the write endpoints, so the change
  also allowed writes to devices whose radios a controller owns. Reads needed
  widening; writes needed a guard, and did not get one.

  Five paths now refuse or skip provisioned radios: bulk SSID deployment, single
  interface create, interface edit, interface delete, and guest network setup.
  Delete was the worst of them — RouterOS will not remove a provisioned interface,
  but the cached row was deleted anyway, so it vanished from the UI until the next
  poll brought it back.

  The check is shared: partitionRadios() splits physical radios into locally
  controllable and controller-owned, assertNotCapsmanManaged() refuses an edit to a
  provisioned interface and names the controller.

Neighbours identified only by MAC (#95)
  Topology resolved a neighbour to a managed device by IP address. A trunk port has
  no IP, so LLDP across one reports a MAC and an identity but no address — and
  those links stayed unresolved, drawing an external node beside the very device it
  already had on the map. Two directly connected devices appeared four times.

  Resolution by MAC now runs first, before address matching, because a MAC is
  hardware identity and unique fleet-wide where an address is not. This is the same
  join the CAPsMAN radio matching uses.

CAPsMAN panel, from the reporter's testing
  - Radios and provisioned interfaces were two tables describing the same things
    from different angles; they are now one, per interface, with the access point,
    SSID, band, frequency, channel, clients and status.
  - Provisioning rules sit inside the configuration they apply. Rules referencing a
    configuration we did not collect are listed separately rather than dropped,
    since that usually means a stale reference.
  - The Wireless header reported zero clients while the Clients page reported 32:
    it summed the CAP-local count, which is empty under central forwarding. It now
    takes the greater of that and the controller's per-radio counts, so a controller
    that is also a CAP is not double-counted.
  - A remote radio could be attributed to the controller by the five-octet prefix
    fallback when the two shared a MAC block. Prefix matches now refuse the
    controller for a non-local radio; exact matches are still honoured.

Radios tab followed device_type
  device_type is user-supplied tagging, and a router with radios had no way to
  display them. The tab now follows the detected wifi_role, which is a fact about
  the hardware.

48 tests on the CAPsMAN module, 22 on topology, including a fixture built from the
device output in #95. Verified against the fleet: topology output unchanged, so MAC
resolution merged nothing it should not have; guards correctly permissive on a
standalone AP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:48:29 -07:00
2GT-RichandClaude Opus 5 b03ea87f11 fix(topology,ui): spurious cross-segment links, CPU rounding, serial column (v0.20.1-beta)
Closes #90, #91, #92.

#90 — Spurious topology links
  Topology treated IP addresses and system identities as unique across the whole
  fleet. They are unique only within a broadcast domain, so an installation with
  physically separate segments that each reuse 10.2.0.0/24 saw devices in one
  segment joined to devices in another.

  The reporter pointed at shared-segment detection, and that is one of four sites
  with the same flaw — but not the first. Step 2 resolves a neighbour IP to a
  managed device from a global first-wins map, so a reused address silently
  resolves to whichever device was loaded first; the union-find in step 6 then
  merges on the resulting device id, a correct-looking key derived from bad data.
  Fixing only step 6 would have left the reported link in place.

  Addresses and identities are now scoped to the device that reported them
  wherever they name a neighbour, so they still collapse one device's duplicate
  sightings but can never join two devices. Resolution and LLDP-coverage
  suppression additionally require the address to be unambiguous fleet-wide.
  MAC addresses and resolved device ids are unaffected — they identify hardware,
  and genuinely shared segments still merge on them.

  Ambiguity detection deliberately ignores MAC divergence. A switch assigns a MAC
  per port, so a device with two uplinks is reported under several MACs; an
  earlier version of this fix treated that as a conflict and refused to resolve
  ordinary multi-port hardware. Caught on a live fleet, where three sightings of
  one CRS510 disagreed on MAC but agreed on identity. The signals used instead are
  an address claimed by two managed devices, or sightings that disagree about the
  neighbour's identity.

  The graph builder is extracted from the route into a pure module with 16 tests,
  including a fixture of the reporter's five-device, three-segment network. It had
  no test coverage before, which is why a flaw this systemic went unnoticed.

  Verified against a live fleet: graph output is identical before and after, and
  the ambiguity list is empty when addressing is clean.

#91 — CPU load not rounded
  The devices table rendered the raw value from a 5-minute Influx mean, so a
  device idling between 1% and 2% displayed as 1.3333333333333333%. Rounded for
  display; the sparkline keeps the unrounded series so its curve stays smooth.

#92 — Serial number in the device table
  Serials were already collected, stored, returned by the API and matched by
  global search, so this is presentation only: a sortable SERIAL column, and
  serial added to the table's filter predicate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:47:18 -07:00