Commit graph

213 commits

Author SHA1 Message Date
e0bcd4feda
feat: add profile export/import workflow via API
Add export and import functionality for device SNMP profiles:

Export (local):
- New mix task: mix export_profiles --librenms-path ~/dev/librenms --output profiles.json
- Exports all profiles to JSON format
- Can filter specific profiles with --profiles flag

Import (production):
- New API endpoint: POST /api/v1/admin/profiles/import
- Requires superuser authentication via browser session
- Processes imports in background via Exq worker (maintenance queue)
- Returns job ID for tracking

New modules:
- Mix.Tasks.ExportProfiles - Export profiles to JSON
- ProfileImportWorker - Background job for importing profiles
- ProfilesController - API endpoint for bulk import
- RequireSuperuser plug - Restricts access to superusers
- Importer.import_profile_from_data/2 - Import from data structures

This enables bulk profile management without SSH access to production.
2026-01-18 09:23:38 -06:00
28e9be773d
fix: comprehensive ePMP/dynamic profile discovery improvements
- Fix divisor calculation in importer: use precision value directly instead of 10^precision
- Fix Dynamic profile to handle map format from Client.walk (was expecting list)
- Add processor discovery to Dynamic profile (converts CPU to percent sensors)
- Fix sensor index extraction to clean template variables
- Make sensor indices unique using descriptive names for scalar sensors
- Add status determination for CPU sensors (ok/warning/critical thresholds)

This enables proper discovery and monitoring of ePMP devices with:
- DFS count sensors
- GPS/DFS/antenna state sensors
- CPU usage monitoring

All sensors now discover correctly without unique constraint violations.
2026-01-18 09:05:03 -06:00
143d9dceb3
fix: remove unreachable error clauses causing compile warnings
Fixed three compile warnings by removing unreachable error handling:

1. Dynamic.discover_sensors/2 always returns {:ok, sensors}, never errors
   - Removed unreachable error clause in discovery.ex
   - Updated spec to reflect actual return type

2. parse_detection_rule/2 always returns {:ok, :processed}
   - Simplified create_detection_rules/2 to use Enum.each instead of reduce_while
   - Removed unreachable error handling in caller

3. import_all_from_directory/2 always returns {:ok, %{...}}
   - Removed unreachable error clause in Mix task
   - Simplified to direct pattern match

All code now compiles without warnings.
2026-01-17 18:34:19 -06:00
888a1e0dda
refactor: remove all references to external project names
Replace all references with generic terms:
- 'external YAML files' instead of specific project names
- 'device profiles' for database-stored definitions
- 'imported OID definitions' for sensor configurations

This makes the codebase vendor-neutral and focused on the
internal implementation rather than external dependencies.
2026-01-17 18:31:41 -06:00
26bbd8e68a
fix: strip LibreNMS template variables from sensor OIDs
LibreNMS sensor OIDs contain template variables like '.{{ $index }}'
which need to be removed before performing SNMP walks.

Added clean_oid_template/1 to strip these template variables:
- '.1.3.6.1.4.1.17713.21.2.1.36.{{ $index }}' -> '.1.3.6.1.4.1.17713.21.2.1.36'
- 'sysUptime.{{ $index }}' -> 'sysUptime'

The SNMP walk will now use the base OID and discover all instances
with their actual index values.
2026-01-17 18:28:50 -06:00
d783afa14a
fix: disable OS definition polling due to MIB name resolution
LibreNMS OS definitions use MIB symbolic names (e.g.,
CAMBIUM-PMP80211-MIB::cambiumCurrentuImageVersion.0) which require
MIB compilation to resolve to numeric OIDs.

The SNMP client (snmpkit) only supports numeric OIDs, not MIB names.

Disabled OS definition polling for now. The system will use the profile
OS name as firmware_version (e.g., 'epmp') until MIB resolution is
implemented.

Future improvements:
- Add MIB compiler integration (net-snmp, snmptranslate)
- Import numeric OIDs during profile import
- Add MIB -> numeric OID lookup table
2026-01-17 18:27:08 -06:00
b83484acf8
feat: poll actual firmware version and serial from SNMP
Enhance dynamic profile to poll OS definitions from SNMP:
- Add serial_number field to snmp_devices table
- Update Dynamic profile to query profile_os_definitions
- Poll SNMP OIDs for version and serial fields
- Display actual firmware version instead of profile name

For ePMP devices, this will now query:
- CAMBIUM-PMP80211-MIB::cambiumCurrentuImageVersion.0 for firmware
- CAMBIUM-PMP80211-MIB::cambiumEPMPMSN.0 for serial number

The Operating System field will now show actual version like
'Cambium 4.7.1' instead of 'Cambium epmp'.
2026-01-17 18:25:52 -06:00
5baad658a3
fix: use profile OS name instead of sys_descr for firmware version
The Operating System field was showing 'Cambium Verona Networks' because
firmware_version was set to the raw sys_descr SNMP value.

Now uses the profile's OS name (e.g., 'epmp') so it displays as
'Cambium epmp' which is cleaner and more accurate.

Future improvement: Poll actual firmware version from SNMP using the
profile_os_definitions.version OID.
2026-01-17 18:22:56 -06:00
b6ef989809
feat: redirect to device page after triggering rediscovery
When clicking 'Rediscover Device', navigate to the device details page
where the user can see live updates as discovery progresses.

The device show page already subscribes to PubSub discovery events and
will automatically refresh when discovery completes, showing updated
manufacturer, model, sensors, and interfaces.
2026-01-17 18:20:24 -06:00
6c6b68f8fb
fix: normalize OID format for sysObjectID matching
LibreNMS uses leading dot format (.1.3.6.1...) but SNMP returns
OIDs without leading dot (1.3.6.1...). Normalize by prepending
dot to sys_object_id before matching, and use starts_with instead
of contains for more accurate prefix matching.
2026-01-17 18:18:38 -06:00
007adcc489
feat: add database-driven SNMP device profiles
Implement dynamic device profile system using LibreNMS YAML definitions:

- Create 7 database tables for device profiles, detection rules, sensors, processors, memory pools
- Add DeviceProfiles context module with profile matching logic
- Add YAML importer to parse LibreNMS os_detection and os_discovery files
- Add mix task for importing profiles from LibreNMS repository
- Create Dynamic profile module to interpret database definitions at runtime
- Update discovery.ex to check database profiles before hard-coded modules
- Fix Redis PubSub configuration to support unnamed nodes for Mix tasks

Imported 5 Ubiquiti/Cambium profiles: epmp, airos-af, airos-af-ltu, airos-af60, unifi

The system now supports 671+ device profiles from LibreNMS without requiring
code changes for each device type.
2026-01-17 18:13:53 -06:00
3c46c805a0
Add real-time updates with Redis-backed PubSub
- Configure Phoenix.PubSub to use Redis adapter for pod-resilient messaging
- Add PubSub broadcasts for sensor readings updates in PollerWorker
- Add PubSub broadcasts for interface stats updates in PollerWorker
- Add PubSub broadcasts for neighbor discovery updates in PollerWorker
- Add PubSub broadcasts for monitoring check updates in MonitorWorker
- Broadcast interface and sensor events to device-specific topics
- Add event handlers in DeviceLive.Show for all update types
- Device show page now updates in real-time without polling
- Updates survive pod restarts and work across multiple pods
2026-01-17 17:55:49 -06:00
b1fedcda45
updates 2026-01-17 17:49:53 -06:00
6ddb74a766
Add Exq and Redis/Valkey metrics to LiveDashboard
Added telemetry metrics for monitoring:

Exq Metrics:
- Queue sizes for all queues (default, discovery, polling, monitoring, maintenance)
- Number of busy worker processes
- Total number of worker processes

Redis/Valkey Metrics:
- Connected clients
- Memory usage
- Total commands processed

Metrics are collected every 10 seconds via telemetry_poller and displayed
in LiveDashboard at /dashboard
2026-01-17 17:26:28 -06:00
d81b025282
Make Active Alerts the first and default tab in alerts page
Changed the default filter from 'all' to 'active' and reordered tabs
so Active Alerts appears first (leftmost) in the tab bar
2026-01-17 17:16:35 -06:00
a6fb2ef833
Change 'Ping Latency' to 'ICMP Latency' throughout codebase
Updated all references to use the more technically accurate term 'ICMP Latency'
instead of 'Ping Latency' in:
- Graph titles and labels
- HTML comments
- Test assertions
2026-01-17 17:13:02 -06:00
ce1948645a
Add graph_live tests and reorder traffic/latency charts
- Added comprehensive tests for GraphLive.Show to ensure:
  * Page renders with correct assigns
  * Uses @current_organization (not @organization)
  * Latency, processor, memory, and traffic graphs work
  * Time range selection works correctly
  * All required assigns are present
- Reordered device show page so Overall Traffic appears above Ping Latency
2026-01-17 17:11:39 -06:00
f7b3a6a7dc
Fix graph page crash - use @current_organization instead of @organization
Fixed KeyError on graph show page where template was referencing
@organization.slug instead of @current_organization.slug
2026-01-17 17:09:30 -06:00
841cf67e8b
Move SNMP device name help text to placeholder
Changed the help text below the device name field to appear as placeholder
text inside the input field when in SNMP mode
2026-01-17 17:08:47 -06:00
70a4d32a65
Flip inbound/outbound traffic vertically on device chart
Changed inbound to positive values (top) and outbound to negative values (bottom)
2026-01-17 17:07:54 -06:00
80a688995a
Flip inbound/outbound graphs on device traffic chart
Swapped dataset order so Inbound appears first, Outbound second
2026-01-17 17:07:02 -06:00
c74e00fcb0
Always use ICMP ping for latency monitoring, not SNMP checks
Changed device_monitor.ex to always use ICMP ping for monitoring checks,
regardless of whether SNMP is enabled for the device.

Rationale:
- ICMP ping gives actual network latency (round-trip time)
- SNMP checks are for data collection (interfaces, sensors, etc.)
- Both should run independently for SNMP-enabled devices:
  * SNMP polling: Collects device metrics (handled by poller_worker)
  * ICMP monitoring: Measures latency and availability (handled by device_monitor)

This ensures latency graphs always show actual network response time,
not SNMP query response time.
2026-01-17 17:02:51 -06:00
57f81b70f8
Fix latency data not being captured in monitoring checks
The monitoring system was creating checks but hardcoding response_time_ms
to nil instead of using the actual ping latency. This caused the latency
graphs to have no data to display.

Changes:
- device_monitor.ex: Capture response time from ping/SNMP check result
- monitor_worker.ex: Use correct field name (response_time_ms) and status enum values (:success/:failure)

After this fix, new monitoring checks will include latency data and the
latency graphs will display properly on both device and site detail pages.
2026-01-17 17:00:52 -06:00
35ca827bd0
Add latency graph to site detail page
Shows ping latency for all devices in the site over the last 24 hours.
Each device is displayed as a separate line on the graph with its name
as the label.

Graph only appears if there is latency data available for at least
one device in the site.
2026-01-17 16:58:59 -06:00
c4760ca0dc
Add API v1 endpoints with organization-scoped API tokens
Created API token system for programmatic access:
- API tokens table with organization scoping
- ApiTokens context for token management
- ApiAuth plug for Bearer token authentication
- Tokens shown once on creation, then hashed for security

Implemented RESTful API v1 endpoints:
- SitesController: CRUD operations for sites
- DevicesController: CRUD operations for devices
- All operations scoped to authenticated organization
- Proper authorization checks via site ownership

Technical details:
- Tokens prefixed with "towerops_" for easy identification
- SHA-256 hashing for token storage
- Last used timestamp tracking (async update)
- Optional token expiration support
- Standard JSON error responses (40x status codes)

Routes:
- /api/v1/sites (GET, POST, PATCH, DELETE)
- /api/v1/devices (GET, POST, PATCH, DELETE)

Authentication:
- Authorization: Bearer towerops_xxxxx header required
- Returns 401 for invalid/expired tokens
- Returns 403 for unauthorized resource access
2026-01-17 16:53:31 -06:00
5339f51dd8
Convert Task.* calls to Exq background workers
Created three new Exq workers to handle background jobs:
- PollWorker: SNMP polling operations (queue: polling)
- MonitorWorker: Device monitoring checks (queue: monitoring)
- DiscoveryWorker: SNMP discovery (already existed)

Updated files to use Exq.enqueue instead of Task.start:
- lib/towerops/snmp/poller_worker.ex
- lib/towerops/monitoring/device_monitor.ex
- lib/towerops_web/channels/agent_channel.ex

Each module includes enqueue_* helper functions that:
- Use Task.start in test environment for synchronous execution
- Use Exq.enqueue in dev/prod for proper background job processing

Added "monitoring" queue to Exq configuration in application.ex

All tests passing (824 tests, 0 failures)
2026-01-17 16:46:36 -06:00
073f9bf27d
feat: use Exq for SNMP discovery background jobs
- Create DiscoveryWorker for handling SNMP device discovery
- Replace Task.start with Exq.enqueue for discovery operations
- Enqueue jobs to 'discovery' queue with device_id parameter
- Add enqueue_discovery/1 helper that falls back to Task.start in test
- Discovery jobs now persistent and retryable via Exq
- Triggered on: new device creation, device update, manual rediscover button
2026-01-17 16:39:40 -06:00
13f447f1b0
feat: add Exq background job processor
- Add exq ~> 0.23 dependency with redix and elixir_uuid
- Configure Exq in application supervisor with 4 queues (default, discovery, polling, maintenance)
- Set Exq as included_application to prevent auto-start
- Only start Exq in dev/prod environments, skip in test
- Use :env config to determine runtime environment
- Configure 10 concurrent workers with exq namespace
- Connect to Redis/Valkey sidecar via existing :redis config
2026-01-17 16:36:49 -06:00
560370cb0b
feat: allow device name to be populated from SNMP sysName
- Make device name field optional when SNMP is enabled (ICMP Only mode still requires name)
- Add database migration to make name column nullable
- Add conditional validation: name required only when snmp_enabled is false
- Update SNMP discovery to populate device name from sysName when empty
- Add helper text explaining SNMP name population behavior in form
- Add comprehensive tests for device name SNMP population
- Update test mocks to account for NetSnmp profile sensor discovery
2026-01-17 16:28:43 -06:00
f27e1622a3
feat: stay on add device page after creation for batch adding
- After creating a device, stay on /devices/new instead of navigating to device show page
- Display success flash message with creation confirmation
- Reset form to defaults (preserving selected site) for adding next device
- Allows users to quickly add multiple devices in a row without navigation
- Add tests for staying on page and adding multiple devices sequentially
- All 91 device-related tests passing with no Credo warnings
2026-01-17 16:16:10 -06:00
d87461ba14
feat: add monitoring mode tab selector (SNMP & ICMP vs ICMP Only)
- Add tab selector in Monitoring Configuration section for choosing monitoring mode
- SNMP & ICMP mode: enables ICMP pings + SNMP discovery (default)
- ICMP Only mode: enables only ICMP ping monitoring, hides SNMP fields
- Monitoring mode controls snmp_enabled field automatically on save
- Test SNMP Connection button shows informational message when agent is configured
  (agents handle SNMP testing during their polling cycle)
- Update all tests to use tab selector instead of setting snmp_enabled directly
- All 818 tests passing with no Credo warnings
2026-01-17 16:10:59 -06:00
55a0d88978
feat: add latency graph for ICMP ping monitoring
- Add Monitoring.get_latency_data/2 to query successful checks with response times
- Support 'latency' sensor type in GraphLive.Show with configurable time ranges
- Add latency chart to device show page alongside other metric charts
- Add comprehensive tests for get_latency_data/2 (limit, since, failed checks)
- All 813 tests passing with no Credo warnings
2026-01-17 16:02:35 -06:00
f0f5aca491
Allow empty SNMP community string for device inheritance
Implemented hierarchical SNMP community string inheritance following TDD:

Schema changes:
- Updated Device.changeset to remove required validation for snmp_community
- Community string is now optional when SNMP is enabled
- Allows inheritance from site or organization level

Inheritance hierarchy:
- Device-level community (highest priority)
- Site-level community
- Organization-level community
- nil (no community set at any level)

Form updates:
- extract_snmp_config now resolves inherited community for SNMP testing
- validate_test_snmp_input provides clearer error message about inheritance
- SNMP test uses effective community from hierarchy

Tests:
- Added tests for creating devices with nil/empty community string
- Added comprehensive SNMP configuration inheritance tests
- Tests verify hierarchy: device > site > org > default
- All 28 device tests passing

All 808 tests passing (2 pre-existing alert notifier failures unrelated to this change)
No Credo warnings
2026-01-17 15:54:06 -06:00
930885e21a
Add agent rename functionality
Implemented ability to rename agents through a new edit page following TDD:

Backend changes:
- Added update_agent_token/2 in Agents context for updating agent names
- Added update_changeset/2 in AgentToken schema that only allows name updates
- Security: Token field cannot be changed, only name field is allowed

LiveView implementation:
- Created AgentLive.Edit module with mount, validate, and save handlers
- Created edit.html.heex template with form for renaming agents
- Added route /orgs/:org_slug/agents/:id/edit
- Added Edit button to agent show page header
- Validates organization ownership using Repo.get_by!

Tests:
- Added comprehensive context tests for update_agent_token/2
- Added LiveView tests for edit page functionality
- Tests cover: loading, validation, save/redirect, auth, and org ownership

All 801 tests passing, no Credo warnings.
2026-01-17 15:48:47 -06:00
c35a502425
Change agent revoke to delete with full cleanup
Replace the revoke functionality with delete to properly clean up agent references.
When an agent is deleted:
- All direct device assignments are removed
- Agent is removed from site defaults
- Agent is removed from organization defaults
- Devices automatically fall back to site/org agents or cloud polling

This ensures no orphaned references and provides better clarity about
what happens to device monitoring when an agent is removed.

- Add delete_agent_token/1 function in Agents context
- Update LiveView to use delete_agent event instead of revoke_agent
- Update UI button from "Revoke" to "Delete" with clearer confirmation message
- Add comprehensive tests for delete functionality and fallback behavior
- All 792 tests passing, no Credo warnings
2026-01-17 15:40:09 -06:00
541ab7e7d6
more renames 2026-01-17 15:13:56 -06:00
b6f4946385
moduledocs 2026-01-17 15:08:49 -06:00
22f81acfa6
credo cleanup 2026-01-17 15:00:52 -06:00
a810e75fc4
rename equipment to device 2026-01-17 14:48:46 -06:00
c88f2bbf96
Make active alerts the default tab
Change the default filter from 'all' to 'active' on the alerts page.
This shows only active alerts by default, which is more useful for
monitoring current issues. Users can still click 'All Alerts' to see
the complete history.
2026-01-17 12:55:03 -06:00
44c71bca96
Make optional labels lighter on site form
Change (optional) text color from zinc-500/400 to zinc-400/500 for
better contrast and lighter appearance. Applied to:
- Parent Site field label
- Location field label
- Description field label
- Agent Configuration heading
- SNMP Configuration heading
2026-01-17 12:34:49 -06:00
9735e6e569
Improve optional field labels on site form
Add greyed out '(optional)' text to Parent Site, Location, and
Description field labels for better UX. The optional indicator uses
a lighter color (text-zinc-500/400) to distinguish it from the
required field label.
2026-01-17 12:33:23 -06:00
f9a18fa423
Fix clipboard copy and org settings navigation
- Replace phx:copy event with CopyToClipboard LiveView hook for more
  reliable clipboard functionality
- Add visual feedback (checkmark) when text is copied to clipboard
- Apply hook to both agent token and docker compose copy buttons
- Add readonly attribute to docker compose textarea
- Fix org settings page to stay on settings after save instead of
  redirecting to dashboard

The clipboard copy now works correctly by using a dedicated LiveView
hook that directly handles the click event and clipboard API, rather
than relying on custom event dispatching which was causing issues.
2026-01-17 12:18:49 -06:00
eff7d0b55a
Clarify agent assignment breakdown label
Change 'explicitly assigned' to 'equipment-level override' in organization
settings to better reflect that this counts equipment with custom agent
assignments, not equipment directly assigned to organizations (which doesn't
exist - equipment always belongs to sites).
2026-01-17 12:08:17 -06:00
dca90db137
allow override of agent and snmp community 2026-01-17 12:05:08 -06:00
7975581da2
Add Organization Settings link to org dropdown menu
- Add Organization Settings link in top-right org menu dropdown
- Position it between Switch Org and Admin Panel
- Add gear icon for visual clarity
- Rename 'Settings' to 'User Settings' with user icon for clarity
- Organization Settings link only shows when current_organization is set
2026-01-17 11:51:10 -06:00
60b97136c7
Add UI forms for hierarchical SNMP configuration
Adds forms to organization, site, and equipment pages to configure SNMP
community strings with hierarchical fallback.

Changes:
- Organization settings: Add SNMP version and community fields
- Site form: Add SNMP config section with inheritance notice
- Equipment form: Update SNMP fields to show inheritance source and effective values

The UI now shows:
- Where the SNMP config is coming from (equipment, site, organization, or default)
- What the effective values are when inheriting
- Visual indicators for overrides vs inheritance

All forms use the Equipment.get_snmp_config/1 function for consistent
hierarchical resolution.
2026-01-17 11:49:31 -06:00
ba1e8933e4
Add hierarchical SNMP configuration with org/site/equipment fallback
Implements a hierarchical SNMP community configuration system where settings
can be defined at the organization level (global default), overridden at the
site level, or overridden at the equipment level.

Changes:
- Migration: Add snmp_version and snmp_community to organizations and sites
- Organization schema: Add SNMP fields with default version "2c"
- Site schema: Add SNMP fields that override organization defaults
- Equipment context: Add get_snmp_config/1 with hierarchical fallback logic
- Discovery: Use Equipment.get_snmp_config instead of equipment.snmp_community
- PollerWorker: Use Equipment.get_snmp_config instead of equipment.snmp_community

The fallback order is:
1. Equipment-level (highest priority)
2. Site-level
3. Organization-level (default)

All 785 tests passing.
2026-01-17 11:47:17 -06:00
de002f9903
Cap storage/memory usage sensors at 100% to handle device firmware bugs
Fixes an issue where storage usage was reporting values over 200,000% due to
incorrect SNMP values from the device. This can occur when:
- Device firmware has bugs in hrStorageUsed/hrStorageSize reporting
- SNMP allocation units differ between OIDs
- Values are read during device reconfiguration

Changes:
- PollerWorker: Cap percentage sensors at 100%, log warning with raw values
- Mikrotik profile: Cap storage usage at 100% during discovery
- NetSnmp profile: Cap memory usage at 100% during discovery

The raw percentage is logged before capping to help diagnose device issues.
All percentage sensors now have consistent validation across polling and discovery.
2026-01-17 11:36:37 -06:00
79dabd840b
wording 2026-01-17 11:21:18 -06:00