feat(wireless): CAPsMAN awareness and read-only controller view (v0.22.0-beta)

Addresses #94, phases A and B. Write operations are deliberately out of scope.

An access point provisioned by CAPsMAN holds none of its own wireless
configuration, so /interface/wifi/print returns rows with the configuration.* and
security.* fields absent. normalizeWifiInterface mapped nothing, stored nulls, and
the UI rendered an AP with no SSID, security or band — indistinguishable from one
that is simply broken.

Phase A — stop showing blanks
  Devices are classified standalone / cap / controller / controller_cap from
  /interface/wifi/capsman and /interface/wifi/cap. Both toggles can be on: MikroTik
  supports a controller that also runs its own radios under itself, and the issue
  calls that case out, so it gets its own role.

  Provisioned interfaces are marked and labelled in the Radios tab with the
  controller's MAC and an explanation that the blank fields live on the controller.
  SSID, mode and channel are recovered from RouterOS's status line where present,
  so the AP is described rather than blank.

  Role is the signal, not missing data: an AP whose SSID is empty because the
  controller owns it looks identical to an unconfigured one, and only the toggle
  distinguishes them. A CAP can still hold locally-configured interfaces alongside
  provisioned ones, so per-interface state is checked rather than assuming the
  whole device.

Phase B — read-only controller view
  Controllers now collect the radios they manage, named configurations, and
  provisioning rules, surfaced as a CAPsMAN panel on the Wireless page grouped by
  the access point each radio physically lives on.

  CAPs are matched to managed devices by radio MAC from the controller's own
  /interface/wifi/radio list, not by resolving addresses through neighbours as the
  issue proposed. A MAC is hardware identity and globally unique, so unlike an
  address it cannot mean two devices on two segments — the failure fixed in
  v0.20.1. A radio MAC often differs from the interface MAC in the final octet, so
  lookup falls back to a five-octet prefix while keeping the OUI exact. A CAP that
  is not a managed device is surfaced rather than hidden; that is usually the
  reason an AP appears to be missing from the fleet.

  The wireless section required device_type = 'wireless_ap' in five places, which
  hid a dedicated controller — normally classified as a router — from every
  wireless view. Collection was gated the same way. Both now include CAPsMAN roles,
  and the collector probes an unknown device once, records the role, and skips
  cheaply forever after.

Not included
  Provisioning, config edits and interface enable/disable. A CAPsMAN configuration
  applies to every AP bound to it simultaneously, which is the fleet-wide version
  of the failure Change Guard exists to prevent. It needs the same prediction and
  revert treatment rather than being bolted on. Legacy /caps-man (pre-RouterOS 7)
  is a separate API tree and is not covered.

Verified: API shapes probed against RouterOS 7.23 rather than taken from docs.
24 unit tests on the pure parsing and matching. No CAPsMAN hardware here, so the
read path was exercised against a seeded controller with a local radio, a matched
two-radio CAP and an unmanaged CAP; grouping, provisioning and SSID resolution all
came out correct. Confirmed no regression on a fleet with no controller: roles
detected correctly, no interface falsely flagged, wireless views unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2GT-Rich
2026-08-20 08:25:35 -07:00
co-authored by Claude Opus 5
parent 19617e8357
commit 14bab24c7e
14 changed files with 1134 additions and 18 deletions
+26 -1
View File
@@ -2,7 +2,7 @@
A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface.
![Version](https://img.shields.io/badge/version-0.21.0_Beta-blue)
![Version](https://img.shields.io/badge/version-0.22.0_Beta-blue)
![License](https://img.shields.io/badge/license-AGPLv3-blue)
![Docker](https://img.shields.io/badge/docker-compose-2496ED?logo=docker&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-5.3-3178C6?logo=typescript&logoColor=white)
@@ -237,6 +237,31 @@ Configure under **Settings → Change Guard** (`change_guard_enabled`, `change_g
- **WiFi connectivity success** funnel (Association / Authentication / DHCP) derived from device logs
- **Rogue & neighbor AP detection** — cross-references stored AP-scan results against your own SSIDs and radio MACs. A foreign BSSID broadcasting one of *your* SSIDs is flagged as a **rogue / evil-twin** (and raised in Operations → Things to handle); everything else is a ranked neighbor-network inventory
### CAPsMAN
Access points provisioned centrally by CAPsMAN hold none of their own wireless configuration — the
controller owns it — so reading an AP directly returns blank SSID, security and band. The platform
now recognises that instead of showing an access point that looks broken:
- **Role detection** — each device is classified as standalone, CAP, controller, or controller-with-
local-radios (a supported MikroTik arrangement) from `/interface/wifi/capsman` and `/interface/wifi/cap`
- **Provisioned interfaces are labelled** — a CAPsMAN badge on the Radios tab, and an explanation that
the blank fields live on the controller, with the controller's MAC. SSID, mode and channel are
recovered from RouterOS's status line where present
- **CAPsMAN panel on the Wireless page** — every controller with the access points it provisions,
grouped per AP: radios, hardware type, live channel, provisioned SSIDs with client counts, named
configurations, and the provisioning rules that bind them
- **CAPs are matched to managed devices by radio MAC**, taken from the controller's own
`/interface/wifi/radio` list. A MAC is hardware identity and globally unique, so unlike an IP it
cannot mean two devices on two segments. A CAP that is *not* in the fleet is surfaced rather than
hidden, since that is usually why an AP appears to be missing
- **Controllers are no longer excluded** — the wireless section previously required
`device_type = 'wireless_ap'`, which hid a dedicated controller (normally classified as a router)
**Read-only for now.** Editing a CAPsMAN configuration applies it to every access point bound to it
at once, so provisioning and config edits are deliberately not exposed until they carry the same
prediction-and-revert protection that device changes do. Legacy `/caps-man` (the pre-RouterOS 7
controller) is a separate API tree and is not covered yet.
### Guest WiFi (Hotspot)
A one-click captive-portal guest network on RouterOS Hotspot, under **Wireless → Guest WiFi**:
- **Guided setup wizard** — creates the whole guest network in one pass: a new **guest SSID** (a virtual AP on every physical radio), **VLAN segregation** (guest SSIDs tagged onto the bridge with an L3 VLAN interface for the portal — or a dedicated guest bridge when no VLAN is given), IP pool, DHCP, hotspot profile, portal server, a bandwidth-limited guest user profile, and an optional masquerade NAT rule. Idempotent, with honest warnings (e.g. bridge VLAN-filtering disabled). Can also target an existing interface.