The two follow-ups left open by v0.23.17. One is a real fix; the other turned
out to need explaining rather than fixing.
**Redis had no memory ceiling.** v0.23.17 stopped the poller from growing Redis
without bound, but nothing stopped anything else from doing so, and the host had
no protection either way. Redis now runs with a 512 MB cap (REDIS_MAXMEMORY).
The eviction policy is the part worth care. `allkeys-lru` would be actively
dangerous here: BullMQ job data carries no TTL, so an LRU sweep across all keys
could discard queued work with no trace. `volatile-lru` evicts only keys that
set an expiry — which is exactly the poller's caches and in-flight claims, all
of which are safe to lose because losing one merely causes an earlier re-poll.
Verified rather than assumed: sampled BullMQ job keys report ttl=-1 and poller
cache keys 528-3408s. If pressure ever exhausts the volatile keys Redis refuses
writes instead of evicting queue state, which is loud and survivable.
**The 14% fast-poll failure rate is not a bug.** Current rate across all devices
and job kinds is 0% over 1,462 attempts. The historical 230,707 failures were
genuine device downtime: device_availability records 324 outages totalling ~1,003
hours across four lab devices, which at a 30-second cadence and two attempts per
cycle predicts ~240,700 failures — within 4% of what was recorded. Nothing to
fix; retention had simply been hoarding an accurate count.
It did expose real waste, though. Periodic polls no longer retry in-job, because
the schedule already is the retry: a device that did not answer is unlikely to
answer a second later, and each extra attempt occupies a worker for up to the
job timeout. On a fleet with several devices down that doubled the capacity they
consumed — the same capacity the rest of the fleet was short of in #114. It also
doubled the recorded failure count, which is how ~1,000 hours of downtime came
to look like 231k distinct faults. User-initiated full syncs keep their retries.
Co-Authored-By: Claude Opus 5 <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>
Squash-merge of PR #28 from @denis-ev. All three review blockers addressed: encryption key management documented, string truncation replaced with skip+warn validation, and undefined return on duplicate merge fixed with proper 500. Local test stack verified against cloned prod DB — migrations, new features, and backend all clean.