Commit graph

313 commits

Author SHA1 Message Date
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
991e339cfa
fix: remove Redis config from test.exs - tests should mock Redis 2026-01-17 16:30:52 -06:00
eb7b57aa42
feat: add Valkey as k8s sidecar for Redis compatibility
- Add Valkey 8.0 alpine container as sidecar in k8s deployment
- Configure with appropriate security context (non-root, no privilege escalation)
- Add resource limits (256Mi memory, 200m CPU) and requests (128Mi, 50m)
- Add health probes using valkey-cli ping command
- Configure Redis connection via REDIS_HOST and REDIS_PORT environment variables
- Add Redis config to runtime.exs, dev.exs, and test.exs
- Valkey runs on localhost:6379 within the same pod as Phoenix app
2026-01-17 16:30:38 -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
5398ff65bc
chore: add TODOS.md to gitignore
Remove TODOS.md from version control as it contains personal task tracking notes
2026-01-17 16:12:35 -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
55a0d88978
feat: add latency graph for ICMP ping monitoring
- 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
2026-01-17 16:02:35 -06:00
f0f5aca491
Allow empty SNMP community string for device inheritance
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
2026-01-17 15:54:06 -06:00
930885e21a
Add agent rename functionality
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.
2026-01-17 15:48:47 -06:00
c35a502425
Change agent revoke to delete with full cleanup
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
2026-01-17 15:40:09 -06:00
137f84f5e5
credo tweak 2026-01-17 15:32:33 -06:00
541ab7e7d6
more renames 2026-01-17 15:13:56 -06:00
b6f4946385
moduledocs 2026-01-17 15:08:49 -06:00
22f81acfa6
credo cleanup 2026-01-17 15:00:52 -06:00
a810e75fc4
rename equipment to device 2026-01-17 14:48:46 -06:00
c88f2bbf96
Make active alerts the default tab
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.
2026-01-17 12:55:03 -06:00
9c2d95b55a
Add zero-downtime deployment configuration
Prevent WebSocket disconnections during deployments by:

1. Rolling Update Strategy:
   - maxSurge: 1 (allow 1 extra pod during rollout)
   - maxUnavailable: 0 (keep all pods running)
   - minReadySeconds: 10 (wait before continuing rollout)

2. Graceful Shutdown:
   - terminationGracePeriodSeconds: 30 (allow Phoenix to drain connections)

3. PodDisruptionBudget:
   - minAvailable: 1 (ensure at least 1 pod always available)
   - Prevents all pods from being terminated simultaneously

4. Improved Health Checks:
   - Faster readiness probe (5s initial, 3s period)
   - More aggressive success/failure thresholds
   - New pods marked ready faster

This ensures new pods are fully ready and serving traffic before old
pods are terminated, maintaining WebSocket connections and preventing
'something went wrong' errors during deployments.
2026-01-17 12:44:22 -06:00
44c71bca96
Make optional labels lighter on site form
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
2026-01-17 12:34:49 -06:00
9735e6e569
Improve optional field labels on site form
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.
2026-01-17 12:33:23 -06:00
f9a18fa423
Fix clipboard copy and org settings navigation
- 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.
2026-01-17 12:18:49 -06:00
eff7d0b55a
Clarify agent assignment breakdown label
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).
2026-01-17 12:08:17 -06:00
dca90db137
allow override of agent and snmp community 2026-01-17 12:05:08 -06:00
7975581da2
Add Organization Settings link to org dropdown menu
- Add Organization Settings link in top-right org menu dropdown
- Position it between Switch Org and Admin Panel
- Add gear icon for visual clarity
- Rename 'Settings' to 'User Settings' with user icon for clarity
- Organization Settings link only shows when current_organization is set
2026-01-17 11:51:10 -06:00
60b97136c7
Add UI forms for hierarchical SNMP configuration
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.
2026-01-17 11:49:31 -06:00
ba1e8933e4
Add hierarchical SNMP configuration with org/site/equipment fallback
Implements a hierarchical SNMP community configuration system where settings
can be defined at the organization level (global default), overridden at the
site level, or overridden at the equipment level.

Changes:
- Migration: Add snmp_version and snmp_community to organizations and sites
- Organization schema: Add SNMP fields with default version "2c"
- Site schema: Add SNMP fields that override organization defaults
- Equipment context: Add get_snmp_config/1 with hierarchical fallback logic
- Discovery: Use Equipment.get_snmp_config instead of equipment.snmp_community
- PollerWorker: Use Equipment.get_snmp_config instead of equipment.snmp_community

The fallback order is:
1. Equipment-level (highest priority)
2. Site-level
3. Organization-level (default)

All 785 tests passing.
2026-01-17 11:47:17 -06:00
de002f9903
Cap storage/memory usage sensors at 100% to handle device firmware bugs
Fixes an issue where storage usage was reporting values over 200,000% due to
incorrect SNMP values from the device. This can occur when:
- Device firmware has bugs in hrStorageUsed/hrStorageSize reporting
- SNMP allocation units differ between OIDs
- Values are read during device reconfiguration

Changes:
- PollerWorker: Cap percentage sensors at 100%, log warning with raw values
- Mikrotik profile: Cap storage usage at 100% during discovery
- NetSnmp profile: Cap memory usage at 100% during discovery

The raw percentage is logged before capping to help diagnose device issues.
All percentage sensors now have consistent validation across polling and discovery.
2026-01-17 11:36:37 -06:00
79dabd840b
wording 2026-01-17 11:21:18 -06:00
07b0ddb67d
Set DEPLOY_TIMESTAMP from GitLab CI instead of pod metadata
- Update GitLab CI deploy job to set DEPLOY_TIMESTAMP when deploying
- Use 'kubectl set env' to update all pods with same timestamp
- Change deployment.yaml to use static placeholder value
- Update documentation to explain GitLab CI timestamp approach

This ensures all pods show the same deployment time, regardless of:
- Which pod handles the request (with replicas=2)
- When individual pods were created or restarted
- Pod restarts from crashes or rolling updates

The timestamp now represents when the deployment was initiated by GitLab CI,
not when individual pods were created.
2026-01-17 11:17:07 -06:00
9fcf19b304
Use Kubernetes pod creation timestamp for deploy time in footer
- Read DEPLOY_TIMESTAMP environment variable for actual deploy time
- Fall back to compile time in development when env var not set
- Add Kubernetes Downward API to inject metadata.creationTimestamp
- Update k8s/deployment.yaml to set DEPLOY_TIMESTAMP env var
- Document deployment timestamp mechanism in k8s/README.md

This ensures the footer shows when the pod was deployed to the cluster,
not when the Docker image was built in CI/CD.
2026-01-17 11:14:15 -06:00
ae16acf016
Add build timestamp to footer with shared time formatting helpers
- 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
2026-01-17 11:10:50 -06:00
654fcb541c
Add footer with dynamic copyright year to all layouts
- Add footer component to app, authenticated, and admin layouts
- Update marketing layout footer to use dynamic year
- Use flexbox layout to push footer to bottom of viewport
- Footer displays 'Copyright © <year> Towerops'
- Year updates automatically using Date.utc_today().year
2026-01-17 11:05:39 -06:00
b53a53b199
Add comprehensive Dialyzer type specifications
- Add @type definitions to all schema modules:
  - User, UserCredential, Membership, Invitation
  - Organization, Site, AgentAssignment
  - InterfaceStat, SensorReading, Alert
- Fix all compilation warnings with stronger Elixir types:
  - Remove unused require Logger in log_filter.ex
  - Remove unused parse_float(nil) clause
  - Add pin operators (^) for variables in binary pattern matches
- Fix Dialyzer errors (25 → 0):
  - Remove unreachable pattern match clauses
  - Fix unmatched return values with _ = prefix
  - Update @spec for deliver_alert_notification/1
- Properly handle all Phoenix.PubSub.subscribe and Task.start return values
- Explicitly ignore if statement return values where needed

All files now pass mix compile --warnings-as-errors and mix dialyzer.
2026-01-17 10:52:02 -06:00
02c163986e
Fix 21 Dialyzer errors by adding schema types and fixing unmatched returns
Schema types added:
- AgentToken.t/0
- Equipment.t/0
- Device.t/0
- Interface.t/0
- Sensor.t/0

Fixed unmatched returns in AgentChannel:
- Agents.update_agent_token_heartbeat/3 (2 instances)
- process_snmp_result/2

Reduced Dialyzer errors from 48 to 27.
2026-01-17 10:40:47 -06:00
e1b9976219
Add Dialyzer typespecs to WebSocket agent code and Agents context
- Add @spec to AgentSocket callbacks (connect/3, id/1)
- Add @spec to AgentChannel callbacks (join/3, handle_info/2, handle_in/3)
- Add @spec to AgentChannel private function build_jobs_for_agent/1
- Add @spec to Agents.verify_agent_token/1
- Add @spec to Agents.update_agent_token_heartbeat/3
- Add @spec to Agents.list_agent_polling_targets/1

These specs provide strict type checking for the critical WebSocket
agent communication code implemented in this session.
2026-01-17 10:37:27 -06:00
bdde71462a
Fix memory/CPU chart fill: explicitly disable area fill for non-traffic charts
- Set fill: false for CPU, memory, and storage charts
- Prevents Chart.js from auto-filling when backgroundColor is set
- Only traffic charts should have filled area (fill: 'origin')
- Fixes visual bug where 20% memory usage appeared as 100% fill
2026-01-17 10:28:52 -06:00
5b62910bae
Remove unused module attributes and alias in Ubiquiti profile
- Remove unused Discovery alias
- Remove unused @radio_tx_capacity and @radio_rx_capacity attributes
- Remove unused @radio_uptime_pct, @radio_firmware, @radio_status
- Remove unused @radio_remote_mac and @radio_remote_ip attributes
- Fixes all compile warnings
2026-01-17 10:20:19 -06:00
305b22dc65
Fix organization_id access: use equipment.site.organization_id 2026-01-16 19:21:58 -06:00
6b7a3ba36e
Fix binary protobuf encoding for JSON serializer
- Use base64-encoded binary in JSON payload instead of {:binary, data} tuples
- V1.JSONSerializer cannot encode tuples, needs plain JSON objects
- Agent already sends/expects base64-encoded binary in 'binary' key
2026-01-16 18:36:24 -06:00
9b88a32f33
Move token authentication from URL to Phoenix channel join payload 2026-01-16 18:22:57 -06:00
538f582fec
Update agent setup instructions with Watchtower auto-updates
- 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
2026-01-16 17:38:30 -06:00
eb91798729
agent improvements and agent config 2026-01-16 17:26:51 -06:00
a7ed057976
neighbor improvements 2026-01-16 16:25:49 -06:00
dcd4588304
fix neighbors 2026-01-16 16:09:15 -06:00
6627235981
add tests and add snmp neighbor discovery 2026-01-16 13:44:29 -06:00
3539e36f12
updates 2026-01-16 13:15:59 -06:00
6908661bd1
Fix mobile API organizations endpoint
Fixed bug where list_user_organizations was being called with user struct
instead of user_id, causing 400 Bad Request errors.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 16:54:28 -06:00
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