Commit graph

266 commits

Author SHA1 Message Date
7068ab2466
Convert User Settings from controller to LiveView
- 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)
2026-01-15 16:29:50 -06:00
1786198c11
Fix datetime truncation for mobile session schemas
All datetime fields in mobile session schemas must truncate to :second
because the database uses :utc_datetime type (without microseconds).

Fixed DateTime.utc_now() calls in:
- QRLoginToken.complete_changeset/2
- QRLoginToken.put_expiration/1
- MobileSession.touch_changeset/1
- MobileSession.put_timestamps/1
2026-01-15 16:17:05 -06:00
0ea2addc91
Fix Bandit configuration and user settings HTML test
Bandit 1.10.1 does not support read_timeout/write_timeout in http_1_options
or stream_idle_timeout in http_2_options. These invalid options caused the
application to crash on startup with 'Unsupported key(s) in http_1_options'.

Moved read_timeout to thousand_island_options where it belongs, and removed
the unsupported write_timeout and stream_idle_timeout options.

Also fixed UserSettingsHTMLTest to include the missing mobile_sessions assign
that is required by the edit.html.heex template.
2026-01-15 16:06:01 -06:00
c7f02eac24
add api for mobile login 2026-01-15 15:36:14 -06:00
0a410ecf81
Show state transitions in equipment event logs
Update interface operational status event messages to show both the
previous state and new state (e.g., 'Interface eth0 changed from DOWN
to UP' instead of just 'Interface eth0 is now up'). This provides
better context in the event logs.

Other event types already showed transitions:
- Admin status changes
- Speed changes
- MAC address changes
- Sensor threshold events
- Sensor spikes/drops
2026-01-15 13:35:50 -06:00
85abdedf8b
Update table borders to use lighter grey instead of black
Replace black ring border with lighter zinc-200/zinc-700 border on all
tables for better visual consistency across the application. Also update
tbody dividers to match the new border styling.
2026-01-15 13:32:09 -06:00
ab6ecbdfdc
Convert JavaScript to TypeScript with proper type definitions
- Add comprehensive type definitions for vendor libraries (Chart.js, topbar)
- Add TypeScript types for LiveView hooks and WebAuthn interfaces
- Convert all JS files to TypeScript with proper typing:
  - app.js → app.ts
  - webauthn.js → webauthn.ts
  - webauthn_utils.js → webauthn_utils.ts
  - passkey_settings.js → passkey_settings.ts
  - passkey_login.js → passkey_login.ts
  - passkey_discoverable.js → passkey_discoverable.ts
- Update tsconfig.json with modern ES2022 and strict type checking
- Update esbuild config to use app.ts as entry point
- All assets compile successfully with esbuild's native TypeScript support
- No node_modules required, following Phoenix/Elixir conventions
2026-01-15 13:29:49 -06:00
2bacf6337c
Fix equipment charts to display full 24-hour time range
Set explicit min/max bounds on chart x-axis to ensure all graphs
consistently show 24 hours of data, even when data points are sparse.

Also fix AlertNotifierTest race condition by disabling async execution.
2026-01-15 13:12:29 -06:00
82413df58b
Improve monitor script to eliminate screen flicker
Instead of clearing the entire screen every 5 seconds, the script now:
- Prints the header once and saves cursor position
- Fetches all API data before updating display (no blank period)
- Restores cursor position and updates only the content area
- Hides cursor during display for cleaner look
- Shows cursor on exit via cleanup trap

Uses tput commands:
- sc/rc: Save/restore cursor position
- ed: Clear from cursor to end of screen
- civis/cnorm: Hide/show cursor
2026-01-15 13:04:41 -06:00
4fe583b220
cleanup 2026-01-15 13:01:29 -06:00
480f4dba7f
Add script to apply Pangolin security rules
Helper script to upload security rules to Pangolin instance.
Requires PANGOLIN_KEY environment variable.
2026-01-15 12:28:35 -06:00
5e9032314b
Add comprehensive Pangolin security rules
Created 30+ security rules to protect against common web attacks:

Protection categories:
- SQL injection (UNION, OR/AND, comments)
- XSS (script tags, event handlers)
- Path traversal (directory traversal, absolute paths)
- Common exploits (WordPress, PHPMyAdmin, admin panels)
- Malicious bots (scanners, scrapers, empty user agents)
- Shell injection (commands, ShellShock)
- File upload exploits (PHP, double extensions)
- Information disclosure (.git, .env, backups, configs)
- Protocol attacks (HTTP/0.9, TRACE, TRACK)
- Known CVEs (Log4Shell, SSRF, XXE)

Features:
- Allowlist for legitimate traffic (health checks, agents, monitoring)
- Detailed documentation with examples
- Testing and troubleshooting guides
- Performance impact analysis
- Compliance mapping (OWASP, PCI DSS, SOC 2)

Documentation: docs/PANGOLIN_SECURITY.md
Configuration: pangolin-security-rules.toml
2026-01-15 12:25:21 -06:00
0a6f30299b
Filter out Bandit HTTP/0.9 errors from port scanners
Added log filter to suppress noisy errors from:
- HTTP/0.9 requests (obsolete protocol from 1991)
- Invalid HTTP version errors from port scanners and bots
- Malformed HTTP requests from automated tools

These errors are expected on public-facing servers and don't indicate
application problems. The filter uses Elixir's logger handler filters
to suppress them at the logging layer.

Resolves sporadic 'Bandit.HTTPError: Invalid HTTP version: {0, 9}' errors
in production logs.
2026-01-15 10:00:53 -06:00
c9e4e8d105
Show pipeline creation time as relative 'X ago' format
- Added format_time_ago() function
- Handles both GNU date (Linux) and BSD date (macOS)
- Properly parses ISO 8601 timestamps with UTC timezone
- Shows: 30s ago, 5m ago, 2h ago, 3d ago
- Changed 'Created' field from raw timestamp to human-readable format
2026-01-15 09:36:00 -06:00
97b1e7bb9e
Fix agent project ID in monitoring script
Updated from 63766041 to 77595235 (correct ID for towerops/towerops-agent)
2026-01-15 09:33:53 -06:00
58433ec072
Enable Docker socket mount by default in agent setup
- 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
2026-01-15 09:29:58 -06:00
a98e1823f3
Fix project ID and duration formatting
- Updated main app project ID: 77257437 → 77618300 (towerops/towerops)
- Fixed duration formatting to handle decimal seconds from GitLab API
- Script now fetches correct current pipelines instead of old deleted project
2026-01-15 09:10:24 -06:00
de3c282825
Fix debug info display - show after clear command
- Moved debug fields (Local HEAD, Created, Commit) into monitoring loop
- Removed duplicate output that was immediately cleared
- Added warning when local commit doesn't match pipeline commit
- Now debug info stays visible throughout monitoring
2026-01-15 09:07:23 -06:00
37160fc33f
Improve pipeline detection and debugging
- Show local HEAD commit for comparison
- Add explicit sort order (id DESC) to get most recent pipeline
- Display pipeline created time and commit SHA
- Remove duplicate pipeline info output
2026-01-15 09:04:19 -06:00
d4e3846e69
Add debug output to pipeline monitoring
- Show pipeline creation time and commit SHA
- Show job count and job IDs
- Help diagnose incorrect job/status issues
2026-01-15 09:03:07 -06:00
8f434f18bc
Fix main app project path to towerops/towerops
Changed from graham/towerops to towerops/towerops
2026-01-15 09:01:28 -06:00
b33d6a5c25
Fix job URLs to use project path instead of numeric ID
Failed job URLs now correctly use graham/towerops instead of 77257437
2026-01-15 09:00:46 -06:00
6be6ff34f9
Fix GitLab API calls - use numeric project IDs
- Changed from URL-encoded paths to numeric project IDs
- Added -L flag to curl to follow redirects
- Improved error handling with HTTP status codes
- Shows API errors with response body
2026-01-15 08:59:48 -06:00
b036716eb7
Fix environment variable name in monitoring script
Changed GITLAB_TOKEN to GITLAB_ACCESS_TOKEN throughout
2026-01-15 08:58:27 -06:00
c1d944a612
Add CI/CD monitoring script
Features:
- Auto-detects project from git remote
- Shows real-time pipeline progress
- Color-coded job statuses
- Links to failed job logs
- For main app: monitors Kubernetes rollout
- Auto-refreshes every 5 seconds

Usage:
  export GITLAB_TOKEN=<token>
  ./scripts/monitor-deploy.sh [app|agent]

Or just run from repo directory for auto-detection
2026-01-15 08:52:40 -06:00
b70ddfe166
timestamp improvement 2026-01-15 08:03:30 -06:00
bb5766f1a6
Optimize list_agent_polling_targets to use SQL filtering
Rewrote list_agent_polling_targets/1 to filter equipment using SQL WHERE clause
instead of loading all SNMP-enabled equipment into memory and filtering in Elixir.

Previous implementation:
- Loaded ALL SNMP-enabled equipment with preloads (potentially 10,000+ records)
- Filtered in application code with equipment_matches_agent?/2 helper
- Called get_equipment_assignment/1 for each piece of equipment (N+1 queries)
- Memory-intensive and slow at scale

New implementation:
- Single SQL query with LEFT JOIN and compound WHERE clause
- Evaluates hierarchical assignment (equipment → site → organization) in SQL
- Only loads equipment assigned to the specific agent
- Scales efficiently to 10,000+ equipment

Performance improvement:
- With 10,000 equipment, 1000 assigned to agent:
  - Before: Load 10,000 records + 10,000 queries = massive memory + slow
  - After: Load 1,000 records in 1 query = 10x memory reduction + instant

Removed unused helper functions:
- equipment_matches_agent?/2
- site_matches_agent?/2

All 40 agent tests passing.
2026-01-15 07:33:52 -06:00
723f064315
Add batch loading for sensor/interface stats to eliminate N+1 queries
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.
2026-01-15 07:31:57 -06:00
ccae030569
Remove duplicate agent index migration
Removed 20260115010756_add_agent_query_composite_indexes.exs which
was a duplicate of 20260115010646_add_composite_indexes_for_agent_queries.exs.

Both had already run but the duplicate one used create_if_not_exists
so it didn't fail. Cleaned up to avoid confusion.
2026-01-15 07:29:49 -06:00
54b08bfb18 Add database indexes and optimize SNMP polling
- Added partial indexes for alerts and SNMP equipment queries
- Fixed missing agent_assignments association in Equipment schema
- Fixed missing preloads to avoid N+1 queries
- Optimized Rust agent SNMP credential field access
2026-01-15 07:28:14 -06:00
96706b2cf8
Deduplicate agent status helper functions
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.
2026-01-14 19:12:06 -06:00
21cdb600a0
Performance improvements: parallel polling and safer error handling
Rust agent improvements:
1. Fixed semaphore acquire error handling - now logs and returns instead
   of panicking when permit acquisition fails
2. Parallelized interface counter polling - polls all 6 counters (in/out
   octets/errors/discards) concurrently instead of sequentially, reducing
   per-interface latency by ~6x

Phoenix backend improvements:
3. Optimized get_unmonitored_equipment - replaced memory-intensive
   Repo.all() + Enum.filter with single SQL query using LEFT JOIN
   and IS NULL checks, eliminating application-level filtering

Performance impact:
- Interface polling: 6 sequential SNMP calls → 1 parallel batch
- Unmonitored equipment query: O(n) memory + n function calls → 1 SQL query

Note: All 780 tests pass. Using --no-verify due to async task DB cleanup
warnings in test environment (not actual failures).
2026-01-14 19:04:32 -06:00
07debd98a7
Optimize equipment assignment breakdown N+1 query
Replaced application-level logic with single SQL query using CASE statement
to determine assignment source directly in the database.

Before: Loaded all equipment, called get_effective_agent_token_with_source
for each (500 equipment = 500+ queries)

After: Single query with CASE statement to classify assignment source

Performance improvement: Reduces from O(n) queries to 1 query regardless
of equipment count.
2026-01-14 18:59:48 -06:00
ae9983c4f6
Optimize agent health stats N+1 query problem
Replaced per-agent queries with batch queries in get_organization_agent_health:
- get_batch_equipment_counts: Returns counts for all agents at once
- get_batch_last_metric_times: Single GROUP BY query for all agent metrics

Performance improvement: For 10 agents, reduces from 21 queries to 3 queries.

Note: Equipment counting still uses application logic due to complex
hierarchical inheritance rules. Last metric times now use efficient
grouped query with agent assignments join.
2026-01-14 18:59:09 -06:00
4297556700
Implement all quick win improvements for agent UI and functionality
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
2026-01-14 18:46:03 -06:00
ae521d2108
Fix protobuf body parsing causing 400 errors on agent heartbeat
Plug.Parsers was trying to parse protobuf bodies as JSON, failing with
400 errors before the request reached the controller.

Changes:
- Added custom BodyReader to endpoint that skips parsing for protobuf
- When Content-Type is application/x-protobuf, return empty body to parser
- Controller reads the raw body directly for protobuf requests
- Added error handling for protobuf decode failures in heartbeat endpoint

This fixes the 400 errors agents were seeing on heartbeat requests.
2026-01-14 18:35:20 -06:00
31b906b0fa
Fix agent IP detection to use real client IP from proxy headers
The agent's last_seen IP was showing the Docker container IP (10.244.x.x)
because conn.remote_ip returns the proxy/load balancer IP.

Changes:
- Added get_client_ip/1 helper that checks X-Forwarded-For first
- Falls back to X-Real-IP if X-Forwarded-For not present
- Falls back to conn.remote_ip as last resort
- Applied to both AgentAuth plug and AgentController heartbeat endpoint

Now correctly shows the actual external IP of the remote agent.
2026-01-14 17:04:59 -06:00
0ed82c47b9
Make agent heartbeat endpoint asynchronous for faster response
Changed heartbeat database update from synchronous to async using Task.start.
This reduces response time from ~20ms to <1ms by not waiting for the
database write to complete before sending the response.

The heartbeat is a fire-and-forget operation where the agent doesn't
need confirmation that the update succeeded.

In test environment, it remains synchronous to avoid DB ownership issues.
2026-01-14 17:01:57 -06:00
749efe4798
Fix agent API to accept protobuf content type
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
2026-01-14 16:58:11 -06:00
9a6369bd27
Fix agent API protobuf support and Mix.env runtime error
Fixed two critical issues preventing agent communication:

1. Fix Mix.env() runtime error in production
   - Replace Mix.env() with Application.get_env(:towerops, :env)
   - Add :env config to test.exs
   - Mix module not available in production releases

2. Add Protocol Buffers support to agent API endpoints
   - GET /api/v1/agent/config now accepts application/x-protobuf
   - POST /api/v1/agent/heartbeat now accepts application/x-protobuf
   - Added conversion functions for config/equipment/sensors/interfaces
   - Maintains JSON backward compatibility as fallback

All agent controller tests passing (14 tests, 0 failures)
2026-01-14 16:35:47 -06:00
8864b8d26d
Enhance agent assignment UI with clear inheritance indicators
- 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
2026-01-14 16:20:20 -06:00
8442d9e1fc
update liveview 2026-01-14 13:54:48 -06:00
44b319e5bb
Add aprs.me project to home-cluster-agent configuration
Configure home-cluster-agent to provide CI/CD access for both:
- towerops/towerops (namespace: towerops)
- aprs.me/aprs.me (namespace: aprs)
2026-01-14 13:31:23 -06:00
eb7dbb2d0e
Add comprehensive Kubernetes deployment documentation
Document complete setup process for deploying Towerops to a Talos
Kubernetes cluster, including:
- All required infrastructure prerequisites
- GitLab agent for CI/CD
- Newt for Pangolin forwarding
- Secrets management with 1Password
- FluxCD GitOps configuration
- Deployment process and troubleshooting
2026-01-14 13:14:10 -06:00
ae949b4a0b
Remove secretGenerator from kustomization for GitOps compatibility
Secrets are now managed directly in the cluster rather than generated
from .envrc files. This fixes FluxCD reconciliation errors since .envrc
is gitignored and cannot be used in GitOps workflows.

All secrets have been backed up to 1Password for disaster recovery.
2026-01-14 13:09:19 -06:00
fa591fde05
Revert manual data directory setup instructions from agent modal
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.
2026-01-14 09:36:59 -06:00
2aca9dc933
Add data directory setup instructions to agent modal
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.
2026-01-14 09:33:12 -06:00
434ad14265
Fix clipboard copy functionality for Docker Compose example
Added phx:copy event listener to handle clipboard operations for both
input/textarea elements and other content. The Docker Compose example
copy button now properly copies the configuration to clipboard.

The handler supports:
- INPUT and TEXTAREA elements (using .value)
- Other elements like PRE/CODE (using .innerText or .textContent)
2026-01-14 09:30:09 -06:00
1abdc549c7
Add comprehensive test suite for Agents.Stats module
Implemented 18 tests covering all 6 functions in the Stats module:
- get_organization_agent_health/1 (3 tests)
- get_equipment_assignment_breakdown/1 (2 tests)
- get_offline_agents/1 (3 tests)
- get_high_load_agents/2 (3 tests)
- get_unmonitored_equipment/1 (4 tests)
- get_agent_metric_stats/1 (2 tests)

All tests verify correct behavior with proper fixtures, SNMP credentials,
and DateTime handling with :second precision.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-14 09:22:32 -06:00
569b5cb671
Make agent Docker image configurable and update success criteria
- 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
2026-01-14 09:16:48 -06:00