Replace the HoneybadgerNoticeFilter that emailed raw stacktraces on
every exception with ErrorTracker for self-hosted error tracking.
Honeybadger itself is retained. ErrorTracker dashboard is mounted at
/admin/errors behind the superuser auth wall.
CheckExecutorWorker was missing the phoenix_snmp_disabled and
should_phoenix_poll_device guards that DevicePollerWorker and
DeviceMonitorWorker already have. This caused SNMP checks to execute
through Phoenix even when devices are assigned to agents, creating
an infinite loop of failing jobs that fill the Oban queue.
When the Oban queue backs up, the 60-second uniqueness window expires
and duplicate jobs stack up per device. Switch to period: :infinity so
only one poll/monitor job exists per device at any time. Add replace
option to supersede stale scheduled jobs with updated scheduled_at.
Remove :executing from unique states so self-scheduling works while
the current job runs. Set max_attempts: 1 since retrying stale polls
is pointless.
Also fix all credo --strict issues across the codebase.
- invitation_test: default role is now :technician not :member
- settings_live_members_test: use "technician" role in invite form (member removed from select)
- form_test: MikroTik API section is intentionally disabled, expect refute
- Fetch MAC address from Gaiia custom fields during inventory sync
- Remove name-based device matching (model names caused false matches)
- Keep IP-only matching for device suggestions
- Make unmapped rows clickable in the Gaiia mapping table
- Point authenticated logo link to /dashboard instead of /orgs
- Fix flaky monitor test teardown race condition
After Gaiia sync, match inventory item IPs against network site CIDR
blocks to populate account_count and total_mrr on each site. This
enables the Site Health table to show subscriber and MRR data per site.
Each billing sync (Gaiia, Splynx, VISP, Sonar) already computes
subscriber counts and MRR but only stored them in sync messages.
Now persists them on the integrations table so the dashboard can
aggregate across all billing providers instead of only querying
Gaiia network sites (which were never populated).
Also fixes SnmpKit Config environment detection to use Mix.env()
as fallback when MIX_ENV env var is not set.
- Entity mapping table: hide Gaiia ID and Linked columns on mobile,
show Gaiia ID inline under name on small screens
- Allow text wrapping in name column for long names
- Add overflow-x-auto to table container for safety
- Redirect authenticated users from / to /dashboard instead of /orgs
Instead of showing all 4 billing providers as separate full-width cards,
the billing section now shows:
- When no billing connected: a compact 4-column grid picker to select
your billing platform (Gaiia, Sonar, Splynx, VISP)
- When one is active: a single card showing the connected provider with
status, sync info, and a note to disable before switching
This reduces visual clutter since users only ever use one billing system.
Non-exclusive categories (QoE, Incidents, Infrastructure) remain as
individual cards since multiple can be active simultaneously.
- Billing subscription and inventory item status fields are scalars, not objects
- IP blocks use "block" key, not "subnet"
- Strip nil variables from GraphQL pagination (Gaiia returns null for after:null)
- Handle nil root keys in paginated responses gracefully
- Handle empty string response from Preseem scores endpoint
- Fix CLOAK_KEY placeholder in nix shell (invalid base64)
- Fix error HTML test assertion to match actual template
- Fix Gaiia sync: read 'subnet' field instead of 'block' for IP blocks
- Fix Gaiia sync: extract nested status name from subscription objects
- Fix security headers tests for environment detection
- Fix mobile auth tests to use raw_token
- Fix LiveView test assertions to match actual template content
- Fix SNMP config test expectations for test environment
- Refactor: resolve all Credo complexity/nesting issues
- Extract helper functions in NetBox sync, VISP sync, Sonar sync
- Flatten nested conditionals in settings, integrations, alerts
- Use with/validate_present pattern for connection testing
- Header: X-Gaiia-Webhook-Signature with t=timestamp,v1=signature format
- Signed payload: timestamp.body (not just body)
- 5-minute timestamp tolerance to prevent replay attacks
- Updated tests to match new 4-arity verify_signature/4
- Use pattern match assertion in towerops_native_test to satisfy type checker
- Clear ETS whitelist cache in brute_force_test setup to prevent cross-test leakage
- Escape HEEx template braces in GraphQL/API docs with raw(~S[...])
- Fix test assertions for updated marketing copy and UI text
- Extract helper functions in GraphQL resolvers to reduce nesting depth
- Create shared ErrorHelpers module for API controllers
- Fix ETS race condition in brute force whitelist cache for async tests
- Fix property test generators to use ASCII instead of printable unicode
- Add alert_severity helper to site_live/show
- Update accounts fixtures for explicit user confirmation
- Remove auto-confirmation on registration (register_user and register_user_with_organization)
- Add deliver_user_confirmation_instructions/2 and confirm_user/1 to Accounts context
- Add verify_email_token_query/2 and by_user_and_contexts_query/2 to UserToken
- Make deliver_confirmation_email public in UserNotifier
- Send confirmation email after registration in both flows
- Block unconfirmed users at password login with helpful message
- Add UserConfirmationController with confirm/resend routes
- Add resend confirmation link to login page
- Update test fixtures to confirm users after registration
- Organization changeset: name length, SNMP port range, version enum, SNMPv3 fields
- Integration changeset: sync interval validation, provider enum
- Settings LiveView: tab routing with garbage params, arbitrary name/port/community input
- All capped at max_runs: 10-50 for fast execution
Switch deployment pipeline from GitLab Agent kubectl to FluxCD image
automation. CI now only builds and pushes tagged images to Forgejo
registry; FluxCD detects new tags and updates the deployment manifest.
Fix Ecto.MultipleResultsError on /dashboard when a device has multiple
Preseem access point records by using limit(1) instead of Repo.get_by.
Gaiia migrated all status fields from scalar enums to object types,
not just AccountStatus. Update BillingSubscription.status,
InventoryItem.status, and nested billingSubscriptions.status in the
accounts query to request { id name } subfields and extract the name
value in sync mappers.
Auto-generate HMAC-SHA256 webhook secret when enabling the Gaiia
integration. Display webhook URL, secret with copy buttons, regenerate
option, and setup instructions on the integrations page.
Gaiia API changed AccountStatus and AccountType from scalar enums to
objects with id/name/type subfields. Updated the accounts query to
request nested fields and the sync mapper to extract the name values.
wire time_format into scope, add settings UI selector, change defaults
to 24h, and replace all user-facing Calendar.strftime calls with
centralized TimeHelpers using the user's timezone and time format.
Transform the dashboard into a single-pane-of-glass command center that
blends operational metrics with business context from Gaiia subscriber data.
- Extend insight schema with multi-source support (snmp, gaiia, system)
- Add Oban workers for automated insight generation (device health, system, gaiia)
- New Dashboard context with health score computation and site summaries
- Rewrite dashboard LiveView with health overview, alert/insight feeds, site grid
- Add source filter pills for insights with URL state management
- Add metrics bar to site detail pages (device count, alerts, subscribers, MRR)
- Add subscriber/MRR context to alert list site links
- Add subscriber badges to device list site headers
- Real-time PubSub updates for alerts on dashboard
the viewer query doesn't exist in gaiia's schema, causing a 400
validation error. use a minimal accounts query instead. also handle
400 responses with graphql errors properly.
Three write-back capabilities: ticket creation from alerts, subscriber
notes during outages, and inventory item updates. Adds GraphQL mutation
methods to the client and an Actions module that handles credential
retrieval and API call orchestration.
Computes subscriber impact when device alerts fire. Three analysis
layers: device-level (inventory item -> direct subscriber) and
site-level (network site -> all subscribers at tower). Impact data
stored as JSONB on alert records and displayed as badges on alert list.
Replace the thundering-herd cron job with a dispatcher that enqueues
individual per-integration Oban jobs staggered across the 10-minute
window using PollingOffset. Show last synced timestamp in user's
local timezone via the <.timestamp> component.
the default base url was pointing at the docs site
(apidocs.preseem.com) instead of the actual api endpoint
(api.preseem.com), causing all api calls to fail with
unauthorized. removed the unnecessary base_url user override
since the api endpoint is fixed.
Completes full migration from device-level polling to per-check polling.
Migration Changes:
- Removed DevicePollerWorker scheduling from device lifecycle
- Checks automatically scheduled when created
- Discovery creates and schedules checks atomically
New Functions:
- Monitoring.stop_device_checks/1: Cancel all check jobs for device
- Monitoring.disable_device_checks/1: Disable checks when SNMP off
Discovery Integration:
- All create_*_check functions now schedule checks after creation
JobHealthCheckWorker Updates:
- Removed DevicePollerWorker recovery logic
- Only recovers DeviceMonitorWorker jobs now
Test Updates:
- Marked DevicePollerWorkerTest as skipped (deprecated)
- Updated JobHealthCheckWorkerTest expectations
All 6953 tests passing, 0 failures
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>