Stream recovery, accurate connectivity history, self-explaining offline routers
Two separate "it says offline but it isn't" reports turned out to be different bugs, and both are fixed.
Most of the stream recovery and collector freshness work in this release was contributed by @invoker-karl in #91. Thank you.
Fixed
- Phantom router outages after a switch or idle teardown (
src/index.js). Tearing a session down cancelled the pending offline debounce timer and then closed the API connection, but the resultingcloseevent arrived after that cancel and armed a fresh 30 second timer which nothing cancelled. It then recorded an Offline event and fired a router down alert for a router that was never unreachable. Every router switch, idle teardown, disable and delete took this path, so the Connectivity report accumulated outages that showed as "Ongoing", because the torn down session never reconnects and no closing row was ever written. With alerts enabled it also produced bogus notifications. Closes #84 - Silently stalled traffic streams (
src/collectors/traffic.js). A persistent RouterOS stream can stay attached while delivering nothing, leaving the graph frozen after a few seconds while the app still reports as connected. A watchdog now restarts the stream after ten seconds without data. Addresses #90 and #55 (#91, @invoker-karl) - Collector freshness was never checked (
src/health.js,src/index.js)./healthzonly tested the parent RouterOS connection, so a dead collector still looked healthy. It now validates that critical collectors are delivering fresh data, while excluding collectors intentionally suspended because nobody is viewing them (#91, @invoker-karl) - Interface rate poll failures were suppressed (
src/collectors/interfaceStatus.js). Errors were swallowed while_buildAndEmit()kept advancinglastIfStatusTs, so a broken collector reported itself healthy. Failures are now recorded and no longer advance the freshness timestamp (#91, @invoker-karl) - Traffic health froze with no browser connected (
src/collectors/traffic.js). The collector's timestamps were updated after the idle gate, so a perfectly healthy stream looked stale to/healthzon an idle dashboard (#91, @invoker-karl) - Idle interfaces reporting numeric zero counters were discarded as empty packets, and are now treated as valid samples (#91, @invoker-karl)
Added
- Offline routers explain themselves (
src/routeros/classifyError.js,public/app.js). The Routers page now shows why a router is unreachable, for example "Connection refused", "Authentication failed" or "Network unreachable", instead of a bare "Offline". The classifier was extracted fromwireRosEvents()so the status only sessions produce identical wording, which is what makes the reason available for routers you are not currently viewing. Numeric error codes are resolved too: node-routeros wraps socket failures in aRosExceptioncarrying only an errno, so a refused connection previously surfaced as an opaqueRouterOS API error [-111]. Closes #92 - Heartbeat emissions for unchanged data (
src/collectors/interfaceStatus.js,src/collectors/bandwidth.js), so a stable reading is not mistaken by the browser for a dead collector (#91, @invoker-karl) - Tests run on every pull request (
.github/workflows/test.yml). Builds the production image, runs the full suite inside it and smoke tests startup. Previously only CodeQL ran on pull requests
Security
- Router labels are sanitised at their source (
src/routeros/client.js). The label is the origin of the[label][collector]log prefix and is attacker influenceable, because the system collector adopts the device's own board name while the label is still the default. Control characters, which could forge whole log lines, and%, which could act as a format specifier, are now stripped at the single point the value enters - Dependency updates: body-parser 1.20.6, brace-expansion 1.1.16, js-yaml 4.3.0 (#89)
Changed
stream(command, params, callback)is now explicitly supported by the RouterOS wrapper rather than relying on a quirk of the vendored driver (#91, @invoker-karl)AI_CONTEXT.mddocuments the standing CodeQL dismissals and the logger invariant behind themREADME.mddescribes the stricter/healthzbehaviour
Contributors
- @invoker-karl — traffic stream watchdog, collector freshness checks in
/healthz, interface poll error reporting, heartbeat emissions, zero-counter handling and explicit three-argumentstream()support (#91)