The plug was incorrectly placed in the endpoint, causing it to run on
all requests including APIs. Moved to :browser pipeline where it belongs,
and removed redundant fetch_session call since the pipeline handles it.
- Move Oban Web from /oban to /admin/oban
- Move LiveDashboard from /dashboard to /admin/dashboard
- Both remain protected by :require_superuser plug
- Update documentation to reflect new paths
- Created new on_mount hook :load_default_organization that automatically loads user's first organization
- Moved sites and devices routes to root path (/ sites, /devices) instead of /orgs/:org_slug
- Updated all navigation links and route references throughout the app
- Dashboard, alerts, and agents still use org-specific routes (/orgs/:org_slug)
- Users can still switch organizations via org selector for other pages
- 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
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.
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.
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
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.
- 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)
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
The :accepts plug in the agent_api pipeline was only allowing JSON,
which caused 406 errors when the agent sent protobuf requests.
Changes:
- Register application/x-protobuf MIME type in config
- Add protobuf to agent_api pipeline accepts list
- Agent can now successfully communicate using protobuf format
Use Plug.Telemetry's dynamic log level feature to disable logging for
/health requests. This is the proper Phoenix way to exclude specific
endpoints from request logs.
- Add log_level/1 function to endpoint that returns false for /health
- Configure Plug.Telemetry to use dynamic log level
- Remove log: false from router (handled by endpoint now)
This prevents Kubernetes health probe spam in application logs.
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
- 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
Create HealthController that returns 200 OK for health checks.
Route is outside browser pipeline to avoid SSL redirect and
authentication requirements.
- Sites schema with hierarchical parent-child relationships
- CRUD operations for sites
- Sites context with helper functions
- LiveView pages:
- /orgs/:slug/sites - List all sites
- /orgs/:slug/sites/new - Create new site
- /orgs/:slug/sites/:id - View site details
- /orgs/:slug/sites/:id/edit - Edit site
- Support for site locations and descriptions
- Site tree builder for hierarchy visualization
2. Equipment Management
- Equipment schema with monitoring fields
- IP address validation (IPv4 & IPv6)
- Equipment status tracking (up/down/unknown)
- Customizable check intervals per equipment
- Equipment context with CRUD operations
- LiveView pages:
- /orgs/:slug/equipment - List all equipment
- /orgs/:slug/equipment/new - Add equipment
- /orgs/:slug/equipment/:id - View equipment details
- /orgs/:slug/equipment/:id/edit - Edit equipment
- Equipment can be added from site pages
- Status badges and last checked timestamps
3. Database Schema
- sites table with self-referencing parent_site_id
- equipment table with status tracking
- All migrations run successfully
- Proper indexes on foreign keys and status
4. Features Implemented
- ✅ IP address validation using :inet.parse_address
- ✅ Site hierarchy with parent-child relationships
- ✅ Equipment linked to sites
- ✅ Monitoring enabled/disabled per equipment
- ✅ Customizable check intervals (30s - 3600s)
- ✅ Status tracking (up/down/unknown)
- ✅ Timestamps for last check and last status change
- ✅ Organization-scoped data (users only see their org's data)