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>
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.
- 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.
New Syslog page under Network Services exposes RouterOS logging actions
and routing rules with full CRUD. Logging actions define destinations
(remote syslog server, memory, disk, echo); logging rules define which
topics route to which action.
Single Device and All Devices modes are functionally identical — All
Devices applies changes across every online device in parallel and
shows per-entry coverage badges indicating which devices have each
action or rule. Toggle enable/disable works in both modes.
Remote actions display address:port with facility/severity in a single
Destination column; memory/disk/echo rows show no irrelevant remote
fields. Syslog KPI card added to the Network Services overview.
Add a right-justified refresh interval dropdown above the client table
with options for Not Updating, 30 s, 1 min, and 3 min. The chosen
interval controls both React Query polling and WebSocket-triggered
invalidations — selecting Not Updating fully freezes the list until
page reload. Preference persists via localStorage.
RouterOS vs RouterBOOT distinction:
- Rename "Firmware Updates" → "RouterOS Updates" everywhere (Config tab,
dashboard banner, devices list tooltip) to accurately reflect what is
being updated
- Add RouterBOOT bootloader upgrade support as a distinct operation:
new DeviceCollector methods checkRouterboardUpgrade() and
installRouterboardUpgrade() using /system/routerboard/print and
/system/routerboard/upgrade
- New API endpoints: POST /api/devices/:id/check-routerboard and
POST /api/devices/:id/install-routerboard
- Daily poller now also checks RouterBOOT upgrade status while already
connected during firmware check cycle, with first-discovery alerts
Config tab update panel:
- Combine RouterOS and RouterBOOT into a single "Software & Firmware
Updates" card with two subsections separated by a divider
- Pre-populate both sections from stored DB state on load — no longer
requires a manual "Check" click to see a known pending update
- Enforce update order: when both are pending, show a blue info banner
explaining RouterOS must be installed first, and disable the
RouterBOOT "Upgrade & Reboot" button until the RouterOS update clears
- Fix GET /api/devices/:id to include firmware_update_available,
latest_ros_version, routerboard_upgrade_available, upgrade_firmware_version
(these were missing from the detail endpoint)
Dashboard & devices list:
- Combined amber banner now shows two distinct rows: RouterOS (N) with
device links and RouterBOOT (N) with device links — one dismiss for both
- Devices list: amber ArrowUpCircle = RouterOS update, blue Cpu icon =
RouterBOOT upgrade, each with distinct tooltips
- Fix GET /api/devices (list) to include routerboard_upgrade_available and
upgrade_firmware_version (these were missing, causing RouterBOOT
notifications to never appear on the dashboard)
DB migration:
- Add routerboard_upgrade_available BOOLEAN DEFAULT FALSE
- Add upgrade_firmware_version VARCHAR(20)
Discovery system fixes:
- Fix duplicate entries: replace single-key (mac || ip || identity) dedup
with three cross-referenced indexes so a neighbor seen via LLDP by one
device and via MNDP by another always merges into one row
- Fix offline devices persisting: handleDeviceFailure now deletes
topology_links for the offline device (same pattern as clients)
- Add periodic stale-link sweep every 15 min that removes topology_links
rows older than 20 min as a safety net for devices that go offline
without cleanly triggering handleDeviceFailure
Documents CORS_ORIGIN env var, credential preset operator access control,
and server-side bulk device add job in README. Bumps version across all
four locations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* devices: hide discovered neighbors that are already managed
Discovered Devices showed every unresolved MikroTik neighbor, even when
the neighbor was obviously the same device a managed node is reporting
through a different link / MAC / alias. Since we already know every
managed device interface MAC, primary IP, and system identity, we can
flag those rows.
- /api/devices/discovered now annotates each row with duplicate_of_device_id
and duplicate_of_device_name when the neighbor matches a managed device
by interface MAC, primary IP, or system identity.
- Add a Hide duplicates of managed devices toggle (default on) above the
table, with a count of how many matches were found. When enabled, the
list hides matched rows; when disabled, matches stay visible but are
dimmed and tagged Already managed so the origin is clear.
- Suppress the Add to Manager action for rows that are already a
managed device, since adding them would create a second record for the
same hardware.
Co-authored-by: Denis <denis-ev@users.noreply.github.com>
* devices: safer duplicate handling, try-all safeguards, and timezone/firmware fixes
Co-authored-by: Denis <denis-ev@users.noreply.github.com>
* Non-blocking follow-ups: parsePort util, tests, bulk-add queue, preset rate limit
Co-authored-by: Denis <denis-ev@users.noreply.github.com>
* fix(security): tighten CORS, Redis rate limits, and preset access controls
Require explicit CORS origins in production, align Socket.IO CORS with HTTP,
default Docker Compose to local HTTPS origins, and remove the in-memory
mutation limiter in favor of Redis. Also add operator-use flags for credential
presets in the UI and harden bulk-add job access and client error messages.
Co-authored-by: Denis <denis-ev@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Sets up Jest + ts-jest for the backend and adds 47 tests across 5
suites covering crypto utilities, OUI vendor lookup, auth middleware,
AlertService dispatch/cooldown logic, and DeviceCollector uptime parsing.
- SECURITY.md with vulnerability disclosure policy and self-hosting guidance
- Dependabot config for weekly dependency updates (npm, Docker, Actions)
- ESLint v9 flat config for frontend and backend, with eslint-plugin-security
- GitHub Actions CI: lint, typecheck, build, and npm audit on every push/PR
- CodeQL workflow for automated security scanning (runs on push and weekly)