Commit graph

390 commits

Author SHA1 Message Date
cf412e2261
test: add reliability test for Task.yield_many race condition fix 2026-03-05 08:58:23 -06:00
61586d14e2
chore: update all hex dependencies
Upgrades: phoenix 1.8.4, phoenix_live_view 1.1.26, ecto_sql 3.13.5,
bandit 1.10.3, credo 1.7.17, swoosh 1.22.1, yaml_elixir 2.12.1,
styler 1.11.0, error_tracker 0.8.0, finch 0.21.0, honeybadger 0.25.0.

Fix site_aggregation_test assertion to match actual DB default (NULL,
not 0) for account_count on skipped sites.
2026-03-04 13:30:32 -06:00
f0fea0dd3e
fix: retry firmware fetch with exponential backoff on network errors
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.
2026-02-18 10:34:51 -06:00
7081e5daed
fix: correct subscriber counts by filtering CGNAT IPs and excluding routers
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
2026-02-18 09:15:10 -06:00
0f32086933
Ignore pod shutdown errors in ErrorTracker
Add ErrorTrackerIgnorer to drop port_died, write_failed, epipe, and
broken pipe errors that occur during K8s pod shutdown. Same patterns
already filtered by HoneybadgerFilter and LoggerFilters.
2026-02-17 10:59:51 -06:00
2929848f51
fix: validate NetBox URL scheme before making HTTP requests
The NetBox client crashed with "scheme is required for url" when
integration credentials had a nil or blank URL. normalize_url/1 now
validates the scheme and returns a clear error instead of letting
Finch blow up.
2026-02-17 10:50:00 -06:00
c92dfff6c9
Add ErrorTracker, remove Honeybadger email notice filter
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.
2026-02-17 08:13:34 -06:00
1712e7ac9d
Skip SNMP check execution in CheckExecutorWorker for agent-polled devices
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.
2026-02-17 07:47:15 -06:00
de986bddf6
Prevent Oban polling/monitoring job stacking per device
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.
2026-02-16 16:37:48 -06:00
179f0494e5
Fix pre-existing test failures from role enum and MikroTik changes
- 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
2026-02-16 16:11:32 -06:00
47b364a112
Fix Gaiia inventory matching, mapping UX, and logo link
- 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
2026-02-15 17:41:43 -06:00
f7dc546bcf
Merge remote-tracking branch 'origin/main' into gaiia 2026-02-15 17:17:10 -06:00
8b51b02d45
Compute per-site subscriber counts from IP block matching
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.
2026-02-15 17:11:23 -06:00
de075ea8d9
Store billing subscriber/MRR data on integrations for dashboard
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.
2026-02-15 16:51:33 -06:00
c6baeb9f47 Add Splynx client + sync tests, enhance Gaiia client tests
Splynx tests (20 new):
- Client: auth flow (api_key + admin fallback), test_connection,
  list_customers, list_routers, list_customer_services, pagination,
  auth header format verification, error handling
- Sync: full sync, MRR calculation, zero MRR, nil/numeric MRR,
  auth failure, rate limiting
- All response fixtures captured from real demo.splynx.com API

Gaiia tests (4 new):
- Client: unexpected status codes, header verification,
  create_ticket mutation, create_note mutation

Uses Req.Test stubs with realistic response shapes — no network
traffic during test runs. 7374 tests, 0 failures.
2026-02-15 16:20:22 -06:00
635d89d774 Fix mobile Gaiia entity mapping + redirect / to /dashboard
- 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
2026-02-15 15:05:20 -06:00
8fe90670b6 Redesign integrations page: billing providers as picker
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.
2026-02-15 13:00:10 -06:00
9ecd01dc8f Security hardening: remaining audit fixes
- Encrypt session cookies (add encryption_salt to endpoint)
- X-Frame-Options: SAMEORIGIN → DENY (match CSP frame-ancestors 'none')
- Remove unsafe-eval from CSP script-src
- Apply security headers in all environments (not just prod)
- Add GraphQL query complexity limit (max 500) via Absinthe.Plug
- GraphQL introspection already blocked in prod via plug

Closes audit items #6, #7, #12, #13, #14, #16
2026-02-15 12:54:38 -06:00
91dd7ad985
fix: allow site_id on device create regardless of use_sites setting
The API was silently stripping site_id when use_sites was false, causing
the Terraform provider to report inconsistent state after apply.
2026-02-15 12:28:06 -06:00
74332e2203
fix: Gaiia and Preseem sync bugs from API response mismatches
- 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
2026-02-15 12:23:47 -06:00
c790794191 Fix tests, credo issues, and Gaiia sync bugs
- 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
2026-02-15 11:55:49 -06:00
a028695d3b
format 2026-02-14 13:57:25 -06:00
950f4d9ae0 fix: implement Gaiia webhook signature verification per their spec
- 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
2026-02-14 13:37:26 -06:00
3284f0bce6 fix: increase walker concurrent test timeout, prefix unused vars, add heex to dialyzer ignore 2026-02-14 13:07:14 -06:00
c59a852e22
fix: resolve typing violation and ETS cache race in tests
- 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
2026-02-14 12:32:43 -06:00
146f5745cf
fix: resolve compilation errors, test failures, and credo issues
- 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
2026-02-14 12:23:10 -06:00
bb4ff232e3 feat: add PagerDuty Events API v2 integration with 2-way alert sync 2026-02-14 11:28:57 -06:00
f326eb5dd4 feat: require email verification before first login
- 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
2026-02-14 11:28:57 -06:00
6d8c3f932b test: add stream_data property tests for org settings and integrations
- 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
2026-02-14 11:28:57 -06:00
dbed25b366 test: add stream_data property-based tests for org settings 2026-02-14 11:28:57 -06:00
8d00a04187
feat: add FluxCD image automation, fix Preseem duplicate AP crash
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.
2026-02-14 10:30:12 -06:00
1dbb8926ed
fix: update remaining gaiia graphql status fields for object schema
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.
2026-02-13 16:16:00 -06:00
d603a12322
feat: add per-organization Gaiia webhook setup UI
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.
2026-02-13 16:15:38 -06:00
20ec04379d
fix: update gaiia graphql queries for account status/type schema change
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.
2026-02-13 15:20:06 -06:00
872bef3512
feat: add 12/24-hour time format setting with consistent timezone display
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.
2026-02-13 15:16:29 -06:00
20f5f48372
feat: command center dashboard with unified insights and contextual enrichment
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
2026-02-13 14:52:49 -06:00
848efa8fc4
fix: align gaiia graphql queries with actual api schema
field mappings from docs:
- accountStatus/accountType -> status/type
- accountBillingSubscriptions -> billingSubscriptions
- billingSubscriptionStatus -> status
- address city/state/zip -> locality/region/postalCode
- networkSiteIpBlocks -> ipBlocks
- inventoryItemStatus -> status, ipAddress -> ipAddressV4
- inventoryModel -> model, inventoryModelCategory -> model.category
- account/networkSite -> assignation.assignee (union type)
- product/recurringAmount/currency -> productVersion (price in cents)
- account on subscription -> entity (union type)
2026-02-13 14:41:14 -06:00
c01f638d3a
fix: use valid graphql query for gaiia connection test
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.
2026-02-13 13:44:58 -06:00
55382a7ec6
add gaiia inventory reconciliation (stage 7) 2026-02-13 13:28:52 -06:00
6da05d461b
add gaiia write-back actions (stage 6)
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.
2026-02-13 11:25:21 -06:00
934ee5bb86
add gaiia outage impact analysis (stage 5)
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.
2026-02-13 11:21:59 -06:00
2e9f1660d0
stagger preseem sync across organizations
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.
2026-02-13 10:52:32 -06:00
bad1590fed
add gaiia webhook listener with per-org signature verification
Stage 3 of Gaiia integration: webhook event processing for real-time
incremental updates from Gaiia. HMAC-SHA256 signature verification
using per-organization secrets stored in integration credentials.
Handles account, billing subscription, and inventory item events.
2026-02-13 10:40:49 -06:00
3cd2303b6f
add gaiia integration stages 1-2: sync engine and entity mapping UI
Stage 1 - Foundation:
- GraphQL client with Relay cursor pagination and rate limiting
- 4 Ecto schemas: accounts, network sites, inventory items, billing subscriptions
- Gaiia context with upsert operations preserving user mappings
- Sync module orchestrating full organization sync
- Oban cron worker running every 15 minutes
- Migration creating 4 tables with org-scoped unique constraints

Stage 2 - Entity Mapping:
- GaiiaMappingLive with two tabs (Network Sites / Inventory Items)
- Match suggestion engine (IP/name matching with confidence levels)
- Inline search and link/unlink UI following Preseem devices pattern
- Filter by all/mapped/unmapped with URL-driven state
- Amber suggestion badges for potential matches
2026-02-13 10:31:32 -06:00
21bbcccc30
fix preseem api base url and remove base_url override
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.
2026-02-13 10:12:43 -06:00
b3f8204047
test fix and preseem check fix 2026-02-13 09:42:39 -06:00
860fb7c3b6
Merge remote-tracking branch 'origin/main' into feature/preseem-integration
# Conflicts:
#	lib/towerops_web/live/device_live/show.ex
#	lib/towerops_web/live/device_live/show.html.heex
2026-02-13 09:11:45 -06:00
9ccea8daaf
add network insights feed page with filters and bulk dismiss 2026-02-13 09:00:34 -06:00
9a19b9b0a5
add preseem insights schema, generation, and queries 2026-02-13 08:53:01 -06:00
6a827a0ed6
add preseem baseline and fleet intelligence computation 2026-02-13 08:40:04 -06:00