Commit graph

245 commits

Author SHA1 Message Date
e278d320ce
design: improve stoplight favicon with transparent background
Make circles more prominent with transparent background and better spacing.
2026-03-06 14:36:32 -06:00
45579813bd
design: update favicon to stoplight style with app colors
Replace standard favicon with stoplight design using red (#ef4444),
yellow (#eab308), and green (#22c55e) circles matching the app's
status indicator colors.
2026-03-06 14:33:29 -06:00
ac2547e7c9
Merge branch 'feature/dynamic-global-pricing' 2026-03-06 14:04:12 -06:00
6e2bba4e88
docs: update changelogs for dynamic pricing feature 2026-03-06 14:00:58 -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
448ed20401
feat: dynamic global billing defaults from ApplicationSettings 2026-03-06 13:44:03 -06:00
7d61d8fb0a
feat: seed global billing settings in application_settings 2026-03-06 13:26:50 -06:00
9229e6e920
fix: remove internal details from public changelog 2026-03-06 13:03:36 -06:00
e161aaab83
docs: update changelogs for billing overrides and interface stats fix 2026-03-06 13:02:55 -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
d17b7baba4
fix: correct 'More' navigation translation from 'or' to 'More' 2026-03-06 12:26:18 -06:00
b3cff9afbb
stripe and email after signup 2026-03-06 10:07:27 -06:00
8f52d87854
feat: add rate limiting to admin endpoints (100 req/min)
- Add :admin type to RateLimit plug with 100 requests per minute per IP
- Apply rate limiting to all /admin routes (LiveView and controller actions)
- Protects superuser endpoints from abuse and brute force attempts
- Aligns with existing auth (10 req/min) and API (1000 req/min) limits
2026-03-05 14:11:19 -06:00
8f31219d85
fix: add --skip-if-loaded to test alias to prevent prompts
The test alias was prompting 'Are you sure you want to proceed?' when
the database structure was already loaded, requiring manual confirmation
on every test run.

Add --skip-if-loaded flag to ecto.load command so it silently skips
structure loading if schema_migrations table already exists.

Now 'mix test' runs without user interaction.
2026-03-05 13:49:51 -06:00
ade7e2fe15
fix: correct mail adapter message interpolation in login page
Fixes fragmented translation that displayed raw '%{link}' placeholder text
instead of properly interpolated link in development mail adapter notice.

Changes:
- Consolidate separate translation calls into single interpolated string
- Use raw() helper to safely inject HTML link into translated text
- Update Spanish translation to include %{link} placeholder
- Extract updated translations with mix gettext.extract

Before: "To see sent emails, visit %{link}. the mailbox page."
After: "To see sent emails, visit the mailbox page." (with proper link)
2026-03-05 13:32:34 -06:00
7607a583cf
perf(ci): optimize database setup with structure.sql (99.7% faster)
Replace sequential migration execution (4m8s for 172 migrations) with
structure.sql loading (416ms), reducing CI database setup time by 99.7%.

Changes:
- Configure Ecto to dump/load schema from priv/repo/structure.sql
- Update test alias: ecto.migrate → ecto.load
- Remove redundant database setup step from CI workflow
- Commit structure.sql to repository for version control

Benefits:
- CI database setup: 4m8s → 416ms (248s → 0.4s)
- Consistent schema snapshots across environments
- Faster local test runs

Maintenance:
- Run 'mix ecto.dump' after adding new migrations
- Structure file auto-updates with schema changes
2026-03-05 13:30:52 -06:00
1d928d4356
security: implement comprehensive security audit fixes
Critical Fixes:
- Remove /health/time endpoint exposing system time information
  * Prevents attackers from detecting time sync issues for TOTP attacks
  * Removed route and controller function, updated tests
- Add email confirmation check to account data export
  * GDPR export now requires confirmed email address
  * Prevents unconfirmed accounts from accessing data export
- Add path traversal validation for MIB archive uploads
  * Extract to temp directory, validate all paths, then copy if safe
  * Prevents malicious tar/zip files from writing outside target directory
  * Added validate_extracted_paths/1 helper function

High Priority Fixes:
- Add comprehensive input validation for mobile auth
  * Length limits: device_name (255), device_os (100), app_version (50), push_token (512)
  * Prevents database corruption and storage exhaustion
- Add heartbeat rate limiting to agent channel
  * Limit database updates to once per 30 seconds (max ~2/min per agent)
  * Prevents malicious agents from exhausting database connections
- Sanitize 500 error responses
  * Return generic error messages to clients
  * Log full details server-side with request_id for support
  * Prevents leaking stack traces and module names
- Add message size limits to agent channel
  * 10MB maximum for all protobuf messages (result, heartbeat, error)
  * Prevents DoS attacks via oversized payloads

Medium Priority Fixes:
- Add GraphQL query depth limits (max_depth: 10)
  * Prevents DoS from deeply nested queries
  * Complements existing complexity limits

Code Quality:
- Refactor agent channel handlers to reduce nesting depth
  * Extract message processing into separate private functions
  * Fixes Credo warnings about excessive nesting
  * Improves code readability and maintainability

Files changed:
- lib/towerops_web/controllers/health_controller.ex
- lib/towerops_web/controllers/api/account_data_controller.ex
- lib/towerops_web/controllers/api/v1/mib_controller.ex
- lib/towerops/mobile_sessions/mobile_session.ex
- lib/towerops_web/channels/agent_channel.ex
- lib/towerops_web/controllers/error_json.ex
- lib/towerops_web/router.ex
- CHANGELOG.txt
- priv/static/changelog.txt
- test/towerops_web/controllers/health_controller_test.exs
- test/towerops_web/controllers/error_json_test.exs

All 7,424 tests passing.
2026-03-05 13:08:10 -06:00
ed4fce49a4
fix: ensure alerts table columns exist
- Add idempotent migration to ensure severity, notification_sent, etc. columns exist
- Fixes production issue where migration was marked as run but columns weren't created
- Uses IF NOT EXISTS to safely add missing columns without errors
- Includes all columns and indexes from ExtendAlertsForChecks migration
2026-03-05 12:41:56 -06:00
e0757fcd6a
feat: add LLDP topology discovery support from agent
Integrates LLDP neighbor discovery from towerops-agent into Phoenix:

- Updated protobuf definitions with LldpTopologyResult and LldpNeighbor messages
- Regenerated Elixir protobuf code (agent.pb.ex) with LLDP_TOPOLOGY job type
- Added validator for LLDP topology results with neighbor and management address validation
- Added AgentChannel handler for "lldp_topology_result" events
- Added Topology.upsert_neighbor/3 public API for storing agent-discovered neighbors
- Stores LLDP neighbors in device_neighbors table via validated protobuf messages

Agent changes were committed separately in towerops-agent repo (commit b6f60c8).

Files modified:
- priv/proto/agent.proto: Added LLDP message definitions
- lib/towerops/proto/agent.pb.ex: Regenerated from proto file
- lib/towerops/agent/validator.ex: Added validate_lldp_topology_result/1
- lib/towerops_web/channels/agent_channel.ex: Added lldp_topology_result handler
- lib/towerops/topology.ex: Added upsert_neighbor/3 public wrapper
2026-03-05 11:16:28 -06:00
7d73193dc6
docs: update changelog for LLDP topology discovery 2026-03-05 10:54:53 -06:00
e13eb3bbce
feat: implement LLDP topology discovery via SNMP
Implements Phase 1 of network topology discovery using LLDP (Link Layer
Discovery Protocol) via SNMP, inspired by concepts from lldp2map.

New Features:
- LLDP-MIB walker for discovering network neighbors via SNMP
- Database schema for storing neighbor relationships
- Functions to discover, store, and query device neighbors
- Automatic device linking when neighbors are found in database

Components Added:
- Migration: device_neighbors table with unique constraint on
  (device_id, local_port, neighbor_name)
- Schema: Towerops.Topology.DeviceNeighbor for neighbor relationships
- Module: Towerops.Topology.Lldp for SNMP LLDP-MIB walking
- Functions: discover_lldp_neighbors/1, list_lldp_neighbors/1,
  remove_stale_lldp_neighbors/1 in Topology context

LLDP-MIB OIDs:
- 1.0.8802.1.1.2.1.3.3.0 (lldpLocSysName)
- 1.0.8802.1.1.2.1.3.7.1.4 (lldpLocPortDesc)
- 1.0.8802.1.1.2.1.4.1.1.7 (lldpRemPortId)
- 1.0.8802.1.1.2.1.4.1.1.8 (lldpRemPortDesc)
- 1.0.8802.1.1.2.1.4.1.1.9 (lldpRemSysName)
- 1.0.8802.1.1.2.1.4.2.1.3 (lldpRemManAddr)

CI Fix:
- Updated config/test.exs to use DATABASE_URL from environment in CI
- Fixes "connection refused to localhost:5432" errors in CI builds
- Falls back to localhost config for local development

Code Quality:
- Refactored LLDP module to reduce cyclomatic complexity
- Extracted helper functions to improve readability
- Used 'with' statement to reduce nesting in parse_management_address
- All Credo checks passing

Next Steps (Phase 2):
- Background worker for automated topology discovery
- Topology visualization in LiveView
- Recursive discovery from seed devices
2026-03-05 10:53:57 -06:00
29b23fb06c
docs: update changelogs for reliability audit completion
- Added entries for organization_id alert optimization
- Added entries for cascade delete safety improvements
- Added entry for sensor state validation
- Updated user-facing changelog with performance improvements
- All 23 bugs from reliability audit now addressed
2026-03-05 09:34:57 -06:00
31cccf97b9
perf: add organization_id to alerts for fast single-table queries
- Denormalize organization_id from device to alerts table
- Add composite indexes on (organization_id, alert_type, resolved_at)
- Optimize list_organization_active_alerts to use single-table query
- Eliminates expensive 3-table joins (alerts → device → site)
- Auto-populate organization_id when creating alerts
- Backfill existing alerts in migration

Fixes bug #14 from reliability audit.
2026-03-05 09:32:43 -06:00
a230f1bfb5
fix: prevent silent data loss from cascade deletes on agent tokens
Changed foreign key constraints from CASCADE to RESTRICT for:
- agent_tokens.organization_id
- agent_assignments.agent_token_id

**Problem:**
Deleting an organization would cascade delete all agent tokens, which
would then cascade delete all agent assignments, causing silent data
loss without any audit trail.

**Solution:**
Use RESTRICT instead of CASCADE - this prevents deletion if there are
dependent records, forcing explicit cleanup and preventing accidental
data loss.

**Impact:**
- Organizations cannot be deleted if they have agent tokens
- Agent tokens cannot be deleted if they have active assignments
- User must explicitly clean up dependencies first

This provides better data integrity and prevents accidental data loss.

Fixes Medium Bug #15 from reliability audit.
2026-03-05 09:25:55 -06:00
1d1a686634
docs: update changelog and memory with bug fix details
- Added comprehensive CHANGELOG.txt entry documenting all 16 bug fixes
- Updated priv/static/changelog.txt with user-facing improvements
- Enhanced MEMORY.md with key learnings:
  * Task.yield_many race condition patterns
  * Enum.zip data corruption prevention
  * LiveView timer memory leak fixes
  * PubSub subscription cleanup
  * Pattern match error handling
  * Health check log silencing

Also fixed:
- Added error checking for sensor reading batch inserts
2026-03-05 08:55:59 -06:00
86a5dc728c
fix: comprehensive bug fixes from reliability audit
Critical Fixes (5):
- Fix Task.yield_many race condition causing data corruption in DevicePollerWorker
- Fix Enum.zip data corruption in SNMP Base Profile with length validation
- Fix missing Alert schema fields for check alerts (check_id, severity, etc.)
- Fix memory leaks from uncancelled LiveView timers in 4 components
- Fix PubSub subscription leak in device form credential testing

High Severity Fixes (3):
- Fix clock skew bug in needs_discovery? check with DateTime.diff clamping
- Fix nil crash in interface status display with proper nil handling
- Fix migration index names after equipment→devices table rename

Medium Severity Fixes (6):
- Fix race condition in device monitor worker (duplicate maintenance checks)
- Fix missing preload validation in devices.ex get_org_default_agent
- Fix broad rescue clause in alerts.ex with specific error handling
- Fix fire-and-forget notification tasks with try-catch error logging
- Fix LiveView state bleeding between tabs (assign_new → assign)
- Add catch-all handle_info callbacks to 3 LiveViews

Infrastructure:
- Silence health check endpoint logs (/health, /health/time)
- Add migration to fix equipment index names missed in rename

Files Changed: 16 files modified, 1 migration added
All changes compile successfully and are backward-compatible.
2026-03-05 08:53:30 -06:00
c231cbdf4c
docs: update changelog for log filtering improvements 2026-03-05 08:23:52 -06:00
212f9089e0
fix: use current_database() in TimescaleDB migration
Previous version used hardcoded 'towerops_dev' which doesn't exist in production.
Now uses current_database() to get the actual database name at runtime.

This fixes the crash: ERROR 3D000 (invalid_catalog_name) database "towerops_dev" does not exist
2026-03-05 07:54:20 -06:00
5e1f8d3ebe
docs: update changelog for TimescaleDB limit fix 2026-03-04 17:44:44 -06:00
a7ab2831b3
fix: increase TimescaleDB tuple decompression limit to unlimited
Fixes ERROR 53400 (configuration_limit_exceeded) during large interface
sync operations in device discovery.

The sync_interfaces operation was decompressing 146,603 tuples but the
default limit was 100,000. Setting to 0 (unlimited) removes this constraint.

Error occurred in: Towerops.Snmp.Discovery.sync_interfaces/2
2026-03-04 17:43:06 -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
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
b12d46c716 Add wireless client table discovery and improve subscriber matching
Poll connected client/SM tables from APs via vendor-specific SNMP:
- Cambium ePMP: cambiumAPConnectedSTATable
- Ubiquiti airOS: ubntStaTable
- MikroTik RouterOS: mtxrWlRtabTable

Wireless client MAC matches now take highest priority for
subscriber-to-AP mapping, giving accurate per-radio subscriber counts.
2026-02-16 11:16:52 -06:00
520f7b0c54 Add subscriber-to-AP matching via ARP + Gaiia integration
Matches Gaiia subscriber accounts to TowerOps devices using:
- ARP table IP matching (high confidence)
- ARP table MAC matching (high confidence)
- Site CIDR fallback (medium confidence)

Enables subscriber impact display on alerts and device pages.
2026-02-16 11:07:20 -06:00
2735c1a032 Add onboarding flow for new organizations 2026-02-16 10:14:45 -06:00
5b786aac08 Add maintenance windows and weather overlay (backend)
Maintenance Windows:
- maintenance_windows table with org/site/device scoping
- Full CRUD context (Towerops.Maintenance)
- device_in_maintenance?/1 checks device → site → org hierarchy
- Alert suppression in DeviceMonitorWorker (skips alerts during maintenance)
- maintenance_badge/1 helper for UI display

Weather Overlay:
- weather_observations + weather_alerts tables
- OpenWeatherMap free API client (lat/lon based)
- WeatherSyncWorker (Oban, 15min interval, respects rate limits)
- Observation parsing from OWM response format
- Correlation helpers: severe_weather_near?/2, weather_at/2
- Severity classification tuned for WISP ops (wind, rain, ice thresholds)
- Per-org latest observations query for dashboard
- 30-day automatic cleanup

Both features are backend-only — no UI yet.
2026-02-16 10:01:19 -06:00
b4c0407ee0 Add granular org roles: executive, technician
New role system:
- owner: full access + financials
- admin: full access + financials (except org deletion)
- executive: read-only + full financial visibility (MRR, revenue)
- technician: field ops (devices, sites, alerts) without financials
- member: legacy alias for technician (migrated)
- viewer: read-only, no financials

Financial data (MRR, revenue, billing) gated behind can_view_financials
assign in all LiveView templates: dashboard, alerts, trace, site show.

Includes migration to rename existing member roles to technician.
2026-02-15 17:40:53 -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
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
669dec5d89 proto: sync agent.proto with towerops-agent
- 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
2026-02-15 08:56:48 -06:00
75aece28a7 i18n: complete Spanish translations for all gettext domains 2026-02-14 17:45:19 -06:00
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
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
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
5df5c97c45 feat: add geographic location to sites with geocoding, address fields, and Leaflet.js network map 2026-02-14 13:07:14 -06:00
c37ae76ceb fix: device index uses device_role instead of nonexistent device_type, last_checked_at instead of last_seen_at; make TimescaleDB migrations conditional for dev/test 2026-02-14 10:13:03 -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
b07c322dc9 docs: update changelogs with navigation and visual polish 2026-02-13 19:18:36 -06:00