- Update MibTranslator to automatically expand configured MIB directories
- snmptranslate doesn't search recursively, so we explicitly list all subdirs
- Simplify config to only specify root priv/mibs directory
- Update both config.exs (production) and dev.exs (development)
- Subdirectories like mikrotik/, cisco/, net-snmp/ are auto-discovered
- Update config.exs to point to /app/priv/mibs in production
- Update dev.exs to include priv/mibs root for standard MIBs
- Remove obsolete :mib_dir (singular) config
- MIB files are now part of the release and included in Docker image
- Paths work in both development and production environments
- Remove PVC volume mount from k8s/deployment.yaml
- Delete k8s/mib-pvc.yaml (no longer needed)
- Update .gitignore to commit MIB files to git
- Add mix import_mibs task to copy MIBs from LibreNMS
- Add all MIB files from priv/mibs/ to git
This fixes the multi-attach volume error in Kubernetes where new pods
couldn't start because the RWO (ReadWriteOnce) PVC was attached to the
old pod. MIBs are now part of the Docker image and can be used by all
pods simultaneously.
- Add monitoring_enabled and check_interval_seconds fields to Device and SnmpDevice protobuf messages
- Add MonitoringCheck protobuf message for ping results
- Update AgentChannel to handle monitoring_check events from agents
- Configure monitoring settings in job payloads sent to agents
- 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
The neighbor discovery was using snmp_device.id instead of the actual
Equipment device.id when creating neighbor records. This caused all
neighbor inserts to fail because the foreign key constraint expected
the Equipment ID, not the SNMP Device ID.
Fixed in two places:
- Discovery: Use snmp_device.device_id (FK to Equipment)
- PollerWorker: Use device.id (Equipment ID) instead of snmp_device.id
Neighbors should now be properly discovered and saved during both
initial discovery and ongoing polling.
- Pass range parameter to SensorChart hook via data-range attribute
- Calculate x-axis min/max dynamically based on selected range (1h, 6h, 12h, 24h, 7d, 30d)
- Show date + time on x-axis labels for ranges > 24 hours
- Show time only for ranges <= 24 hours
- Fixes issue where 7d and 30d graphs showed incorrect time range
- Changed ping module to use microsecond timer resolution instead of millisecond
- Prevents 0ms readings for very fast pings (localhost, local network)
- Updated response_time_ms to float type to store decimal precision
- Migration converts existing integer values to float
- Add timezone to socket assigns in mount_current_scope
- Detect user timezone from browser using Intl.DateTimeFormat
- Auto-save detected timezone to user profile on first connection
- Update all templates to use TimeHelpers with timezone parameter
- Update agent helper functions to accept timezone parameter
- Templates updated:
- Alert list (triggered, acknowledged, resolved)
- Device list (last checked)
- Agent list and show (created, last seen, updated)
- Admin dashboard (impersonation logs)
- Admin org/user lists (created/joined dates)
- Main dashboard (alert times)
Wrapped all Exq.Api calls in individual try/rescue/catch blocks to handle
GenServer exits and exceptions when Exq hasn't connected to Redis yet.
The "GenServer Exq terminating" errors in logs are from Exq itself crashing
when it receives calls before it's ready, not from our code. These will still
appear in logs as Exq restarts, but our telemetry code won't crash.
Changes:
- Added try/rescue/catch around each queue_size call in the loop
- Added try/rescue/catch around processes/busy calls
- Kept outer try/rescue/catch as a final safety net
- rescue blocks must come before catch blocks in Elixir
Fixed two profile import issues:
1. Skip sensors without OIDs:
- Some profiles have sensors with null/missing OID fields
- These can't be used for SNMP polling anyway
- Added check to skip importing sensors without oid or num_oid
- Prevents Ecto.Changeset validation errors
2. Truncate MIB values to 255 characters:
- ERROR 22001 (string_data_right_truncation)
- MIB field is varchar(255) but some values exceed this
- Added truncation to first 255 chars before saving
- Location: lib/towerops/device_profiles/importer.ex:229-236
Both errors were causing profile imports to fail with crashes.
Changed pattern matching to proper error handling in telemetry functions
to prevent crashes when Exq or Redis connections aren't available.
Issues fixed:
1. Exq.Api.queue_size/2 calls were using pattern matching {:ok, size} =
which crashed with FunctionClauseError when Exq wasn't connected to Redis
2. Redix.start_link/1 and Redix.command/2 calls were using pattern matching
which crashed when Redis wasn't available
Changes:
- publish_exq_stats: Use case statements for queue_size calls
- publish_exq_stats: Use with statement for processes/busy calls
- publish_redis_stats: Use with statement for all Redis operations
This allows the app to start and run even when Redis/Exq connections
aren't immediately available, which is especially important during
container startup and rolling deployments.
Changed Valkey service from headless to ClusterIP and updated
REDIS_HOST to use full FQDN for more reliable DNS resolution.
Changes:
- valkey-service.yaml: Changed from headless (clusterIP: None) to ClusterIP
- deployment.yaml: Changed REDIS_HOST from "valkey" to "valkey.towerops.svc.cluster.local"
This ensures stable ClusterIP and explicit DNS resolution for Redis connections.
Fixed two import-related errors:
1. DateTime microseconds error when updating API token last_used_at:
- Error: :utc_datetime expects microseconds to be empty
- Fix: Use DateTime.truncate(:second) to remove microseconds
- Location: lib/towerops/api_tokens.ex:179
2. Float parsing error for integer strings like "0":
- Error: :erlang.binary_to_float("0") - not a textual representation of float
- Fix: Use Float.parse/1 instead of String.to_float/1
- Float.parse/1 handles both "0" and "0.0" correctly
- Returns {float, remainder} on success, :error on failure
- Location: lib/towerops/device_profiles/importer.ex:380
Both errors were causing profile imports to fail with crashes.
The `not` operator in Elixir only works with booleans, not nil values.
When Exq process doesn't exist, Process.whereis(Exq) returns nil, causing
`:badarg` error when used with `not`.
Error:
Class=:error
Reason=:badarg
{:erlang, :not, [nil], [error_info: %{module: :erl_erts_errors}]}
Changed from:
not Process.whereis(Exq)
To:
is_nil(Process.whereis(Exq))
This properly checks if the process exists without causing runtime errors.
Previously Valkey ran as a sidecar container in the main deployment,
causing it to restart every time the web app deployed. This resulted
in connection errors and cache loss during deployments.
Changes:
- Created valkey-statefulset.yaml with StatefulSet resource (1 replica)
- Created valkey-service.yaml with headless service
- Removed Valkey sidecar container from deployment.yaml
- Updated REDIS_HOST from 127.0.0.1 to valkey-0.valkey.towerops.svc.cluster.local
- Added new resources to kustomization.yaml
Benefits:
- Valkey persists across web app deployments
- No connection errors during rolling updates
- Cache data preserved
- Independent scaling and resource management
The token success modal was missing z-index classes that caused the modal
content to appear behind the grey backdrop. Users could only see the
checkmark icon and couldn't interact with the modal properly.
Changes:
- Added z-0 class to backdrop div
- Added relative z-10 classes to span and modal content div
- Added unique ID to modal (token-success-modal)
- Added unique ID to modal title (token-success-modal-title)
This matches the z-index structure used in the "add token" modal.