- 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
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.
- 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
- 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
- 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
- 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
- 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
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
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.
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
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.
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
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.
- 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.
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).
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.
- Add compile-time @build_timestamp to Application module
- Create ToweropsWeb.TimeHelpers module for shared time formatting
- Update footer to display 'Last deployed Xm ago · YYYY-MM-DD HH:MM:SS UTC'
- Refactor AgentLive.Helpers to delegate to shared TimeHelpers
- Consolidate duplicate time formatting logic into single module
- Timestamp updates automatically on each build/deploy
- Change default agent image to gmcintire/towerops-agent:main
- Add Watchtower service to docker-compose instructions
- Configure 15-minute update check interval for development
- Add DOCKER_API_VERSION=1.44 to prevent API compatibility errors
- Remove deprecated docker-compose version field
- Create UserSettingsLive to replace UserSettingsController
- Convert email/password forms to LiveView with proper form handling
- Add mobile session management (toggle alerts, revoke devices)
- Add require_sudo_mode on_mount hook to UserAuth
- Create dedicated live_session for sudo mode routes in router
- Keep UserSettingsController for email confirmation via token only
- Add MobileSessions.get_session/1 function for test support
- Update tests to match LiveView behavior (password update redirects to login)
- Remove old controller tests for email/password updates (now in LiveView)
- Removed comment from Docker socket volume mount
- Auto-updates now work out of the box when users copy config
- Socket mount: /var/run/docker.sock:/var/run/docker.sock
Added two new functions to Snmp context:
- get_latest_sensor_readings_batch/1 - Loads latest readings for multiple sensors in one query
- get_latest_interface_stats_batch/1 - Loads latest stats for multiple interfaces in one query
Both use PostgreSQL DISTINCT ON to efficiently get only the latest record per ID.
Updated EquipmentLive.Show.load_snmp_data/1 to use batch functions:
- Equipment with 20 sensors: 20 queries → 1 query (20x reduction)
- Equipment with 10 interfaces: 10 queries → 1 query (10x reduction)
- Total improvement: 30 queries → 2 queries for typical equipment page load
This significantly improves page load performance for equipment with many sensors/interfaces.
Extracted 5 shared helper functions into ToweropsWeb.AgentLive.Helpers:
- agent_status/1 - Determines online/warning/offline/never status
- status_badge_class/1 - Returns Tailwind CSS classes for status badges
- status_dot_class/1 - Returns CSS classes for animated status dots
- format_last_seen/1 - Formats DateTime as human-readable time ago
- format_uptime/1 - Formats uptime seconds as days/hours/minutes
Removed duplicate code from:
- lib/towerops_web/live/agent_live/index.ex
- lib/towerops_web/live/agent_live/show.ex
Both modules now import the shared helpers, following DRY principle.
This reduces code duplication and makes status formatting consistent
across all agent-related pages.
1. Fixed Docker Compose example to include socket mount
- Added /var/run/docker.sock mount for auto-updates in agent modal
2. Show total equipment count with inheritance
- Display both direct and inherited equipment counts in agents table
- Shows breakdown: 'X total' with 'Y direct · Z inherited' details
3. Add visual status indicators with better CSS
- Added animated pulsing dots for online agents
- Color-coded status dots (green=online, yellow=warning, red=offline)
- Improved status badge styling
4. Create agent detail page with metrics
- New /agents/:id page showing comprehensive agent health
- 4 metric cards: Status, Equipment count, Last seen, Agent info
- Displays agent metadata (hostname, version, uptime)
- Lists all polling targets with assignment source badges
- Shows direct vs inherited equipment assignments
- Clickable agent names in table link to detail page
5. Add health endpoint to Rust agent
- Added /health HTTP endpoint on port 8080
- Returns JSON with agent status, version, uptime, metrics pending
- Uses lightweight tiny_http server
- Non-blocking background thread
- Ready for monitoring/health checks
- Add override warnings (amber) when site/equipment explicitly override defaults
- Show inherited agent names in organization and site forms
- Display equipment assignment breakdown in organization settings
- Make equipment boxes fully clickable on site page (remove View button)
- Add hover effects to equipment boxes for better UX
- Consistent icon usage across all assignment levels
The Docker agent now handles permissions automatically via the
entrypoint script, so users don't need to manually create the
data directory or run chown commands.
Reverted the UI back to the simple docker-compose example.
The Docker agent runs as non-root user (UID 1000) for security. When
mounting host directories, users must create the data directory with
proper permissions before starting the container.
Added clear instructions to:
1. Create the data directory: mkdir -p data
2. Set proper ownership: chown -R 1000:1000 data
3. Then use the Docker Compose configuration
This fixes the 'unable to open database file' error when starting
the agent.
- Added agent_docker_image config option (defaults to towerops/agent:latest)
- Updated agent UI modal to use configurable image URL
- Can override in runtime.exs or environment config for production
- Updated AGENT_NEXT_STEPS.md success criteria with completion status
- 8/12 success criteria now complete
- Added Stats module to agent LiveView index
- Load organization-wide health statistics on mount
- Display agent health, equipment counts, and assignment breakdown
- Refresh stats after agent creation/revocation
- All 10 agent LiveView tests passing
Add three-level agent assignment hierarchy (Equipment > Site > Organization)
allowing flexible agent deployment strategies. Agents now receive only equipment
assigned to them through direct assignment or inheritance from site/organization
defaults.
Key changes:
- Add agent_token_id to Sites table with migration
- Implement get_effective_agent_token/1 for hierarchical resolution
- Add list_agent_polling_targets/1 to return polling targets per agent
- Update API config endpoint to use hierarchical polling targets
- Add agent assignment UI to equipment, site, and organization forms
- Show agent source (direct/site/org/none) in equipment form
- Add equipment count column to agent list
- Update terminology from "poll from server" to "cloud polling"
Tests:
- Add 8 comprehensive tests for list_agent_polling_targets/1
- Add end-to-end test for hierarchical config endpoint
- All 775 tests passing
- Changed form layout from horizontal to vertical
- Agent name input now on its own line
- Create Agent and Cancel buttons on line below with gap-3 spacing
Security improvements to prevent potential issues:
1. Port Validation (normalize_port):
- Validate port range is 1-65535
- Use Integer.parse instead of String.to_integer to prevent crashes
- Return default port 161 for any invalid input
2. SNMP Test Validation (validate_test_snmp_input):
- Validate IP address format before allowing SNMP tests
- Require non-empty community string
- Prevent network scanning with invalid/missing IPs
- Return clear error messages for validation failures
These changes ensure user input is properly validated and prevent:
- Integer overflow/underflow with ports
- Process crashes from invalid input
- Unauthorized network scanning via SNMP test feature
- SNMP requests with missing credentials
Two issues were preventing impersonation from working correctly:
1. Templates were not passing current_scope to Layouts.authenticated,
so the impersonation banner never displayed.
2. fetch_current_scope_for_user was looking up the user from the
session token, which always pointed to the superuser. This caused
the superuser to see their own organizations and equipment instead
of the impersonated user's data.
Changes:
- Pass current_scope={@current_scope} to all Layouts.authenticated calls
- Store target_user_id in session during impersonation
- Fetch target user directly from database using target_user_id
- Update both fetch_current_scope_for_user (for controllers) and
mount_current_scope (for LiveViews) to properly handle impersonation
- Clean up target_user_id from session when impersonation ends
Now when a superuser impersonates a user, they correctly see:
- The impersonation banner at the top with exit link
- The target user's organizations and equipment
- All data scoped to the impersonated user