Added test job that must pass before building Docker image:
- Code formatting check (mix format --check-formatted)
- Compilation with warnings as errors
- Credo static analysis (--strict mode)
- Full test suite with PostgreSQL + TimescaleDB
Build job now depends on test job passing (needs: test).
This prevents broken code from being deployed to production.
Two critical production bugs fixed:
1. **Alert type enum → string conversion**
- Changed Alert.alert_type from Ecto.Enum to :string for flexibility
- Updated all queries to use "device_down"/"device_up" strings instead of atoms
- Fixed pattern matching in alerts.ex and device_monitor_worker.ex
- Updated 44+ test files to use string literals
2. **SQL array indexing syntax error in activity feed**
- Fixed PostgreSQL syntax: `array_agg(...)[1]` → `(array_agg(...))[1]`
- Prevents "syntax error at or near [" in activity feed queries
3. **Added comprehensive timer cleanup tests**
- Tests for mobile_qr_live.ex timer cleanup on terminate
- Tests for agent_live index timer cleanup
- Verifies memory leak fixes from previous commits
Files changed:
- lib/towerops/alerts.ex
- lib/towerops/alerts/alert.ex
- lib/towerops/activity_feed.ex
- lib/towerops/workers/device_monitor_worker.ex
- test/**/*_test.exs (44+ files with alert_type references)
- test/towerops_web/live/mobile_qr_live_test.exs
- test/towerops_web/live/agent_live_test.exs
- test/towerops/workers/device_poller_worker_test.exs
All tests passing except 1 unrelated brute force protection test.
Created FilterNoisyLogs plug that disables Phoenix logging for both
request and response logs by setting conn.private.phoenix_log to false.
This prevents both:
- Initial request log (Plug.Telemetry)
- Response log (Phoenix.Logger "Sent 200 in Xms")
Filters:
- GET /health (Kubernetes probes)
- HEAD / (external uptime monitors)
Previous version used hardcoded 'towerops_dev' which doesn't exist in production.
Now uses current_database() to get the actual database name at runtime.
This fixes the crash: ERROR 3D000 (invalid_catalog_name) database "towerops_dev" does not exist
Fixes ERROR 53400 (configuration_limit_exceeded) during large interface
sync operations in device discovery.
The sync_interfaces operation was decompressing 146,603 tuples but the
default limit was 100,000. Setting to 0 (unlimited) removes this constraint.
Error occurred in: Towerops.Snmp.Discovery.sync_interfaces/2
The punycode module deprecation warning in Node.js is coming from
actions/checkout@v6. Downgrading to v4 which is more stable and
doesn't use the deprecated punycode module.
Fixed two CI/CD issues:
1. Docker daemon not accessible
- Added explicit dockerd startup step
- Configured buildx to use docker-container driver
- Driver creates its own buildkitd instance
2. Node.js punycode deprecation warning
- Downgraded actions/checkout from v6 to v4
- v4 is more stable and doesn't use deprecated punycode module
Changes ensure the workflow runs successfully in Forgejo Actions runners
that may not have Docker socket access by default.
The select_org LiveView event was updating default_organization_id in
the database but not the session's current_organization_id. Since
load_default_organization prioritizes the session value, users would
always get redirected back to the stale org.
Replaced with a controller POST action that updates both the session
and DB default before redirecting.
Removed all unused private functions and imports from BruteForceProtection
plug since the feature is currently disabled (Cloudflare handles security).
Functions can be restored from git history if feature is re-enabled.
Fixed compilation warnings:
- unused function track_404_if_needed/2
- unused function maybe_track_404/2
- unused function get_remote_ip/1
- unused function get_fallback_ip/1
- unused function format_remote_ip/1
- unused function check_and_block/2
- unused function block_request/2
- unused function agent_websocket?/1
- unused alias BruteForce
- unused import Plug.Conn
When a device resumes polling (last_snmp_poll_at updated), automatically
dismiss any active 'device_poll_gap' insights for that device.
This prevents stale insights from lingering after connectivity issues
are resolved (e.g., agent reconnection after being blocked).
Changes:
- Auto-dismiss device_poll_gap insights in update_snmp_poll_time()
- Log when insights are auto-dismissed for visibility
- Fix brute force protection compilation error
Group sync entries by minute to show only one entry per minute instead
of multiple duplicate entries with the same timestamp. This aggregates:
- Total records synced in that minute
- Average sync duration
- Most recent status
This keeps the activity feed informative while reducing noise from
rapid syncs happening within the same minute.
Only show failed Preseem syncs in the activity feed to reduce noise.
Successful syncs happen every 10-20 minutes and clutter the dashboard
without providing useful information to users.
Users can still see all sync history (including successful syncs) in
the Preseem integration settings page if needed.
Disable all IP blocking and 404 tracking to allow unrestricted access
while we review and tune ban thresholds and exemptions.
This is a temporary measure to prevent legitimate traffic from being
blocked. Will re-enable after proper configuration.
Add dark: variants throughout the marketing page and layout so text,
backgrounds, cards, badges, and borders render properly when the
system or user preference is set to dark mode. Use invert + screen
blend mode on the logo PNG to eliminate its white background.
Agent connections authenticate at the channel join level with token
validation, so they should not be subject to IP-based brute force
protection. This prevents legitimate agents from being banned when
connecting to the server.
Changes:
- Exempt /socket/agent/websocket from all IP ban checks
- Update documentation to reflect agent exemption
- Agents still authenticate securely via token during channel join
Increase max_attempts from 3 to 10 and add custom backoff/1 that uses
quadratic growth (attempt² × 60s) capped at 2 hours, so transient
network failures retry over ~9 hours rather than being discarded after
3 quick attempts.
Router devices were inflating per-site subscriber counts because:
1. CGNAT IPs (100.64.0.0/10) in router ARP tables caused false matches
2. Routers see all traffic and shouldn't have subscribers attributed
3. Site totals were computed from raw links without filtering
Changes:
- Filter CGNAT (RFC 6598) IPs from device subscriber links
- Deduplicate accounts across devices by match method priority
- Detect routers via SNMP sys_descr and zero out per-device counts
- Derive site totals from filtered per-device impacts
- Add per-device Subs/MRR columns to site device tables
- Add device link fallback for sites without IP blocks
- Fix Gaiia sync case mismatch and execution order
- Improve device list toolbar button icons and tooltips