Commit graph

717 commits

Author SHA1 Message Date
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
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
e026930f25
logging fix and some other codex fixes 2026-03-10 15:04:28 -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
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
a19155f3de
fix: remove all compact_mode references from device index template 2026-03-10 11:22:40 -05:00
f19fb958cb
refactor: remove non-functional compact view button from devices page
Removed:
- Compact view toggle button from the header
- toggle_compact_mode event handler
- @compact_mode assign

The button was not connected to any actual functionality.
2026-03-10 11:02:12 -05:00
1920c7bc5a
fix: show total discovered device count in tab badge instead of paginated count
The discovered tab badge was showing length(@discovered_devices) which is
the paginated list (20 items per page), not the total count. Now shows
@pagination.total_count to display the actual total (e.g., 876 devices).
2026-03-10 11:00:06 -05:00
6fd03ace16
feat: add comprehensive wireless client tracking and monitoring
Implements real-time wireless client monitoring with historical tracking,
LiveView UI, proactive alerting, and cross-browser e2e tests.

Phase 1: Historical Tracking
- Add TimescaleDB hypertable for wireless_client_readings
- Batch insert client metrics every 60 seconds from DevicePollerWorker
- 90-day retention with compression after 7 days
- Continuous aggregates for hourly (1 year) and daily (5 years) rollups

Phase 2: LiveView UI
- Add wireless tab to device detail page
- Real-time client list with PubSub updates
- Signal strength and SNR badges with 5-level thresholds
- Display MAC, IP, subscriber, TX/RX rates, distance, uptime
- Subscriber matching via device_subscriber_links
- Empty state handling

Phase 3: Proactive Alerting
- WirelessInsightWorker runs every 5 minutes via Oban cron
- 4 insight types with auto-resolution:
  * wireless_signal_weak: < -75 dBm (warning), < -85 dBm (critical)
  * wireless_snr_low: < 15 dB (warning), < 10 dB (critical)
  * wireless_ap_overloaded: > 50 clients (warning), > 75 clients (critical)
  * wireless_client_missing: expected subscribers not connecting
- Hysteresis thresholds prevent alert flapping
- Multi-organization isolation with proper deduplication

Code Quality:
- Refactored reload_current_tab_data to reduce cyclomatic complexity
- Combined double Enum.filter into single pass for efficiency
- Fixed length/1 comparison to use empty list check
- All Credo checks passing

Testing:
- 28 unit tests (ExUnit) - 100% passing
- 15 e2e tests (Playwright) - 100% passing across chromium/firefox/webkit
- Total: 73 tests, all passing

Files changed:
- lib/towerops/workers/wireless_insight_worker.ex (NEW)
- lib/towerops_web/live/device_live/show.ex (wireless tab + refactoring)
- lib/towerops_web/live/device_live/show.html.heex (wireless template)
- lib/towerops/snmp.ex (5 new query functions)
- lib/towerops/gaiia.ex (list_missing_subscribers)
- lib/towerops/preseem/insight.ex (5 new insight types)
- config/runtime.exs (Oban cron schedule)
- test/support/fixtures/snmp_fixtures.ex (NEW)
- test/towerops/workers/wireless_insight_worker_test.exs (NEW)
- test/towerops_web/live/device_live/show_test.exs (9 new tests)
- e2e/tests/wireless-clients.spec.ts (NEW - 15 cross-browser tests)
2026-03-10 09:57:12 -05:00
f09d1b296d feat: add historical wireless client tracking with TimescaleDB
- Create wireless_client_readings hypertable with compression and retention
- Add WirelessClientReading schema and batch insert function
- Update DevicePollerWorker to save historical readings
- Add signal_badge and snr_badge components to CoreComponents
2026-03-09 17:42:17 -05:00
108fb2b7dc
fix: add navigation and footer to changelog page
Wrapped ChangelogLive content in Layouts.authenticated component
to include standard navigation header and footer.
2026-03-09 15:59:13 -05:00
4e3f732f21
ui: comprehensive button styling audit - convert all text-styled actions to proper buttons
Fixed 10 text-styled action buttons across 7 files:

Agent Management:
- Delete Agent buttons (2x) → danger variant
- View Setup links (already fixed) → secondary variant

Organization Settings:
- Remove Member button → danger variant

Admin Pages:
- Edit Overrides button → secondary variant
- Delete Organization button → danger variant
- Remove IP/CIDR button → danger variant
- Delete User button → danger variant

Device Management:
- Delete Backup button → danger variant

All action buttons now use proper .button component with appropriate
variants (danger for destructive actions, secondary for neutral actions).
This provides consistent visual hierarchy and makes destructive actions
more clearly identifiable.
2026-03-09 15:32:42 -05:00
c94ac6e650
ui: convert text-styled action links to proper buttons
- Login page: 'Sign up' link → full-width button
- Agent setup: 'View Setup' text links → secondary buttons

Makes primary actions more visually prominent and consistent
with button component design system.
2026-03-09 15:02:48 -05:00
34e9365520
fix: another Mix.env() call in device_live/index.ex
- Fixed debounce_ms/0 function still using Mix.env()
- Changed to Application.get_env(:towerops, :env)
- This was causing crashes in staging/production
2026-03-09 14:59:41 -05:00
4b11f74e39
fix: replace Mix.env() with Application.get_env() for production compatibility
Mix is not available in production releases. Use Application.get_env/3
to check environment at runtime instead of Mix.env().
2026-03-09 12:47:50 -05:00
fc63a4faaf
fix: properly disable Plug.Telemetry logging for health checks
Set conn.private.log to false to prevent Plug.Telemetry from logging
health check requests in addition to phoenix_log and plug_skip_telemetry.
2026-03-08 16:39:26 -05:00
5a63adacef
fix: align Send Invite button with form inputs
Add invisible label above button to match input field structure,
ensuring proper vertical alignment when using items-end grid alignment.
2026-03-08 16:14:44 -05:00
1c222f8ff5
feat: add capacity insight worker, UI, and reporting views
Add CapacityInsightWorker (every 15 min) that generates critical/warning
insights when backhaul utilization exceeds 90%/75% and auto-resolves
when it drops below 70%.

Add capacity and utilization columns to the device ports tab with
set/clear capacity controls. Add organization-level /capacity page
with summary cards and per-site capacity table. Add capacity summary
card to site show page. Add Capacity link to nav menu.
2026-03-07 14:42:40 -06:00
76545ee31c
fix: handle alert_changed PubSub messages and primary owner ordering
- Add handle_info for {:alert_changed, org_id} in AlertLive.Index and DashboardLive
  to properly handle organization-level alert change broadcasts
- Fix get_primary_owner query to prioritize is_default memberships first,
  ensuring the organization creator is always returned when multiple owners exist

Resolves 5 test failures related to PubSub message handling and
deterministic primary owner selection.
2026-03-07 12:04:30 -06:00
3fa0ea383a
feat: improve organization invitation UX
- Show sender's full name in invitation emails
- Use relative time for "Sent" column (more readable)
- Use date format for "Expires" column (shorter)
- Make invitation table horizontally scrollable

Fixes display cropping issue where long timestamps caused
the pending invitations table to overflow its container.
2026-03-07 11:58:46 -06:00
d5b0f38ed6
feat: add remaining e2e tests and fix 'too many open files' error
- Added e2e tests for auth flows (registration, login, password reset, TOTP)
- Added e2e tests for onboarding and organization creation
- Added e2e tests for team collaboration (org switching, invitations)
- Added e2e tests for activity feed
- Added e2e tests for device graphs (sensors, time ranges, interfaces)
- Added e2e tests for MikroTik backup comparison
- Added e2e tests for sites map (geographic view)
- Added e2e tests for network trace tool

- Fixed 'too many open files' error by configuring Phoenix code reloader
  to ignore deps/, _build/, node_modules/, e2e/, and other non-source directories
- This prevents the file system watcher from monitoring unnecessary files
2026-03-06 17:08:27 -06:00
0832abf33a
feat: comprehensive e2e test coverage for all user-facing features
- Added e2e tests for agents, insights, maintenance windows, maps, help pages
- Added e2e tests for organization settings, config timeline, MikroTik backups
- Added comprehensive search functionality tests
- Added dashboard and sites navigation tests
- Updated existing tests to handle sudo verification redirects
- Fixed navigation tests to be more defensive about missing data
- All 301 tests passing across chromium, firefox, and webkit
2026-03-06 16:58:06 -06:00
95d94b0ce5
fix: resolve dialyzer warnings in device_live/index.ex
Convert compile-time module attributes to runtime functions to avoid
dialyzer warnings about compile-time constant comparisons:

- tick_interval_ms: function instead of @tick_interval_ms
- debounce_ms: function instead of @debounce_ms

This fixes warnings where dialyzer knew the comparisons could never
be false/true based on compile-time values.
2026-03-06 16:45:37 -06:00
b3ee1e5bcb
fix: remove raw() from Gettext interpolation in login page
Gettext interpolation expects plain strings, not safe tuples.
Split the translation into separate parts instead.
2026-03-06 15:56:35 -06:00
cc3ca1c7de
feat: add site search to trace
Trace can now search sites by name, location, or address. Selecting
a site shows all devices at that site with status and recent alerts.
2026-03-06 15:37:23 -06:00
c86ca57864
feat: add real-time status emoji updates in page titles
Dynamically update page title emoji when device/alert status changes:
- Add PubSub broadcasts when alerts are created/resolved
- Create StatusTitleComponent LiveComponent to subscribe to alert changes
- Add StatusTitle JavaScript hook to update document.title in real-time
- Subscribe to organization:alerts channel on LiveView mount
- Push emoji update events when alert status changes

This provides immediate visual feedback in browser tabs when
critical/warning/healthy status changes without requiring page refresh.
2026-03-06 14:49:44 -06:00
9bef1b289d
feat: add status emoji to page titles
Add colored circle emoji (🔴/🟡/🟢) to page titles based on organization
health status:
- 🔴 Red: Critical alerts (device_down, agent_offline, severity 1)
- 🟡 Yellow: Warning alerts (severity 2 or any active alerts)
- 🟢 Green: No active alerts

Provides at-a-glance status visibility in browser tabs.

Note: Favicon remains static stoplight, only page title is dynamic.
2026-03-06 14:45:02 -06:00
100245975a
refactor: reduce complexity in global pricing validation
Extract validation logic into separate functions to reduce:
- Cyclomatic complexity from 12 to acceptable levels
- Nesting depth from 3 to 2

Improves code maintainability and passes Credo checks.
2026-03-06 14:44:33 -06:00
c110832b42
Revert "feat: add status emoji to page titles"
This reverts commit f3873e0da7.
2026-03-06 14:43:13 -06:00
f3873e0da7
feat: add status emoji to page titles
Add colored circle emoji (🔴/🟡/🟢) to page titles based on organization
health status:
- 🔴 Red: Critical alerts (device_down, agent_offline, severity 1)
- 🟡 Yellow: Warning alerts (severity 2 or any active alerts)
- 🟢 Green: No active alerts

Provides at-a-glance status visibility in browser tabs.
2026-03-06 14:42:55 -06:00
ac2547e7c9
Merge branch 'feature/dynamic-global-pricing' 2026-03-06 14:04:12 -06:00
9a4aa57521
feat: update marketing pricing from $3 to $2/device/month 2026-03-06 13:58:58 -06:00
a5e26745ae
feat: add global pricing defaults UI to admin orgs page 2026-03-06 13:58:29 -06:00
6d3fc15f4a
fix: allow data URLs in CSP for dynamic favicon
The dashboard status indicator favicon (colored circle) uses canvas
to generate a data URL that's set as the favicon. This was blocked
by CSP in staging/production because default-src didn't include data:.

Added data: to default-src directive to fix favicon not updating in
browser tabs when device status changes.
2026-03-06 13:51:31 -06:00
cc53dd6a64
fix: access interface stats through latest_stat association
The template accessed if_in_octets/if_out_octets directly on the
Interface struct, but these fields live on InterfaceStat. Access
through interface.latest_stat with nil guard to prevent KeyError
when the association is not loaded.
2026-03-06 13:02:21 -06:00
9aec87636b
feat: per-organization billing override admin UI
Allow superadmins to override free device limits and per-device pricing
on a per-organization basis. Overrides cascade through subscription
limits, billing calculations, and user-facing settings display.

- Add custom_free_device_limit and custom_price_per_device to organizations
- Add billing_override_changeset with validation
- Update SubscriptionLimits.effective_device_limit to respect overrides
- Update Billing to use effective free count and price per device
- Add Admin.update_billing_overrides with audit logging
- Add override editing UI to /admin/organizations
- Update org settings page to show effective limits/pricing
2026-03-06 13:02:05 -06:00
1d98cda79d
fix: configure Watchtower to only monitor agent container
Add WATCHTOWER_SCOPE=towerops environment variable to generated
docker-compose templates. This prevents Watchtower from monitoring
unrelated containers on the same Docker host (e.g., Dokku apps).

Without scope filtering, Watchtower would attempt to monitor all
containers with watchtower.enable=true label, causing warnings
for missing images and unnecessary monitoring overhead.
2026-03-06 12:38:44 -06:00
4b4bb2668f
feat: add billing tab to organization settings
Implements Phase 6 of Stripe billing integration:
- Add Billing tab to organization settings page
- Display subscription status, device usage, and estimated costs
- "Upgrade to Paid Plan" button for free tier orgs
- "Manage Billing" button to access Stripe Customer Portal
- Billing information panel showing subscription details
- Support for dark mode and responsive design

Also fixes organization name truncation in top navigation:
- Long organization names now show with ellipsis instead of being cut off
- Added max-w-xs constraint and truncate class to org switcher button
2026-03-06 11:08:05 -06:00
b3cff9afbb
stripe and email after signup 2026-03-06 10:07:27 -06:00
07911dfa3e
perf(tests): make AgentChannel debounce delay configurable for faster tests
- Add :agent_channel_debounce_ms config (500ms prod, 50ms test)
- Reduce test timeouts from 700-1000ms to 150ms
- Performance improvements:
  * agent receives updated job list: 1567ms → 664ms (57% faster)
  * agent result for deleted device: 1059ms → 620ms (41% faster)
  * rapid assignment changes: 744ms → <500ms
  * debounces rapid changes: 613ms → <200ms
- Total time saved: ~2 seconds across affected tests
- No production behavior changed
2026-03-06 07:21:52 -06:00
b0fcfb800f
fix(logs): add Plug.Telemetry metadata filter to suppress health check logs
The FilterNoisyLogs plug wasn't completely preventing logs in Phoenix 1.8.
Added metadata_filter to Plug.Telemetry to explicitly skip logging for:
- GET /health
- GET /health/time
- HEAD /

This works at the Telemetry level before logs are emitted.
2026-03-05 19:23:23 -06:00
192cf5b064
fix(alerts): resolve stuck alerts on every successful poll, not just status change
Previous fix only resolved alerts when device status changed from down to up.
This didn't help devices already marked as 'up' with stuck unresolved alerts.

Now:
- Always check for stuck device_down alerts after successful SNMP poll
- Also check for stuck device_up alerts (should always be auto-resolved)
- Logs when stuck alerts are found and resolved
- Works for existing stuck alerts without waiting for status to change

This fixes the 8 stuck alerts in production - they'll auto-resolve on next poll.
2026-03-05 16:54:00 -06:00
3f1d97218d
fix(alerts): auto-resolve device_down alerts when agent polls device successfully
When agents poll devices via SNMP and get successful results, the code was
only processing sensor/interface data without updating device status or
resolving alerts. This caused device_down alerts to persist even after
devices came back online.

This fix:
- Updates device status to 'up' when SNMP polling succeeds
- Auto-resolves any active device_down alerts
- Creates device_up alert (matching DeviceMonitorWorker behavior)
- Broadcasts device_status_changed via PubSub

Also fixes device_up alert creation to include resolved_at timestamp
(matching DeviceMonitorWorker behavior).

Test coverage: Added test verifying auto-resolution when device recovers.
2026-03-05 16:47:48 -06:00
ab24a3100e
fix: suppress health check logs and downgrade CI artifacts to v3
Health Check Logging:
- Update TelemetryFilter to catch both request and response logs
- Filter /health and /health/time endpoints completely
- Check phoenix_log metadata to suppress response logs
- Prevents log flooding from Kubernetes probes (every 5s)
- Refactor to reduce cyclomatic complexity (Credo compliant)

CI Artifact Actions:
- Downgrade upload-artifact from v4 to v3
- Downgrade download-artifact from v4 to v3
- v4 is not supported on Forgejo/GHES

Fixes:
- Production logs no longer flooded with health check entries
- CI pipeline works on self-hosted Forgejo instance
2026-03-05 15:54:47 -06:00