Simplify complex functions to improve readability and maintainability:
- clustering.ex: Convert case statement to map lookup for cluster radii
- encoding.ex: Extract codepoint validation into separate function clauses
- leader_election.ex: Extract PID liveness checking and cleanup logic into helpers
Co-Authored-By: Graham <noreply@anthropic.com>
- Fix length/1 check in mobile_channel.ex
- Add LeaderElection alias in connection_manager.ex
All warnings and design suggestions now complete!
Remaining: 40 refactoring opportunities (complex/nested functions)
- Add Phoenix.LiveViewTest import to ConnCase to fix warnings
- Disable AppSignal in dev and test (only enable in prod)
- Explicitly disable clustering in test environment
- Fix PacketDistributor to check cluster_enabled before calling LeaderElection
- Add Phoenix.CodeReloader listener to mix.exs
This prevents tests from trying to call cluster-specific GenServers
(ConnectionMonitor, LeaderElection) that don't exist in test mode.
Previously, when clustering was enabled, the leader election process
would wait indefinitely for the cluster to form. If cluster formation
failed or took too long, no leader would be elected and the APRS-IS
connection would never start, leaving the system disconnected.
Changes:
- Add 30-second timeout for cluster formation
- Force leader election after timeout, even in single-node mode
- Add election_forced flag to prevent duplicate election attempts
- Improve logging to distinguish between cluster and single-node modes
This ensures that at least one node will always connect to APRS-IS,
even if clustering fails completely.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes compilation warning by moving the catch-all handle_info/2 clause
to be immediately after the specific handle_info/2 clauses.
In Elixir, all function clauses with the same name and arity must be
grouped together for pattern matching to work correctly.
This resolves the 'mix compile --warnings-as-errors' failure.
- Fix leader election guard test for :undefined pid
- Fix cpu_sup.util() pattern matching to handle numeric return
- Fix encoding_utils to always return explicit nil when needed
- Remove unused finite_float? function
- Fix signal handler to match :ok return from :os.set_signal/2
- Remove redundant catch-all pattern in database metrics
- Fix Gridsquare pattern matching in info_live template
- Remove unnecessary nil check for calculate_course result
- Fix get_packet_received_at to not check for nil (always returns DateTime)
- Remove redundant catch-all pattern in weather format_weather_value
- Fix query builder to use from(p in Packet) instead of bare Packet atom
Reduced dialyzer errors from 49 to 6. Remaining warnings are mostly
false positives from template compilation and overloaded function specs.
🤖 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>
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>
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>