{gettext("Last Packet:")}
<%= if @aprs_status.packet_stats.last_packet_at do %>
{format_time_ago(@aprs_status.packet_stats.last_packet_at)}
<% else %>
{gettext("None")}
<% end %>
<%= if Map.has_key?(@aprs_status.cluster_info, :all_nodes) do %>
{gettext("Cluster Nodes:")}
<%= for node_name <- @aprs_status.cluster_info.all_nodes do %>
<%= if node_name == @aprs_status.cluster_info.leader_node do %>
<% end %>
{node_name}
<% end %>
{gettext("Leader node")}
{gettext("highlighted")}
<% end %>
{gettext(
"Status is aggregated from all nodes in the cluster. Only the leader node maintains the APRS-IS connection."
)}
<% end %>
"""
end
# Private functions
defp get_aprs_status do
LeaderElection.get_cluster_aprs_status()
rescue
error ->
require Logger
Logger.error("Error getting APRS status: #{inspect(error)}")
# Return a default status when database is unavailable
default_status()
end
defp get_cached_aprs_status do
# Try to get cached status for instant load
case Aprsme.Cache.get(:query_cache, "aprs_status") do
{:ok, status} when not is_nil(status) ->
status
_ ->
# Fallback to direct query if cache miss
status = get_aprs_status()
Aprsme.Cache.put(:query_cache, "aprs_status", status)
status
end
end
defp default_status do
%{
connected: false,
server: "unknown",
port: 0,
connected_at: nil,
uptime_seconds: 0,
login_id: "unknown",
filter: "unknown",
packet_stats: %{
total_packets: 0,
packets_per_second: 0,
last_packet_at: nil
},
stored_packet_count: 0,
oldest_packet_timestamp: nil
}
end
defp schedule_refresh do
Process.send_after(self(), :refresh_status, @refresh_interval)
end
def format_uptime(seconds) when seconds <= 0, do: gettext("Not connected")
def format_uptime(seconds) do
days = div(seconds, 86_400)
hours = div(rem(seconds, 86_400), 3600)
minutes = div(rem(seconds, 3600), 60)
secs = rem(seconds, 60)
format_uptime_parts(days, hours, minutes, secs)
end
defp format_uptime_parts(d, h, m, s) when d > 0, do: "#{d}d #{h}h #{m}m #{s}s"
defp format_uptime_parts(_d, h, m, s) when h > 0, do: "#{h}h #{m}m #{s}s"
defp format_uptime_parts(_d, _h, m, s) when m > 0, do: "#{m}m #{s}s"
defp format_uptime_parts(_d, _h, _m, s), do: "#{s}s"
# Health score buckets: 1 = disconnected, 2–5 = connected by stability ramp.
defp calculate_health_score(%{connected: false}), do: 1
defp calculate_health_score(%{uptime_seconds: s}) when s < 300, do: 2
defp calculate_health_score(%{uptime_seconds: s}) when s < 3_600, do: 3
defp calculate_health_score(%{uptime_seconds: s}) when s < 86_400, do: 4
defp calculate_health_score(_), do: 5
def get_health_description(score, connected) do
case {score, connected} do
{1, false} -> gettext("Disconnected - Connection issues detected")
{2, true} -> gettext("Recently connected - Monitoring stability")
{3, true} -> gettext("Good - Connection stable for less than 1 hour")
{4, true} -> gettext("Very good - Connection stable for less than 1 day")
{5, true} -> gettext("Excellent - Long-term stable connection")
_ -> gettext("Unknown status")
end
end
def format_time_ago(nil), do: gettext("Never")
def format_time_ago(datetime) do
DateTime.utc_now() |> DateTime.diff(datetime) |> format_seconds_ago()
end
defp format_seconds_ago(s) when s < 60, do: gettext("%{count} seconds ago", count: s)
defp format_seconds_ago(s) when s < 3_600, do: gettext("%{count} minutes ago", count: div(s, 60))
defp format_seconds_ago(s) when s < 86_400, do: gettext("%{count} hours ago", count: div(s, 3_600))
defp format_seconds_ago(s), do: gettext("%{count} days ago", count: div(s, 86_400))
def format_number(number) when is_integer(number) do
number
|> Integer.to_string()
|> String.reverse()
|> String.replace(~r/(\d{3})(?=\d)/, "\\1,")
|> String.reverse()
end
def format_number(number), do: to_string(number)
end