- Splynx client: replace broken Basic Auth with proper token flow
(POST /api/2.0/admin/auth/tokens → Splynx-EA bearer header)
- Support both api_key and admin login auth types with fallback
- Add MRR calculation to Splynx sync using mrr_total field
- Integrations UI: per-provider credential fields (instance_url,
api_key, api_secret for Splynx; instance_url, api_token for Sonar)
- Wire up test_connection for Splynx, Sonar, and VISP providers
- Verified against live Splynx demo (demo.splynx.com):
auth, customers, routers, services all working
- All 7350 tests passing
- 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
CRITICAL fixes:
- Mask SNMP community string in agent channel logs (CRITICAL-1)
- Remove snmpv3 passwords from REST API responses, return _set booleans (CRITICAL-2)
- Replace snmp_community with snmp_community_set in GraphQL type (CRITICAL-3)
HIGH fixes:
- Fix cookie same_site from invalid 'Towerops' to 'Lax' (HIGH-4)
- Remove unsafe-eval from CSP script-src (HIGH-6)
- Block GraphQL introspection queries in production (HIGH-7)
- Sanitize LIKE wildcards in SNMP device name search (HIGH-8)
- Reject webhooks when no secret configured instead of accepting (HIGH-9)
MEDIUM fixes:
- Hash mobile session tokens (SHA-256) before DB storage (MEDIUM-10)
- Apply security headers in all environments, not just prod (MEDIUM-14)
- Add GraphQL query complexity limit (500) in production (MEDIUM-16)
- Fix X-Frame-Options to DENY to match frame-ancestors 'none' (MEDIUM-13)
- Add transport field to SnmpDevice and SnmpConfig
- Add arch field to AgentHeartbeat (already in .pb.ex)
- Add Check, CheckResult, CheckList messages for service checks (HTTP/TCP/DNS)
- Add checks field to AgentConfig
- Add CheckResult to Metric oneof
- Sync proto source with compiled .pb.ex
- NetBox.Sync: pull devices/sites from NetBox, match by name/IP, upsert
- NetBoxSyncWorker: Oban cron every 30min for enabled NetBox integrations
- Gaiia sync: show actual error type instead of generic 'unexpected error'
device_up alerts are informational recovery events, not ongoing issues.
Previously they were created without resolved_at, inflating the
unresolved alert count. Now they're born resolved.
Includes a data migration to fix existing stale device_up alerts.
- Added last_sync_message field to integrations schema
- Gaiia sync: shows item counts on success, friendly error on failure
- Preseem sync: shows AP/device counts on success, friendly error on failure
- Error messages translated from technical errors to user-friendly text
- Message displayed on integration card next to status badge
- Exposed in REST API response
- 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
- 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
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.
- Activity feed: use schema modules instead of string table names in Ecto
queries to fix UUID encoding (Postgrex expected binary, got string)
- Changelog parser: remove plain hyphen from separator character class
to prevent splitting on date hyphens (2026-02-13)
- Fix broken route paths in dashboard (/discovery, /subscribers/trace, /config-changes)
- Fix insights empty state settings link (org-scoped route)
- Add underscores to all 86400 literals across 6 files
- Alphabetize aliases in search.ex and agent_channel.ex
- Extract changelog parser helper to reduce nesting
- Extract dashboard impact calculation to reduce nesting
- Refactor agent_channel config change detection (pattern match, extract function)
- Combine double Enum.reject into single call in trace.ex
- Fix line length in trace.ex search query
- Replace length/1 > 0 with != [] in trace_live
- Run mix format on all files
- Health status dot (green/yellow/red/gray) from worst-case check state
- Relative 'last seen' with color-coded staleness thresholds
- Response time badge from latest monitoring check
- Subscriber count badge from Gaiia inventory items
- Batch queries to avoid N+1: Monitoring.get_device_health_summary/1,
Monitoring.get_device_latest_response_times/1,
Gaiia.get_device_subscriber_counts/1
- Add get_impact_summary/1: aggregates current outage subscriber/MRR impact
- Add list_active_incidents/1: down devices enriched with Gaiia/Preseem data, sorted by MRR
- Add get_site_impact_summaries/1: per-site health with QoE, affected subs, MRR at risk
- Dashboard top stats: MRR at Risk and Subscribers Affected cards
- Active Incidents section: color-coded by severity, shows duration/subs/MRR/QoE
- Site Health Grid: green/yellow/red health dots based on uptime and QoE scores
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.
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.
Stage 4 of Gaiia integration: when a device is mapped to a Gaiia
inventory item, show a Gaiia tab with inventory details, linked
subscriber info, billing subscriptions, network site, and IP
mismatch indicators.
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.