Commit graph

483 commits

Author SHA1 Message Date
8752dfec49 fix: netbox url field type, gaiia ipRange→block, remove unknown webhook log 2026-02-14 17:44:01 -06:00
a3730db579 i18n: add Spanish locale with full translations, language selector, and locale hook 2026-02-14 17:44:01 -06:00
c8e398ea05 Add NetBox sync worker + improve Gaiia error messages
- 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'
2026-02-14 17:11:38 -06:00
b8a737ddf4 Fix Gaiia: subnet -> ipRange on NetworkSiteIpBlock 2026-02-14 16:16:44 -06:00
e3c730a8e9 Fix Gaiia readable_id: coerce to string (can be integer) 2026-02-14 16:15:59 -06:00
ebcba70925 Fix Gaiia GraphQL: SubscriptionStatus is scalar, not object 2026-02-14 15:09:05 -06:00
7b72ed3164 PagerDuty two-way sync: webhook receiver for resolve/acknowledge
- POST /api/v1/webhooks/pagerduty/:organization_id endpoint
- HMAC-SHA256 signature verification (optional, via webhook_secret)
- incident.resolved → resolve_alert_silent (no re-notify to PagerDuty)
- incident.acknowledged → acknowledge_alert_silent (no re-notify)
- Matches alerts via dedup_key format: towerops-alert-<uuid>
- Webhook secret configurable in org settings PagerDuty panel
- Setup instructions in UI for PagerDuty Generic Webhooks (v3)
2026-02-14 14:58:58 -06:00
29c2039832 Auto-resolve device_up recovery alerts on creation
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.
2026-02-14 14:53:17 -06:00
61f803afe5 Add language user setting (default: English) 2026-02-14 14:53:17 -06:00
1cfe306373
format 2026-02-14 14:52:08 -06:00
d1cd608ae9 feat: actionable insights with fix-it buttons, proper pluralization via gettext
- Gaiia 'not tracked' insight: Map to Gaiia inventory + View reconciliation buttons
- Gaiia 'ghost' insight: Clean up stale mappings button
- Gaiia 'mismatch' insight: Review mismatches button
- Fixed device(s) → proper ngettext pluralization
- Fixed Towerops → TowerOps capitalization
2026-02-14 14:46:19 -06:00
db40a3e5b7 feat: show human-readable sync status messages for integrations
- 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
2026-02-14 14:11:02 -06:00
5917190efe feat: add NetBox integration with sync direction, entity selection, and filtering
- Three sync modes: NetBox→TowerOps (pull), TowerOps→NetBox (push), Bidirectional
- Configurable entity sync: devices, sites, IP addresses, interfaces
- Optional filters by device role, site, and tag (comma-separated)
- NetBox client with test connection, CRUD operations
- Radio card UI for sync direction, checkbox cards for entity selection
- Credentials stored encrypted (URL, API token, sync config)
2026-02-14 14:09:01 -06:00
a028695d3b
format 2026-02-14 13:57:25 -06:00
392b1e135c fix: trace page crash - detected_at → changed_at to match ConfigChangeEvent schema 2026-02-14 13:44:07 -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
5df5c97c45 feat: add geographic location to sites with geocoding, address fields, and Leaflet.js network map 2026-02-14 13:07:14 -06:00
74f112642d feat: make network insights actionable with affected device links and metadata 2026-02-14 13:07:14 -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
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
12b1a64f8d fix: use atom for Ecto.Enum alert_type in activity feed query 2026-02-13 19:52:29 -06:00
0fc36524b4 fix: resolve 500 errors on /activity and /changelog
- 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)
2026-02-13 19:49:12 -06:00
67eac2f1c9 fix: prevent cascading crash in error email handler when SES not configured 2026-02-13 19:44:51 -06:00
9e7ee5099d refactor: fix all credo strict issues, format all code, fix broken routes
- 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
2026-02-13 19:34:40 -06:00
b7204b604d fix: remove experimental badges from Alerts and Network Map nav items 2026-02-13 19:11:34 -06:00
723f475933 feat: add global search (Cmd+K) across devices, sites, accounts, alerts
- Add Towerops.Search context module with categorized search
- Add GlobalSearchComponent LiveComponent with modal overlay
- Add GlobalSearch JS hook for Cmd+K/Ctrl+K keyboard shortcut
- Wire into authenticated layout
- Keyboard navigation (up/down/enter/esc) support
- DaisyUI-styled modal with categorized results (5 per category)
- Searches: device name/IP, site name, account name/ID, alert message
2026-02-13 18:56:15 -06:00
da5a6c9edc fix: add missing type_badge_class to trace_live 2026-02-13 18:42:00 -06:00
c512f30e5a feat: add device list health indicators
- 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
2026-02-13 18:14:29 -06:00
5a3ede1f71 feat: config change → performance correlation system (Roadmap #2)
- New config_change_events table linking backup diffs to time windows
- ConfigChanges context with record/list functions and date filters
- Auto-detect changed sections from MikroTik config diffs
- Agent channel emits change events after backup creation
- Correlator engine: 2h before / 4h after QoE metric comparison
- Creates suspect_config_change insights on significant degradation
- Config Timeline LiveView with chart data, change list, detail view
- Device page: Config Timeline tab + Recent Config Changes card
- Dashboard: Recent Config Changes section with impact indicators
- Fix pre-existing integrations_live.html.heex scope bug
2026-02-13 17:56:55 -06:00
a1bf5f284a feat: impact dashboard with MRR at risk, active incidents, enhanced site health
- 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
2026-02-13 16:38:14 -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
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
e4499e8dd0
fix: use correct auth header for gaiia api
gaiia expects X-Gaiia-Api-Key header, not Authorization: Bearer.
2026-02-13 14:01:19 -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
ce85fb0465
add gaiia subscriber context to device detail pages
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.
2026-02-13 10:59:05 -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
9a19b9b0a5
add preseem insights schema, generation, and queries 2026-02-13 08:53:01 -06:00