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
Extracted SNMP configuration extraction and connection testing
into separate helper functions, reducing cyclomatic complexity
from 11 to ~5.
- extract_snmp_config/1: Consolidates form data and params
- normalize_port/1: Handles port type conversion
- test_snmp_connection/1: Performs test and formats result
This makes the handle_event callback simpler and each concern
easier to test independently.
Refactored three highly complex functions to improve code maintainability:
1. PollerWorker.detect_sensor_changes (complexity 33 -> ~9):
- Extracted threshold checking logic into separate functions
- Split event creation into focused helper functions
- Reduced nesting by using pipe operator pattern
2. PollerWorker.detect_and_log_changes (complexity 16 -> ~5):
- Created separate functions for each interface change type
- Extracted event building logic into dedicated functions
- Improved readability with clear function names
3. EquipmentLive.Show.format_duration (complexity 13 -> ~5):
- Extracted time unit conversion into separate functions
- Simplified pluralization logic with pattern matching
- Reduced nested conditionals
These changes make the code easier to test, maintain, and understand
while preserving all existing functionality.
Replace expensive length/1 comparisons with direct list comparisons or Enum.empty?/1 checks (13 instances in source code and tests). Add module aliases for Phoenix.HTML.Form and Towerops.Accounts.Scope to reduce nested module references.
Changes:
- Replace length(list) > 0 with list != [] or refute Enum.empty?(list)
- Add Phoenix.HTML.Form alias in CoreComponents
- Add Towerops.Accounts.Scope alias in ConnCase test helper
This eliminates all credo warnings and software design suggestions.
Implement comprehensive admin interface allowing designated superusers to view all users and organizations, impersonate users for debugging, and perform administrative operations. All superuser actions are tracked in audit logs for compliance.
Features:
- Superuser authentication with dedicated admin routes at /admin
- User impersonation with session state preservation
- Admin dashboard with system statistics
- User and organization management interfaces
- Comprehensive audit logging with IP tracking
- Visual impersonation banner with exit capability
- Security controls preventing self-impersonation and superuser-to-superuser impersonation
Database:
- Add is_superuser boolean field to users table
- Create audit_logs table for tracking sensitive operations
- Set graham@mcintire.me as initial superuser
- Display storage sensors as a list similar to temperature/voltage
- Show individual disk/partition usage with current values
- Link to graph view from sensor names
- More consistent with other sensor displays
- Add new event types: sensor_threshold_warning, sensor_threshold_critical, sensor_threshold_normal, sensor_value_spike, sensor_value_drop
- Implement sensor change detection in PollerWorker
- Track threshold violations (warning/critical high/low)
- Detect value spikes/drops for percentage sensors (30% change)
- Track return to normal from threshold violations
- Store threshold config in sensor metadata
- Update sensor last_value and last_checked_at on each poll
- Broadcast sensor events via PubSub for real-time logging
- Replace grid of sensor cards with table format matching device info
- Display sensor name on left, value on right
- Make sensor names clickable links to graph detail page
- Remove generic "Sensors" section from overview
- Cleaner, more scannable layout for temperature and voltage sensors
- Create GraphLive.Show for detailed sensor graph visualization
- Add route for /equipment/:id/graph/:sensor_type endpoint
- Make all chart headers clickable with navigation to detail view
- Implement date range selector (1h, 6h, 12h, 24h, 7d, 30d)
- Fix chart rendering by destroying and recreating on data updates
- Fix duplicate data loading in LiveView event handlers
- Fix MikroTik profile typing warning for entity sensor discovery
- Add Chart.js integration for interactive sensor graphs
- Add processor, memory, storage, temperature, and voltage charts showing 24 hours of data
- Fix device information box to use natural height instead of stretching
- Update MikroTik profile to discover ENTITY-SENSOR-MIB sensors
- Support multiple sensor types per chart (celsius/temperature, volts/voltage)
- Use 24-hour time format in chart tooltips and x-axis labels
- Support auto-scaling y-axis for non-percentage metrics
- Add equipment_events table and Event schema for tracking device changes
- Implement automatic change detection for interface attributes during SNMP polling
- Detects operational status changes (up/down)
- Detects admin status changes
- Detects speed changes with warning severity for speed drops
- Detects MAC address changes
- Add Logs tab to equipment detail page with event display
- Fix temperature sensor display bug (was showing 4.3C instead of 43C due to double division)
- Remove response time tracking from monitoring checks and UI
- Move Rediscover Device button to equipment edit page
This fixes critical issues where SNMP sensor readings, interface stats, and
monitoring checks were not being saved to the database due to schema mismatches.
Database schema fixes:
- Recreate snmp_sensor_readings table with binary_id primary key
- Recreate snmp_interface_stats table with binary_id primary key
- Recreate monitoring_checks table with binary_id primary key
The original migrations used default integer primary keys, but the schemas
expected binary_id (UUID). This caused Ecto to generate UUID strings that
Postgrex tried to encode as binaries, resulting in silent insert failures.
SNMP interface stats fixes:
- Fix field name mismatch in get_interface_stats (if_in_octets vs in_octets)
- Remove unnecessary tuple handling code (Client already unwraps SNMPKit tuples)
- Clean up decode_snmp_value function and improve error messages
Monitoring check fixes:
- Fix response_time type from float to integer in Poller.check_device
- Add error logging to catch and report check creation failures
These changes enable:
- SNMP sensor readings to be collected and stored (disk usage, memory, CPU, etc.)
- Interface statistics to be collected (Counter64 octets, errors, discards)
- Equipment availability metrics to update on the dashboard
Shows the full UTC timestamp when hovering over the relative 'time ago'
text on the equipment detail page.
Changes:
- Wrapped time_ago display in a span with title attribute
- Title shows full datetime in UTC format
- Added cursor-help class for visual hint that tooltip is available
Users can now see both the easy-to-read relative time ('5m ago') and
the precise UTC timestamp (2026-01-04 19:30:15Z) by hovering.
Added back navigation links at the top of all edit/new pages to
improve UX and make it easier to navigate back to previous pages.
Changes:
- Added back link to equipment edit/new pages
- Edit: goes back to equipment detail page
- New: goes back to equipment list
- Added back link to site edit/new pages
- Edit: goes back to site detail page
- New: goes back to sites list
- Links styled with arrow icon and subtle hover effect
- Added error handling to Counter64 decoder with try/rescue
The back links appear above the page header and provide clear
navigation context for users.
Added a new 'danger' button variant to the button component for
destructive actions like deleting equipment or sites.
Changes:
- Added 'danger' variant to button component (red background/hover)
- Updated equipment delete button to use variant="danger"
- Updated site delete button to use variant="danger"
- Removed custom red color classes in favor of standardized variant
This provides consistent styling for all delete/destructive actions
across the application while maintaining proper button base styles
(padding, rounded corners, shadow, focus ring, etc).
Removed the manual 'Check Now' functionality since polling happens
automatically on a schedule with distributed coordination. Manual
triggers would interfere with the coordinated polling system.
Changes:
- Removed 'Check Now' button from equipment detail page
- Removed trigger_check event handler
- Simplified Discover and Edit buttons to use standard button styling
- Removed custom classes in favor of default button component styling
The equipment is now polled automatically based on its configured
check_interval_seconds, coordinated across all pods.
- Remove delete buttons from show pages
- Add 'Danger Zone' section to edit pages with delete functionality
- Improves UX by keeping destructive actions on edit pages
- Add clear warnings about deletion consequences
- After creating a site, redirect to site detail page instead of list
- Show helpful empty state on site page with instructions to add equipment
- Flash message guides user to add their first device
- Dashboard shows welcome wizard when no sites exist
- Equipment page redirects to site creation when no sites exist
- Equipment form redirects users to create a site if none exist
- Equipment list hides "New Equipment" button when no sites exist
- Update layouts to accept nil current_organization for org-less pages
- Change /orgs to use Layouts.authenticated instead of Layouts.app
- Update authenticated layout to show user menu without organization
- Menu button shows org name when in org, 'Menu' on org list page
- Always show Settings and Log out options
- Wrap /orgs page in Layouts.app to include header/footer
- Make entire org box clickable by using .link component
- Remove 'Open' button - entire card is now the link
- Add cursor-pointer for better UX
- Add real-time updates with 10-second refresh and PubSub integration
- Implement pure SVG charts: response time sparkline and uptime gauge
- Create compact dashboard layout with status cards, metrics, and graphs
- Add relative time display (e.g., '5s ago') for last check
- Redesign SNMP data display: compact device info, sensors grid, interfaces table
- Calculate and display uptime percentage and average response time
- Update tests to match new compact layout
- Explicitly discard return value from trigger_check call
- The return value is not needed for this fire-and-forget operation
- Resolves unmatched_return warning at line 58
- Add active_page attribute to authenticated layout
- Update nav_link component to show active state with border and text styling
- Update all LiveView templates to pass active_page parameter
- Fix datetime truncation issues in tests and schemas for :utc_datetime fields
- Fix dashboard test assertions for equipment status text