- Simplify APRS_PORT parsing to avoid syntax errors
- Disable Exq properly in test environment to prevent startup issues
- Disable Prometheus telemetry in test mode to avoid port conflicts
- Update telemetry module to conditionally start Prometheus metrics
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove APRS server port from status page server field
- Add comprehensive cluster node list to cluster status section
- Highlight leader node with primary badge and indicator dot
- Display all cluster nodes in a responsive flex layout
- Include all_nodes field in cluster_info for complete node visibility
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix race condition in map bundle loading by waiting for script to load before initializing map
- Ensure Leaflet is properly exposed as window.L in map bundle
- Add Leaflet availability check in map initialization function
- Disable Exq background jobs in development environment to remove Redis dependency
- Remove invalid TelemetryMetricsPrometheus plug from endpoint
- Application now starts successfully without Redis in development mode
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add telemetry_metrics_prometheus dependency
- Configure Prometheus exporter in telemetry module
- Add metrics endpoint to application endpoint
- Expose /metrics for Prometheus scraping
This enables the application to be monitored by the Prometheus stack.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Run mix format to fix multi-line function call formatting.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix Packet module usage in queries (use from/1 to create proper query)
- Fix error handling in DbOptimizer to use try/rescue
- Remove unused erlc_paths from mix.exs comment
- Improve type specifications consistency
All dialyzer specs now pass without critical errors.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add comprehensive type specs to all functions in Encoding module
- Add missing specs to private functions in EncodingUtils
- Fix compilation warnings (unused variables, deprecated Logger.warn)
- Fix dialyzer warnings about unreachable code and implicit nil returns
- Remove unused do_migrate/1 function
- Remove last references to erlc_paths and gleeunit dependency
All dialyzer specs now pass without warnings.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove erlc_paths and erlc_include_path from mix.exs (leftover from Gleam)
- Add comprehensive dialyzer specs to Encoding module
- Add missing dialyzer specs to EncodingUtils module
- Ensure all public and private functions have proper type specifications
This fixes the Docker build error about undefined erlc_paths function
and ensures full dialyzer compliance.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Convert Gleam encoding module to Elixir implementation
- Remove all Gleam dependencies from mix.exs
- Remove Gleam configuration files (gleam.toml, src directory)
- Update Dockerfile to remove mix_gleam installation
- Update EncodingUtils to use Elixir module instead of Gleam
- Remove Gleam references from documentation
- Clean up all Gleam-related build configuration
This simplifies the build process and removes the compilation issues
that were preventing successful Docker builds.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove manual Exq startup from application supervision tree
- Add :exq to extra_applications in mix.exs for automatic startup
- Remove duplicate Exq configuration functions
- Let Exq start automatically via runtime.exs configuration
The issue was Exq being started twice - once manually in the supervision
tree and once automatically via configuration, causing "already_started" error.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add statement_timeout and lock_timeout settings to migration
- Remove generated column addition that requires full table rewrite
- Replace with functional index to avoid table rewrite
- Create separate migration for generated column with 2-hour timeout
- Add MIGRATION_TIMEOUT environment variable support (default 2 hours)
- Configure extended timeouts in Release.migrate for large operations
The main migration now uses only CONCURRENTLY operations that don't lock
the table. The generated column addition is deferred to a separate migration
that can be run during a maintenance window.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add MigrationLock module using PostgreSQL advisory locks
- Prevent concurrent migrations across multiple nodes
- Update Release.migrate to use distributed lock when cluster enabled
- Disable auto-migrations in cluster mode to avoid race conditions
- Add init container configuration for Kubernetes deployments
- Document migration strategies in k8s/README-migrations.md
This ensures only one node runs database migrations at a time, preventing
conflicts and race conditions in distributed deployments.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace Oban dependency with Exq for Redis-based background jobs
- Update PacketCleanupWorker to use Exq's perform/1 signature
- Add CleanupScheduler GenServer for periodic job scheduling
- Configure Exq with Redis connection and queue settings
- Update tests to work with Exq job format
- Maintain 6-hour packet cleanup schedule functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- RedisCache uses child_spec, not start_link
- Update application.ex to use tuple syntax for starting Redis caches
- This fixes the :undef error when starting caches
Co-Authored-By: Claude <noreply@anthropic.com>
- Redix doesn't support pool_size option directly
- Removed pool_size from both RedisRateLimiter and RedisCache
- This fixes the NimbleOptions.ValidationError on startup
Co-Authored-By: Claude <noreply@anthropic.com>
- Group handle_info/2 clauses together
- This fixes the warning about clauses with same name/arity
- Now compiles cleanly with --warnings-as-errors
Co-Authored-By: Claude <noreply@anthropic.com>
- Delay DeviceCache initial load by 1 second to allow Redis connections
- Move shutdown handlers to end of supervision tree
- This prevents shutdown handlers from interfering with startup failures
Co-Authored-By: Claude <noreply@anthropic.com>
- Add SKIP_DB_CREATE env var to skip database creation when using PgBouncer
- Fix redis_children() to return proper child specs
- Implement zero-downtime graceful shutdown without user notifications
- Add health check plug for Kubernetes probes
- Remove user-facing shutdown notifications for seamless deployments
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces a dual-mode caching and rate limiting system that can use
either Redis (for distributed deployments) or ETS/Cachex (for single-node setups).
Key changes:
- Add Cache abstraction layer that automatically switches between Redis and Cachex
- Implement RedisCache module with full distributed cache functionality
- Implement RedisRateLimiter with sliding window algorithm for accurate rate limiting
- Add RateLimiterWrapper to provide unified API for both implementations
- Update application startup to conditionally use Redis when REDIS_URL is set
- Migrate all cache operations to use the new Cache abstraction
- Support graceful fallback to ETS-based solutions when Redis is unavailable
The system automatically detects Redis availability via REDIS_URL environment
variable and switches between implementations without code changes. This enables
proper distributed caching and rate limiting in Kubernetes deployments while
maintaining backward compatibility for development environments.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The phoenix_pubsub_redis adapter expects the url option at the top level,
not nested under redis_options.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove static Redis PubSub configuration from runtime.exs
- Add dynamic pubsub_config/0 function in application.ex
- Configure PubSub adapter based on cluster_enabled and REDIS_URL at startup
- Ensures proper initialization order for distributed deployments
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This adds a "Last Update" section below the navigation that shows:
- Relative time ("less than a minute ago")
- Exact UTC timestamp ("2025-07-25 19:04 UTC")
Updates automatically whenever LiveView receives packet updates or map bounds changes.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed GeoUtils.significant_movement? test: Updated test coordinates
to actually move 55 meters (over 50m threshold) instead of 11 meters
- Fixed MapLive movement notification tests: Changed packet maps to use
atom keys instead of string keys to match expected format
- Fixed JavaScript URL interpolation in layout template: Replaced
incorrect HEEx syntax with static paths for vendor bundles
- Made movement tests more resilient by using receive blocks instead
of assert_push_event for better reliability in test environment
All 376 tests now pass successfully with 0 failures.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Download external libraries locally to reduce CDN dependencies
- Create combined vendor bundles for JavaScript and CSS
- Replace CDN script tags with local bundles in layout template
- Add build scripts for vendor bundle creation and optimization
- Configure esbuild to process vendor bundles separately
- Total bundle size: ~512KB JS + 17KB CSS (optimized)
Benefits:
- Reduced external requests and CDN dependencies
- Better caching control and reliability
- Eliminated CORS and SRI integrity issues
- Faster page loads with bundled resources
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use the correct entry point from package.json (dist/oms.js) instead
of oms.min.js, and switch back to unpkg with the verified working
path to resolve content corruption issues.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Switch from unpkg.com to jsdelivr.net CDN to resolve MIME type
mismatch error. The unpkg CDN was serving the JavaScript file
with 'text/plain' content type instead of 'application/javascript'.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Both /status and /status.json endpoints now show cluster-wide APRS-IS
connection status instead of just local node status.
Features:
- get_cluster_aprs_status() checks all nodes in cluster via RPC
- Returns status from any connected node (usually the leader)
- Shows cluster info: total nodes, connected nodes, leader node
- Graceful fallback to local status in non-clustered mode
- Enhanced status page UI with cluster information display
This ensures status pages accurately reflect the true APRS-IS
connection state across the entire cluster.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The cleanup_stale_registrations function was calling Process.alive?
on remote PIDs, which causes an ArgumentError. Fixed by:
- Using Process.alive? only for local PIDs
- Using :rpc.call for remote PID liveness checks
- Adding proper error handling for RPC failures
- Cleaning up registrations for disconnected nodes
This prevents GenServer crashes when checking leader status.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Leader election now waits for other nodes to connect before starting
- Added cleanup of stale global registrations from disconnected nodes
- Only starts election after cluster is established in clustered mode
- Non-clustered mode continues to elect immediately
This prevents both nodes from becoming leaders when they start
before the cluster is fully formed.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The previous conflict resolution tried to call Process.info/2 on remote
PIDs, which doesn't work. Changed to use node name comparison for
deterministic leader selection across the cluster.
This ensures only one leader is elected when nodes join the cluster.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevent both AprsIsConnection and cluster-managed Is connection from
starting simultaneously. When clustering is enabled, only the cluster's
ConnectionManager should handle APRS-IS connections via leader election.
This resolves aprsc server disconnections caused by duplicate validated
clients with the same username (W5ISP-1).
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement leader election to ensure only one APRS-IS connection across
multiple Kubernetes replicas. This prevents duplicate packet processing
and respects APRS-IS usage policies.
Key changes:
- Add leader election using :global registry
- Create connection manager for dynamic APRS-IS management
- Implement packet distribution from leader to all nodes
- Add Kubernetes headless service for node discovery
- Configure DNS-based clustering with libcluster
- Update deployment to support 3 replicas with clustering
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added missing CSS for slideover panel with proper width and positioning
- Fixed overflow issues by adding box-sizing: border-box
- Adjusted Track button padding from px-6 to px-4 to prevent cutoff
- Added whitespace-nowrap and flex-shrink-0 to prevent button text wrapping
- Ensured proper panel width of 352px on desktop with transform animations
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added base CSS styles for slideover panel with proper positioning
- Increased right padding (pr-8) in content area to prevent button cutoff
- Set overflow to hidden on panel container to prevent scrollbar issues
- Ensures Track button and other content are fully visible
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed ArgumentError in rate limiter by replacing cond statement with
proper case pattern matching. The cond was incorrectly evaluating
assignment expressions as truthy values instead of checking if headers
were empty lists.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Updated IP geolocation to check CF-Connecting-IP header first
- Added support for X-Real-IP header as additional fallback
- Updated rate limiter to use same header priority for consistency
- Headers checked in order: CF-Connecting-IP > X-Forwarded-For > X-Real-IP > remote_ip
This ensures proper client IP detection when the site is behind Cloudflare.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace get_in/Access usage with proper struct pattern matching to fix
UndefinedFunctionError when accessing Sentry.Event fields. The struct
doesn't implement the Access behavior, so we now use pattern matching
and direct field access instead.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>