Commit graph

232 commits

Author SHA1 Message Date
eb9ba57f2e
fix(telemetry): handle Exq/Redis connection errors gracefully
Changed pattern matching to proper error handling in telemetry functions
to prevent crashes when Exq or Redis connections aren't available.

Issues fixed:
1. Exq.Api.queue_size/2 calls were using pattern matching {:ok, size} =
   which crashed with FunctionClauseError when Exq wasn't connected to Redis

2. Redix.start_link/1 and Redix.command/2 calls were using pattern matching
   which crashed when Redis wasn't available

Changes:
- publish_exq_stats: Use case statements for queue_size calls
- publish_exq_stats: Use with statement for processes/busy calls
- publish_redis_stats: Use with statement for all Redis operations

This allows the app to start and run even when Redis/Exq connections
aren't immediately available, which is especially important during
container startup and rolling deployments.
2026-01-18 11:25:26 -06:00
04daf191f7
fix(imports): handle DateTime microseconds and integer string floats
Fixed two import-related errors:

1. DateTime microseconds error when updating API token last_used_at:
   - Error: :utc_datetime expects microseconds to be empty
   - Fix: Use DateTime.truncate(:second) to remove microseconds
   - Location: lib/towerops/api_tokens.ex:179

2. Float parsing error for integer strings like "0":
   - Error: :erlang.binary_to_float("0") - not a textual representation of float
   - Fix: Use Float.parse/1 instead of String.to_float/1
   - Float.parse/1 handles both "0" and "0.0" correctly
   - Returns {float, remainder} on success, :error on failure
   - Location: lib/towerops/device_profiles/importer.ex:380

Both errors were causing profile imports to fail with crashes.
2026-01-18 11:23:43 -06:00
a6a2c1cbde
fix(telemetry): use is_nil instead of not for Process.whereis check
The `not` operator in Elixir only works with booleans, not nil values.
When Exq process doesn't exist, Process.whereis(Exq) returns nil, causing
`:badarg` error when used with `not`.

Error:
  Class=:error
  Reason=:badarg
  {:erlang, :not, [nil], [error_info: %{module: :erl_erts_errors}]}

Changed from:
  not Process.whereis(Exq)

To:
  is_nil(Process.whereis(Exq))

This properly checks if the process exists without causing runtime errors.
2026-01-18 11:14:15 -06:00
dcb875ea8c
fix(ui): fix API token success modal z-index issue
The token success modal was missing z-index classes that caused the modal
content to appear behind the grey backdrop. Users could only see the
checkmark icon and couldn't interact with the modal properly.

Changes:
- Added z-0 class to backdrop div
- Added relative z-10 classes to span and modal content div
- Added unique ID to modal (token-success-modal)
- Added unique ID to modal title (token-success-modal-title)

This matches the z-index structure used in the "add token" modal.
2026-01-18 11:10:29 -06:00
8e38e19510
fix duplicate modal-title IDs in user settings 2026-01-18 11:01:05 -06:00
5f8041dcc3
fix API token creation and add raw ICMP ping to Rust agent
API token creation fix:
- Handle both nested (%{"token" => %{...}}) and flat params
- Support URL-encoded form data from LiveView
- Add validation for required name and organization_id fields

Rust agent raw ICMP ping implementation:
- New ping module with async ICMP echo request/reply
- Build ICMP packets manually with proper checksum calculation
- Use socket2 for raw socket creation (requires CAP_NET_RAW)
- Parse both raw ICMP and IP-wrapped responses
- Include unit tests for checksum and packet building
- Dependencies: socket2 v0.5, rand v0.8
- Compiles successfully with release profile optimizations
2026-01-18 10:58:46 -06:00
f530c1b9a6
fix AutoDismissFlash hook order and implement raw ICMP ping
AutoDismissFlash hook fix:
- Move hook definitions before LiveSocket initialization
- Ensures hooks are defined when referenced in LiveSocket constructor
- Remove duplicate Hooks object

Raw ICMP ping implementation:
- Replace system ping command with pure Elixir implementation
- Use raw ICMP sockets for echo request/reply
- Build ICMP packets manually with proper checksum calculation
- Parse IP addresses using :inet.parse_address/1
- Handle both raw ICMP and IP-wrapped responses
- Requires CAP_NET_RAW capability in production containers
- Suitable for containerized environments without ping command
2026-01-18 10:56:04 -06:00
1b4b7f1773
fix user deletion bug and implement timezone support
User deletion bug:
- Store client IP in socket assigns during mount
- Access stored IP during delete event instead of connect_info
- Fixes RuntimeError when attempting to delete users

Timezone support:
- Add timezone offset maps for common timezones (no external deps)
- Implement shift_timezone/2 helper using DateTime.add/3
- Add timezone-aware format_datetime/2, format_date/2, format_iso8601/2
- Keep backwards-compatible 1-arity versions defaulting to UTC
- Add comprehensive test coverage for timezone conversions
- All tests passing (23 tests, 0 failures)

Note: Timezone offsets are standard time only (no DST handling)
Common timezones supported: America/New_York, America/Los_Angeles,
Europe/London, Europe/Paris, Asia/Tokyo, Asia/Shanghai, Australia/Sydney
2026-01-18 10:51:11 -06:00
b781246abd
convert API token modal to LiveView and add timezone support
- Replace JavaScript modal handlers with LiveView event bindings
- Add phx-click events for show_add_token_modal and cancel_add_token
- Convert plain HTML form to LiveView form with phx-submit
- Remove outdated window.liveSocket.execJS JavaScript code
- Add timezone parameter support to TimeHelpers functions:
  - format_datetime/2 now accepts timezone and shifts DateTime
  - format_date/2 now accepts timezone and shifts DateTime
  - format_iso8601/2 now accepts timezone and shifts DateTime
- All helper functions fallback to UTC if timezone is invalid or nil
2026-01-18 10:46:43 -06:00
243c687d35
fix all credo issues and add flash auto-dismiss
- Replace TODO comment with "Future enhancement" note
- Alias nested DeviceProfiles.Importer module
- Replace expensive length/1 check with empty list comparison
- Extract nested logic into helper functions to reduce complexity:
  - import_sensor_with_states for sensor import
  - walk_sensor_oid and walk_processor_oid for SNMP walks
  - apply_processor_precision and determine_processor_status
  - check_device_health for device monitoring
- Add auto-dismiss functionality to flash messages (30 second timeout)
- Implement AutoDismissFlash LiveView hook in TypeScript
2026-01-18 10:43:23 -06:00
90ae39091c
refactor user settings to use tab navigation
- Add tab navigation UI with Personal, Account, Security, API, Notifications tabs
- Add active_tab state management in LiveView
- Add switch_tab event handler
- Show only active tab content (like Tailwind Plus template)
- Remove section prefixes since each is now on separate tab
- Default to Personal tab on mount
2026-01-18 10:35:29 -06:00
f941e13f0d
add user profile settings with name, avatar, and timezone
- Add migration for name, avatar_url, timezone fields
- Update User schema with profile_changeset
- Add Accounts context functions for profile updates
- Reorganize settings page into sections:
  * Personal Information (name, avatar, timezone)
  * Account (email, password)
  * API (tokens)
  * Notifications (mobile devices)
  * Security (passkeys)
- Avatar URL accepts external URLs (Gravatar, etc)
- Timezone selector with common zones
2026-01-18 10:32:23 -06:00
712dbf25dd
increase sudo mode timeout to 12 hours 2026-01-18 10:25:17 -06:00
3cc69c9250
feat: add Horde for cluster-aware distributed supervision
Prevents duplicate polling in multi-node production environment:
- Replace DynamicSupervisor with Horde.DynamicSupervisor (distributed)
- Replace Registry with Horde.Registry (cluster-aware)
- Each device monitor runs on exactly ONE node across the cluster
- Each SNMP poller runs on exactly ONE node across the cluster
- Automatic failover when nodes go down
- Automatic rebalancing when nodes join/leave

Architecture:
- Horde.Registry for process naming (distributed lookup)
- Horde.DynamicSupervisor for DeviceMonitor processes
- Horde.DynamicSupervisor for PollerWorker processes
- Task.Supervisor remains local per-node for parallel operations
- members: :auto enables automatic cluster discovery via libcluster

Production impact:
- 2 replicas = devices split ~50/50 across both pods
- No duplicate SNMP polls to same device
- If pod dies, surviving pod takes over all devices
- When new pod starts, devices rebalance automatically

Dependencies added:
- horde ~> 0.9.0 (distributed supervisor/registry)
- delta_crdt (Horde dependency for CRDT-based state)
- libring (Horde dependency for consistent hashing)
2026-01-18 10:14:50 -06:00
fed1b3ecfc
feat: parallelize SNMP polling with Task.Supervisor and async streams
Architecture improvements:
- Add Task.Supervisor (PollerTaskSupervisor) to supervision tree for parallel operations
- Parallelize device-level operations: sensors, interfaces, changes, neighbors run concurrently
- Parallelize sensor polling within device using Task.async_stream (max 10 concurrent)
- Parallelize interface polling using Task.async_stream (max 10 concurrent)
- Parallelize interface changes checking using Task.async_stream (max 10 concurrent)

Benefits:
- Device with 50 sensors: ~5x faster (10 parallel vs sequential)
- Device with 24 interfaces: ~2.4x faster (10 parallel vs sequential)
- 4 device operations (sensors/interfaces/changes/neighbors): run concurrently
- Proper supervision and timeout handling with Task.Supervisor.async_nolink
- Graceful failure handling with Task.yield_many

Example: Device with 50 sensors + 24 interfaces:
- Before: ~74 seconds (50 + 24 sequential)
- After: ~7.4 seconds (5 + 2.4 parallel + concurrent operations)
2026-01-18 10:09:52 -06:00
0816459335
fix: use SNMP poll status for device health checks and improve Exq startup
- Use SNMP poll success as health indicator for SNMP-enabled devices
- Fall back to ICMP ping only for non-SNMP devices or stale SNMP data
- Add connection retry logic to Exq with backoff
- Add better error logging for ping permission issues
- Resolves issue where devices showed as down despite successful SNMP polling

Root cause: Container runs as non-root (UID 65534) without ICMP permissions,
so ping fails even though SNMP (UDP) works. Now using SNMP poll timestamps
to determine device status when SNMP is enabled.
2026-01-18 10:05:12 -06:00
a85085c3ed
fix: prevent telemetry errors when Exq is not running
- Check if Exq process is running before attempting to collect metrics
- Change rescue to catch to handle process exits (:noproc)
- Prevents error logs in environments where Exq is not configured
2026-01-18 10:00:14 -06:00
9b613f7a40
feat: update UI to Tailwind Plus design system and fix auth redirects
- Update user settings page to use Tailwind Plus 3-column grid layout
- Add API token management section to user settings
- Implement organization name display in navigation menu
- Update table component to use Tailwind Plus styling across all tables
- Update flash notifications with Tailwind Plus design
- Update dropdown menus and select inputs with custom chevron styling
- Fix authentication redirect to return to original page after login
- Preserve user_return_to session key across session renewal
- Add store_return_to_for_liveview plug to browser pipeline
- Add profiles.json to gitignore
2026-01-18 09:56:49 -06:00
56093bb493
refactor: use API token auth for profile imports instead of session cookies
Changes profile import endpoint to use standard API token authentication:

API Token Changes:
- Add user_id to api_tokens table (tracks who created the token)
- Update ApiTokens.verify_token/1 to return user along with org_id
- Update ApiAuth plug to assign current_user from token

Profile Import Changes:
- Move endpoint from /api/v1/admin/profiles/import to /api/v1/profiles/import
- Check user.is_superuser in controller instead of using RequireSuperuser plug
- Use api_v1 pipeline (Bearer token auth) instead of browser session
- Update documentation to show API token usage

Security:
- Only API tokens created by superusers can import profiles
- Returns 403 Forbidden if token user is not a superuser
- Logs import attempts with user email for audit trail

This provides a consistent API experience using Bearer tokens
instead of requiring browser session cookies.
2026-01-18 09:30:21 -06:00
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