Commit graph

1063 commits

Author SHA1 Message Date
f980e9eb6d
fix: preserve NetBox url/api_token in netbox_config across re-renders
Sync direction button clicks (phx-click) triggered a LiveView re-render
that reset the url and api_token inputs to their saved DB values, wiping
any unsaved typed text. Fix by tracking url and api_token in @netbox_config
so they survive click-triggered re-renders.
2026-03-12 14:25:08 -05:00
6db0213686
remove subtitle from alerts page 2026-03-12 14:09:05 -05:00
5d1d277697
feat: add alert count badge to notification bell
Shows a red badge on the bell icon with the unresolved alert count.
Displays the number when <= 100, blank red dot when > 100 (Apple-style).
Count is loaded at mount and updated live when alerts change via PubSub.
Uses process dictionary pattern (same as cookie consent) so no template
changes needed across the 41 LiveView layouts.
2026-03-12 14:06:18 -05:00
ad4fde9415
fix: humanize raw error strings in integration sync modules
Replace binary passthrough in humanize_sync_error/1 across all 6 sync
modules (Gaiia, VISP, NetBox, Preseem, Splynx, Sonar) so that raw
Erlang/SSL error messages never surface in the UI. SSL/CA cert errors
get a clean message; all other raw strings are logged and replaced with
a generic user-facing message.
2026-03-12 13:53:28 -05:00
ecc69b4e96
ux: improve navigation, tooltips, and discoverability across pages
- Replace "Back to X" links with proper breadcrumb trails on settings,
  integrations, Gaiia, and Preseem pages
- Fix active_page on config-timeline routes so Devices nav highlights
- Add title tooltip to truncated alert messages (hover to see full text)
- Add tooltips to QoE column header explaining the metric
- Add tooltips to Subs/MRR labels on dashboard status bar
2026-03-12 13:40:10 -05:00
58181923f8
improve Gaiia integration UX
- Make insight messages more descriptive (explain what untracked/ghost/mismatch means)
- Clarify action button labels on insight cards
- Redesign Gaiia mapping page to be TowerOps-centric: devices/sites as primary rows, search Gaiia for what to link them to
- Add IP-based match suggestions and smart sorting (suggestions first, then unlinked, then linked)
- Add search_inventory_items/2 and search_network_sites/2 to Gaiia context
- Make IP addresses clickable links (http://<ip>)
- Add optional Gaiia App URL setting to enable deep links to inventory items and network sites
2026-03-12 13:04:17 -05:00
1b8fb606a5
remove dedicated capacity page from nav and routing
Capacity functionality will be surfaced through insights instead.
2026-03-12 12:20:23 -05:00
904ced77b9
fix: add recurring job dispatch timer for agent-polled devices
The server only sent polling/ping jobs to agents once on WebSocket join
and never re-dispatched them. After the first batch completed (~5-10s),
devices were never polled again until the agent reconnected.

:send_jobs now schedules a follow-up dispatch every 60s (configurable).
Unifies the timer management with :assignments_changed to prevent
accumulation.
2026-03-12 12:07:34 -05:00
793c38e784
fix crash when clicking site compound nodes on network map
Site compound nodes have IDs like "site_<uuid>" which can't be cast to
binary_id in Ecto queries. Add guard clause to get_node_detail/2 and
skip node_clicked events for site nodes in Cytoscape hook.
2026-03-12 11:54:15 -05:00
80304329bd
improve network map topology visualization
- fix bidirectional link queries to find links where device is source or target
- merge A→B and B→A edges into single link with combined confidence
- include interface speed in edge data for variable edge width
- infer discovered node roles from LLDP/CDP capabilities (router, switch, wireless, phone)
- store remote_capabilities in link metadata during evidence processing
- replace Cytoscape destroy-and-recreate with element diffing to preserve zoom/pan/positions
- add node detail slide-out panel with device info, connections, and confidence
- support ?node= URL param for deep-linking to selected nodes
- add site-based compound node grouping in Cytoscape
2026-03-12 10:59:20 -05:00
72f81c572d
add real-time GraphQL API with time-series queries and subscriptions
- add absinthe_phoenix for WebSocket subscription support
- create GraphQL WebSocket socket with API token auth at /socket/graphql
- add time-series queries: deviceSensors, sensorReadings, interfaceTraffic, checkResults
- add subscriptions: deviceStatusChanged, alertEvent, sensorReadingsUpdated
- wire subscription triggers into agent_channel and alerts contexts
- add org-scoped access control for sensors/interfaces via join queries
- add 17 tests covering queries, org isolation, empty data edge cases, and auth
- update GraphQL API docs page with time-series and subscription sections
2026-03-12 10:24:39 -05:00
550a23dafc
fix SSL CA trust store for HTTPS in containers
Start a named Finch pool with CAStore certificate path and configure
Req to use it by default. Fixes integration sync failures on Dokku
where the OS CA trust store is unavailable.
2026-03-11 14:48:54 -05:00
89ca5cadb4
add day dividers between date labels on schedule timeline 2026-03-11 14:43:37 -05:00
7613a4dde5
add missing REST API controller tests
Tests for agents, alerts, integrations, organization, members, and
invitations controllers. Refactor alert filters to reduce complexity.
75 new tests, all passing.
2026-03-11 14:43:17 -05:00
17fa005781
add maintenance windows REST API and GraphQL support
REST endpoints for CRUD, scoped filtering (active/upcoming/past).
GraphQL queries and mutations for maintenance windows.
Fix agent edit test assertion message.
2026-03-11 14:33:25 -05:00
f6e9063577
feat: add REST API and GraphQL for on-call schedules and escalation policies
REST API v1 endpoints for schedules (14 endpoints) and escalation
policies (10 endpoints) with full CRUD for nested resources (layers,
members, overrides, rules, targets). GraphQL types, resolvers, and
schema mutations for both resource trees.
2026-03-11 14:01:15 -05:00
5026614bf3
fix: nav overflow on small screens and fix bad gettext translations
Bump desktop nav breakpoint from md (768px) to lg (1024px) so the
mobile hamburger menu is used on medium-width screens instead of
nav items overflowing outside the viewport.

Clear all 65 bad fuzzy translations in English .po files (e.g.
"Navigation" was incorrectly translated to "Pagination") and fix
49 fuzzy entries in Spanish .po files with correct translations.
2026-03-11 14:00:31 -05:00
3bba1a6289
fix: display on-call user names instead of emails on schedule index
Update add_member test to use phx-change select instead of form submit.
2026-03-11 13:35:43 -05:00
61368e5c5e
enhance: schedule show with PagerDuty-style timeline and UX improvements
- Add 2-week visual timeline with per-layer and final schedule views
- Color-coded user spans with today marker and prev/next navigation
- Inline user picker when adding new layers (auto-add on select)
- Auto-add members on select for existing layers (no Add button)
- Display user names (first last) instead of emails throughout
- Default handoff time to 8:00 AM and start date to today
2026-03-11 12:59:23 -05:00
0cd2ed3567
feat: add on-call schedules and escalation policies
Built-in PagerDuty-equivalent system for on-call scheduling and alert
escalation. Users can now manage schedules, rotation layers, overrides,
and escalation policies directly in the app alongside PagerDuty.

- On-call schedules with rotation layers (daily/weekly/custom), member
  management, and temporary overrides
- Escalation policies with ordered rules, timeout-based escalation,
  and user/schedule targets
- Automatic escalation via Oban worker with configurable repeat count
- Email notifications via Swoosh for on-call alerts
- Resolver computes who's on-call from layer stacking and overrides
- AlertNotificationWorker integration: starts escalation alongside
  PagerDuty, acknowledges/resolves incidents on alert state changes
- Device and organization schemas support escalation_policy_id
- Escalation policy picker on device form
- Schedules nav item with tabbed index (schedules + escalation policies)
- Full CRUD UI for schedules, layers, members, overrides, rules, targets
- 62 LiveView tests, 56 context/schema/resolver/escalation tests
- 26 E2E Playwright tests for smoke and critical path coverage
2026-03-11 12:32:54 -05:00
3b97e437f2
fix: add castore dep and sanitize sync error messages
Add castore as a direct dependency to provide CA certificates in
environments where the OS trust store is not available (Dokku staging).

Stop leaking raw system errors (like CA trust store details) to the
UI by logging the full error server-side and showing a generic
user-friendly message instead.
2026-03-11 10:41:09 -05:00
3b0487fd75
add 100% test coverage for Towerops.Accounts
- add 23 new ExUnit tests covering all previously uncovered code paths
- fix touch_changeset bug: DateTime.utc_now() -> DateTime.utc_now(:second)
  to avoid microsecond errors with :utc_datetime fields
- add defensive coverage tests using PostgreSQL triggers to exercise
  Multi transaction catch-all clauses and partial insert_all paths
- coverage: 88.67% -> 100.00% (169 -> 192 tests)
2026-03-11 10:37:03 -05:00
41708651a0
refactor: collapse inactive billing providers into expandable disclosure
When a billing provider is active, all other billing providers are
hidden from the main list and collected into a compact <details>
disclosure element showing provider names and descriptions.
2026-03-11 10:09:27 -05:00
031427a247
feat: collapse and dim inactive billing integrations
When a billing provider is active (enabled), the other billing providers
are dimmed and collapsed to a single row showing a lock icon and which
provider is active. This reinforces the mutually exclusive constraint.

Also fixes status emoji accumulating in page title (JS regex needed
unicode flag) and adds missing page_title to 8 LiveViews.
2026-03-11 10:00:38 -05:00
f2608e8f16
fix: status emoji accumulating in page title and add missing page titles
Fix JS regex in StatusTitle hook to use unicode flag so emoji are
properly matched and replaced instead of accumulating. Add page_title
to 8 LiveViews that were missing it, which caused "TowerOps | TowerOps"
duplication from the live_title suffix.
2026-03-11 09:54:18 -05:00
198f2c3cbc
feat: add API key setup help to integration configuration panels
Add setup_help info boxes to each integration provider showing where
to create API keys, step-by-step instructions, required permissions,
and example URL patterns.
2026-03-11 09:46:22 -05:00
5e5da89f3a
fix: correct SNMP OIDs for 7 vendor modules to match MIB definitions
Cambium PTP: Rx Power .12.6→.12.12 (rawReceivePower), Tx Power
.12.3→.12.4, data rates from .12 subtree→.20 subtree, renamed
Link Loss→Signal Strength Ratio, added aggregate rate

Ceragon: TX/RX power OIDs from .10.4.1.11→.10.5.1.1 subtree
(genEquipRfuStatusTxLevel/RxLevel), removed SNR and link status

Aviat: RSL→.9.15 (RXPERFORMANCE-MIB), Tx Power/SNR→.9.33
(RXPERFORMANCE-EX-MIB), capacity→.9.3 (MODEM-MIB), removed
temperature

DragonWave: SNR column .2.3→.2.8, RSL column .2.2→.2.4, removed
temperature

Exalt: frequencies .1.7/.1.8→.57.4/.57.5 (ExtendAirG2), Tx Power
.1.9→.57.1, RSL .2.1→.2.4.3.1.3 (locCurrentRSL), removed SNR

Teltonika: RSSI column 7→12, RSRP 8→20, SINR 9→19, RSRQ 10→21,
Connection Type 3→16, SIM Status 2→9, temperature .1.7→.2.2.1.17

SIAE: Tx Power/RSSI/Frequency→.39.2.1 subtree (Alfo80hd MIB),
added Tx/Rx rate from .15.4.1.17/.18, removed SNR
2026-03-10 17:07:43 -05:00
835b38c4de
fix: correct airos SNMP OIDs to match UBNT-AirMAX-MIB definitions
8 of 13 OIDs were pointing to wrong MIB columns:
- RSSI: was reading ubntWlStatSignal(.5), now ubntWlStatRssi(.6)
- Quality: was reading ubntWlStatSignal(.5), now ubntAirMaxQuality(.6.1.3)
- Tx Rate: was reading ubntWlStatSsid(.2), now ubntWlStatTxRate(.9)
- Rx Rate: was reading ubntWlStatHideSsid(.3), now ubntWlStatRxRate(.10)
- Capacity: was reading ubntWlStatTxRate(.9), now ubntAirMaxCapacity(.6.1.4)
- Distance: was reading ubntWlStatSecurity(.11), now ubntRadioDistance(.1.1.7)
- Utilization: was reading WDS/Repeater booleans(.12/.13), now ubntAirMaxAirtime(.6.1.7)
- Added Signal Level sensor from ubntWlStatSignal(.5)
2026-03-10 16:53:08 -05:00
9b3ffa874a
fix: correct airfiber SNMP OIDs to match MIB table definitions
All 7 OIDs were reading from wrong SNMP table/columns. Frequency and
power config values pointed at the status table instead of the config
table, and capacity/signal columns pointed at unrelated fields like
radio temperature and boolean flags.

Corrected OIDs to match UBNT-AirFIBER-MIB structure:
- Frequency/power: config table (.3.1) entries 5, 6, 9
- Signal: status table (.3.2) entries 11, 14
- Capacity: status table (.3.2) entries 5, 6 with 1M divisor
2026-03-10 16:41:14 -05:00
91a051f14d
Refactor duplicate site API logic 2026-03-10 16:27:35 -05:00
48270f5e35
chore: remove staged deletions from previous work 2026-03-10 16:20:41 -05:00
532d88ffb9
perf: disable Req retry for faster error tests
- Add retry: false to VISP Client HTTP requests
- Add retry: false to ReleaseChecker GitHub API requests
- Remove unused Plug.Conn import from RemoteIpLogger
- Remove unused default parameter from req_get/2

Results:
- VISP sync test: 7007ms → 113ms (62x faster)
- ReleaseChecker test: 7004ms → 17ms (402x faster)
- UserResetPasswordLive test: 1495ms → 284ms (5x faster)

Req's default retry behavior (1s, 2s, 4s exponential backoff) was
causing 7-second delays for HTTP 500/503 error responses in tests.
For these clients, immediate failure is preferred over retries.
2026-03-10 16:19:31 -05:00
495b1a75b2
Add GeoIP import HTTP tests 2026-03-10 16:02:49 -05:00
b4ef6b2020
Improve topology discovery matching 2026-03-10 15:44:46 -05:00
232915e3e4
fix: filter out ARP entries with empty MAC addresses from SNMP
- Added guard clause to return nil for empty binary MAC addresses
- Filter rejects entries with nil IP or MAC addresses
- Prevents 'can't be blank' validation errors on upsert
- Root cause: some SNMP devices return incomplete ARP entries
2026-03-10 15:33:52 -05:00
7525bfd9a3
fix: show ARP error samples in log message text instead of metadata
- Changed error logging to include sample errors directly in message string
- Ensures error details are visible regardless of logger metadata config
- Extracted format_error_samples/1 helper to reduce nesting (Credo)
- Helps diagnose ARP upsert failures in staging/production
2026-03-10 15:12:09 -05:00
e026930f25
logging fix and some other codex fixes 2026-03-10 15:04:28 -05:00
3e70f0ba48
fix: add detailed error logging for ARP upsert failures
Previously just logged a count without any details, making debugging
impossible. Now logs first 3 sample errors with IP, MAC, and validation
errors to help identify root cause.

Sample output:
  290 ARP upsert failures for device X
  sample_errors: [
    %{ip: nil, mac: "00:11:22:33:44:55", errors: %{ip_address: ["can't be blank"]}},
    %{ip: "10.0.0.1", mac: nil, errors: %{mac_address: ["can't be blank"]}},
    ...
  ]
2026-03-10 14:46:45 -05:00
bbb5f886ec
fix: add alert_changed handlers to critical LiveViews
Proactively add catch-all handlers to device index/show pages to
prevent FunctionClauseError when StatusTitleComponent broadcasts
alert changes. These are the most frequently open pages where users
would encounter the crash.
2026-03-10 14:12:29 -05:00
48b6f390a8
fix: handle alert_changed messages in AgentLive.Index
The StatusTitleComponent (rendered in authenticated layout) subscribes
to organization:*:alerts PubSub topic. Since LiveComponents run in the
parent process, both the component and parent LiveView receive these
messages.

Added catch-all handler to prevent FunctionClauseError when alert
changes broadcast. The component already handles the message properly.
2026-03-10 14:10:10 -05:00
a52f5a8644
fix: handle nil SNMP community string in discovery logging
- Check if community string is nil before slicing
- Display '(none)' instead of crashing with FunctionClauseError
- Fixes discovery crashes when community string is not set

Error was: String.slice(nil, 0..3) causing discovery tasks to fail
2026-03-10 13:57:49 -05:00
bc1ac21f1e
fix: use correct auth check for API organization updates
- API auth uses current_user, not current_scope
- Check membership role directly instead of using owner?/1 helper
- Allow superusers to update organization settings
- Fixes KeyError: key :current_scope not found

Previous code assumed LiveView context with current_scope, but API
requests only have current_user and current_organization_id assigned.
2026-03-10 13:41:38 -05:00
03487b1f55
rename "Admin Dashboard" to "Dashboard" 2026-03-10 13:36:03 -05:00
5fb5fea25e
feat: require org owner permission to update organization settings
- Add authorization check in organization_controller.ex update action
- Only organization owners can modify organization settings via API
- Returns 403 Forbidden if user is not an owner
- Affects all organization settings including use_sites and name
- Organization name can now be updated via API (existing field)

This ensures critical organization settings are only changed by authorized users.
2026-03-10 13:32:38 -05:00
62eb7f9949
fix: infer device role during discovery and handle string price values
Device Type Fix:
- Add Topology.maybe_update_device_role() call after discovery completes
- Device types (AP, router, switch, etc.) now inferred immediately after discovery
- Previously devices showed 'unknown' until first polling cycle completed
- Role inference uses SNMP manufacturer, model, and system description

Billing Fix:
- Handle string values from Settings.get_setting() in default_price_per_device()
- Add case clause to convert string '2.00' to Decimal struct
- Fixes CaseClauseError crash on /orgs/:slug/settings page

All discovery tests passing.
2026-03-10 13:25:51 -05:00
67e15d1dc5
feat: make normalize_mac/1 public and fix subscriber matching tests
- Made normalize_mac/1 public with comprehensive documentation
- Enhanced normalize_mac to handle:
  - Binary MAC addresses (6 bytes) with proper hex conversion
  - Short MAC addresses with zero-padding (e.g., 'A:B' -> '0a:0b')
  - Colons, dashes, and no-separator formats

- Fixed refresh_links_for_device/1 to return :ok when device not found
  (was returning nil, causing test failures)

- Fixed site fallback test to use assigned_network_site_gaiia_id
  (string field) instead of network_site_id (FK)

- Simplified account lookup optimization test to remove
  non-existent Repo.query_count/1 dependency

All 19 subscriber matching tests now passing:
- 7 normalize_mac/1 tests (binary, colons, dashes, padding, etc.)
- 12 refresh_device_links/1 tests (all match methods, prioritization,
  cross-org isolation, etc.)
2026-03-10 12:52:42 -05:00
991001f95a
fix(critical): ensure all money math uses Decimal, never floating point
CRITICAL FINANCIAL BUGS FIXED:

1. Stripe price creation was sending dollars instead of cents
   - create_price() now converts $2.00 -> 200 cents
   - Was sending "2.00" to unit_amount_decimal (0.02 dollars!)
   - Now correctly sends "200.00" (2 dollars in cents)

2. VISP sync was using floating point arithmetic for MRR
   - calculate_total_mrr() was using 0.0 accumulator and float math
   - Now uses Decimal.new("0") and Decimal.add() throughout
   - Prevents precision loss when summing subscriber MRR

All money calculations now use Decimal library to avoid floating
point precision errors. Amounts are only converted to cents (integers)
at the boundary when sending to Stripe API.
2026-03-10 12:17:42 -05:00
e1cfc4ece1
feat: add observability logging for invalid MAC addresses
- Log when normalize_mac drops invalid MAC addresses from SNMP data
- Helps diagnose data quality issues from network equipment
2026-03-10 11:34:45 -05:00
1a9a6e0ef6
fix: critical subscriber matching and performance improvements
- Add missing wireless_mac and wireless_ip to schema validation
  (was causing highest-confidence matches to be rejected)
- Optimize account lookup to load once instead of 5x per refresh
  (eliminates N+1 query performance issue)
- Preserve all matching phases with proper signature updates
2026-03-10 11:33:47 -05:00
a19155f3de
fix: remove all compact_mode references from device index template 2026-03-10 11:22:40 -05:00