Global search:
- Clicking/Enter on a client result now opens that client's detail page
(/clients/<mac>) instead of dumping to the clients list.
Operations dashboard (new backend route /api/operations):
- GET /insights: server-side attention engine (offline, updates, CPU/memory
pressure, missing backups, recent outages, weak WiFi clients, co-channel
overlap, high TX retries, error events) + per-device capacity + merged
activity feed (config changes, audit actions, alerts; login noise filtered).
- POST /backup-all and /sync-all: fleet-wide actions over online devices.
- Quick Actions now execute: Run discovery, Backup all, Sync config, and
Open terminal (device picker -> live SSH TerminalModal); inline status.
- New cards: Capacity & health meters, Security posture rollup (reuses
per-device posture, cached), Recent activity feed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wireless RF Health (fleet Wireless page + per-AP Radios tab):
- Channel usage map across 2.4/5/6 GHz with co-channel overlap; operating
channel resolved live from /interface/wifi/monitor on the wifi package
(auto-channel radios report no frequency in /print).
- AP deployment density: client RSSI distribution on the -90..-30 dBm scale
with a coverage-gap warning.
- AP radio TX retries histogram (0%->35%+) with band selector, derived from
per-client CCQ (new wireless_radio_quality InfluxDB series).
- WiFi connectivity success funnel (Association/Auth/DHCP) derived from logs.
Per-port connected clients (Ports tab):
- New card lists clients physically connected to a selected port (host/MAC/
IP/VLAN/connection time, linked to client pages).
- Distinguishes physical clients from the raw MAC table: uplink/trunk ports
are auto-detected (topology neighbor, multi-VLAN MAC spread, or high MAC
count) and show an uplink explainer + "view MAC table" disclosure instead
of every reachable MAC.
- Added clients.first_seen ("connected since"); set once, preserved across polls.
Clients:
- Replace Previous/Next buttons with a "Show N entries" dropdown
(25/50/100/200, persisted to localStorage) and numbered page selection
with prev/next chevrons and ellipses for long ranges.
Syslog (Network Services):
- Drop the Single Device / All Devices mode toggle; always show the
fleet-aggregated view with coverage badges.
- Add a per-section device dropdown + "Add to device" button beside
"Add to All Devices" for targeting a single device (direct save).
- Edit / enable-disable / remove remain coverage-scoped, so single-device
management is preserved without the separate mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Traffic by Application: donut + legend side-by-side, larger donut with
center total label, percentage in tooltip; card now sizes to its content
- New Inbound Packets area chart below the throughput chart (packets field
now summed per direction in /traffic/timeseries)
- New Flow Sources & Data Health card: collector listening state, packets
received, flows attributed, attribution rate, per-exporter flow/packet
counts and last-seen; low-attribution hint for NAT-in-path exporters
- /traffic/apps now returns per-app packets alongside bytes
- Restructured bottom row: Top Talkers (left) | Application + Data Health
stacked (right) for alignment
- Tightened Top Talkers row spacing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI/UX refinements on top of v0.14.0-beta, all validated on the test server:
- Logo: new theme-aware compass iconmark (light/dark SVGs swapped via the
dark class), sized to 45px; favicon updated to match. Wordmark casing
corrected to "MikroTik Manager" (font size unchanged).
- Navigation icons enlarged 15 -> 17px.
- Dashboard: "Connected clients" line chart converted to a bar chart with a
category axis + light downsampling so bars stay readably thick across ranges.
- Clients: sorting is now server-side across the whole dataset (was sorting
only the current page, so Type/others appeared to do nothing on a uniform
page); table uses a fixed column layout + keepPreviousData so columns no
longer jump or blank out when sorting/paging. Backend clients route gains
whitelisted sort/dir params (safe numeric IP sort, deterministic tiebreaker).
- Connections tab: surfaces the live /ip/firewall/connection/tracking state
(on/auto/off pill) and clearer empty-state copy explaining auto-mode.
Lint/typecheck/build/jest green in both workspaces; npm audit --audit-level=high
clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dedicated top-level Security section plus a Meraki/UniFi-grade firewall built
on the full RouterOS feature set.
Security Center (new /security page)
- Fleet hardening scores, per-device posture list, and a "Common Findings"
rollup aggregating identical issues across the fleet
- "Manage" deep-links into the device's Security tab (?tab= support added)
Firewall & NAT (per device + in Security Center)
- Friendly rule builder: Allow/Drop/Reject chips, Any/Address/Address-List
pickers, port presets, connection-state chips, logging, live plain-English
preview
- Address lists as reusable objects, referenced from rules
- Rule reordering (/ip/firewall/{filter,nat}/move); per-rule hit counters
(print stats) with dead-rule highlighting + reset
- NAT wizards: Port Forward / Masquerade / 1:1 NAT + Custom
- Safe-apply lockout guard: backend 409 on an unscoped input drop/reject that
would sever management; UI confirms before forcing (detectLockoutRisk, tested)
Access & security
- Security posture audit: graduated, de-duplicated scoring that never proposes
disabling the management service the platform connects through (no self-
lockout); switch-aware input-firewall severity; service hardening table
- Bandwidth: simple-queue CRUD + one-click "Limit this client" on client detail
- Active connections viewer
These L3 features are available on all managed RouterOS devices (routers,
switches, APs), not gated by device type.
Backend: DeviceCollector address-list/move/stats/connections/queues/service
methods; device routes for all of the above; firewallSafety + firewallSummary
utils with unit tests (19 new).
Security: bump nodemailer 8 -> 9.0.1 to clear GHSA-p6gq-j5cr-w38f (our
createTransport/sendMail usage is unaffected by the advisory's raw-option path).
Verified live against RouterOS devices; lint/typecheck/build/jest green in both
workspaces; npm audit --audit-level=high clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI Security Audit (npm audit --audit-level=high) was failing. Resolved all
high-severity advisories via non-breaking transitive bumps (npm audit fix,
no --force, no direct-dependency or source changes):
- frontend: form-data, ws (via socket.io-client), @babel/core,
react-router-dom, js-yaml — now 0 vulnerabilities
- backend: form-data, ws (via socket.io) — 0 high-severity remaining
Only package-lock.json files changed plus the version bump. Verified both
workspaces: npm ci, lint, type-check, build, and backend jest (73 tests)
all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two per-client usage charts exposed different time windows: "Wireless
Usage" offered 2h/24h/7d while "App Traffic (NetFlow)" offered 1h/24h/7d/30d.
Worse, selecting 1h (or any range with no flows in window) made the whole
App Traffic card unmount — including its range buttons, so it couldn't be
recovered without reloading.
- AppTrafficCard now uses 2h/24h/7d (default 24h), matching Wireless Usage
- Card visibility latches on first non-empty result, so switching to an
empty range shows an inline "No traffic data yet for this range" state
instead of unmounting the card (latch derived via guarded setState in
render — no effect)
- Backend /api/traffic/* now accepts the '2h' range (rangeToFlux +
windowForRange: 2h -> 2m buckets); previously '2h' silently fell back to 24h
Verified in the live stack with Playwright: ranges render as 2h/24h/7d (no
1h/30d), and clicking 2h on a client with data only outside the 2h window
keeps the card mounted with an empty-state and recovers on switching back.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Dashboard "Top talkers" card and the Traffic page previously used
different data sources: the card ranked clients by device-reported
since-connection byte counters (wireless-centric, reset on reconnect, no
time window), while the Traffic page uses NetFlow-measured bytes.
The card now prefers the NetFlow top-clients data (last 24h — identical
numbers to the Traffic page) and falls back to the legacy counters only
when the collector has no data, with the source labeled in the card
header ("by bytes · 24h" vs "since connection"). Rows now click through
to the client detail page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Configuration History:
- Per-device snapshots from the full RouterOS /export (.rsc) — config-only, so
counters/operational state no longer cause phantom changes; deduped by content hash
- New Config History tab: snapshot timeline, readable line diff, on-demand Capture
(with honest 'no changes' feedback), one-click rollback via the linked .rsc backup
- Snapshot and its backup are one artifact: badged 'Config Snapshot' on the Backups
page, FK ON DELETE CASCADE keeps them in lockstep; delete is gated by a warning modal
- New config_drift alert event (off by default); periodic snapshot poll with
configurable cadence/retention (config_snapshot_interval_min/_retention)
- New endpoints under /api/config-history (list/get/diff/capture/rollback/delete)
Scheduled Backups:
- Replaced the raw cron textbox in Settings with Frequency (Daily/Weekly/Monthly)
+ time + day pickers; cron is generated/parsed behind the scenes
- Fixed cronMatchesNow to evaluate day-of-month/month/day-of-week (it previously
only checked minute/hour, so weekly/monthly schedules ran every day)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CI Security Audit (npm audit --audit-level=high) started failing on all
new PRs after a critical advisory was published against vitest <4.1.0
(GHSA-5xrq-8626-4rwp — Vitest UI server arbitrary file read/exec). The
remediation chain also requires vite to move off the vulnerable esbuild range.
These are devDependencies (test/build tooling) and do not ship in the
production image, so there was no runtime exposure to deployed users — but the
audit gate blocks the pipeline, so bump them:
- vite ^5.0.11 -> ^8.0.16
- vitest ^3.0.5 -> ^4.1.8
- @vitejs/plugin-react ^4.2.1 -> ^5.2.0 (vite 8 peer compatibility)
Vite 8 ships the rolldown bundler, which requires rollupOptions.output
.manualChunks to be a function rather than an object — converted accordingly,
with explicit precedence so @xyflow/react (whose name contains the substring
"react") lands in the `flow` chunk instead of being pulled into `vendor`.
Verified locally: npm ci, npm run lint, tsc --noEmit, npm run build, and
npm test (vitest 4.1.8, 3/3 passing) all pass; npm audit reports 0
vulnerabilities.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The Topology page crashed on load with React error #185 ("Maximum update
depth exceeded"), thrown from inside React Flow's setNodes.
Root cause: handleDeleteManual depended on the whole TanStack Query
useMutation result object, which is recreated on every render. That made
handleDeleteManual unstable, which made the `graph` useMemo recompute every
render, which made the node/edge sync effect fire every render — calling
setNodes/setEdges in a loop until React aborted.
Fix: depend on the referentially-stable `.mutate` function instead of the
whole mutation object, so handleDeleteManual stays stable and `graph` only
rebuilds when the underlying data or connectMode actually change.
Verified: tsc --noEmit clean, eslint clean, vite build succeeds, and the
Topology page renders without #185 in a local Docker build.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: backend filtered switches by status='online', so a mid-poll-cycle
device (briefly 'unknown') caused the endpoint to return [] — and the frontend
rendered nothing when the array was empty.
- Backend: query ALL switch devices (not just online ones); unreachable devices
already fall through to the Promise.allSettled error path and render with an
error badge, so no data is lost
- Frontend: replace silent `length > 0 &&` guard with a three-way ternary that
shows a "No managed switches found" message when the array is empty, so the
user always sees feedback instead of a blank section
- Frontend: add staleTime: 60_000 to the SNMP query to prevent the hair-trigger
background refetch that raced with the poller on every navigation
Backend: GET /api/system/version-check fetches frontend/package.json from
the main branch on GitHub, caches result in app_settings for 24 hours,
and compares semver against the running version. Fails silently (no banner)
for air-gapped or firewalled deployments.
Frontend: dismissible blue update banner in SecurityBanners (rendered
between TopBar and main content) — only visible when a newer version is
detected. Shows target version and docker compose update command.
Backend: accept target_device_id — connect to target, enable btest
server, run test from source using target credentials, disable server
in finally (even on failure). Manual IP mode preserved as fallback.
Frontend: device picker dropdown (all managed devices except self) as
default mode; toggle to Manual IP for non-managed targets with a warning
that the server must be pre-enabled. Results show target IP in labels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After sniffer stops, poll /file/print every 500ms (up to 10s) until
RouterOS reports the capture file, then use the exact 'name' field as
the SFTP path. Falls back to stripping the leading slash if the first
attempt gets "No such file". Eliminates the hardcoded path assumption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Frontend: blob error responses (from responseType:'blob' calls) are now
decoded back to text/JSON so the actual backend error message surfaces
in the UI instead of generic "Request failed with status code 500"
- Backend: step-by-step console.log through capture flow so the exact
failure point is visible in server logs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RouterOS SFTP root maps to the device filesystem root, not /flash/.
Capture file is written as /<name>.pcap, not /flash/<name>.pcap.
Also stop any in-progress capture before reconfiguring, add a 2s
flush delay between sniffer stop and SFTP download, improve error
messages to expose exact failure point, and delete remote file after download.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Packet capture: RouterOS sniffer + SFTP download to PCAP for Wireshark analysis
- Bandwidth test: RouterOS /tool/bandwidth-test with TX/RX Mbps result display
- Both exposed in the Tools tab on device detail pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- aggregateRules/aggregateActions: deduplicate coverage per device using a per-device seenKeys set,
preventing a device with duplicate topic|action rules from inflating the coverage count above total
- executePush (add path): skip devices that already have an identical rule/action before pushing to
all devices, preventing duplicate RouterOS logging entries from being created in the first place
- TopBar: remove hardcoded decorative 'live' indicator that had no backend connection logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 'Copy VLANs from Another Switch' — a 3-step modal available on the
VLANs tab of any switch device. Step 1 selects a source switch and which
VLAN IDs to copy (VLAN 1 deselected by default with a management risk
warning). Step 2 lets the user assign interfaces on the target device
per VLAN: clicking a port chip cycles it through None → Tagged → Untagged
→ None, with a live summary and bridge picker per VLAN. Conflict detection
identifies VLANs already present on the target and offers per-VLAN
Skip/Overwrite with the port grid disabling itself when Skip is active.
Step 3 shows the full plan before a single apply call executes all
operations over one device connection and reports per-VLAN results.
Also fix CPU load display on the Devices page for hardware switches that
report 0% CPU via ASIC offloading — the previous filter (v > 0) silently
dropped all valid zero readings; now filters on field presence instead.
- DevicesPage: add statusFilter + typeFilter to filtered useMemo deps (was a real bug — filter chips had no effect until unrelated re-render)
- DevicesPage: refactor cpuHistories useMemo to pure Object.fromEntries form
- DashboardPage: suppress react-hooks/purity for intentional Date.now() chart domain
- Sidebar: move GroupHeader from inline arrow fn to module-scope named function (fixes react-hooks/static-components)
- axios: bump ^1.6.5 → ^1.16.0 to clear high-severity CVEs (prototype pollution, CRLF injection, NO_PROXY bypass)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>