From 534bd8df8a46b81b87100429513d80bbdbfc35fb Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 15 Jul 2025 13:56:01 -0500 Subject: [PATCH] clean up status page --- lib/aprsme/is/is.ex | 9 ++-- lib/aprsme/packets.ex | 12 +++++ lib/aprsme_web/live/status_live/index.ex | 51 +++++++++--------- test/aprsme/packets_oldest_test.exs | 66 ++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 test/aprsme/packets_oldest_test.exs diff --git a/lib/aprsme/is/is.ex b/lib/aprsme/is/is.ex index 333a3a5..049a848 100644 --- a/lib/aprsme/is/is.ex +++ b/lib/aprsme/is/is.ex @@ -133,7 +133,8 @@ defmodule Aprsme.Is do login_id: Application.get_env(:aprsme, :aprsme_is_login_id, "W5ISP"), filter: Application.get_env(:aprsme, :aprsme_is_default_filter, "r/33/-96/100"), packet_stats: default_packet_stats(), - stored_packet_count: Aprsme.Packets.get_total_packet_count() + stored_packet_count: Aprsme.Packets.get_total_packet_count(), + oldest_packet_timestamp: Aprsme.Packets.get_oldest_packet_timestamp() } _pid -> @@ -154,7 +155,8 @@ defmodule Aprsme.Is do login_id: Application.get_env(:aprsme, :aprsme_is_login_id, "W5ISP"), filter: Application.get_env(:aprsme, :aprsme_is_default_filter, "r/33/-96/100"), packet_stats: default_packet_stats(), - stored_packet_count: Aprsme.Packets.get_total_packet_count() + stored_packet_count: Aprsme.Packets.get_total_packet_count(), + oldest_packet_timestamp: Aprsme.Packets.get_oldest_packet_timestamp() } end end @@ -259,7 +261,8 @@ defmodule Aprsme.Is do login_id: state.login_params.user_id, filter: state.login_params.filter, packet_stats: state.packet_stats, - stored_packet_count: Aprsme.Packets.get_total_packet_count() + stored_packet_count: Aprsme.Packets.get_total_packet_count(), + oldest_packet_timestamp: Aprsme.Packets.get_oldest_packet_timestamp() } {:reply, status, state} diff --git a/lib/aprsme/packets.ex b/lib/aprsme/packets.ex index 9c7b15e..9d3aab9 100644 --- a/lib/aprsme/packets.ex +++ b/lib/aprsme/packets.ex @@ -638,6 +638,18 @@ defmodule Aprsme.Packets do 0 end + @doc """ + Gets the timestamp of the oldest stored packet in the database. + Returns nil if no packets exist. + """ + @spec get_oldest_packet_timestamp() :: DateTime.t() | nil + def get_oldest_packet_timestamp do + Repo.one( + from p in Packet, + select: min(p.received_at) + ) + end + @doc """ Configure packet retention policy. diff --git a/lib/aprsme_web/live/status_live/index.ex b/lib/aprsme_web/live/status_live/index.ex index ce47430..e1ffeed 100644 --- a/lib/aprsme_web/live/status_live/index.ex +++ b/lib/aprsme_web/live/status_live/index.ex @@ -16,7 +16,6 @@ defmodule AprsmeWeb.StatusLive.Index do assign(socket, page_title: "System Status", aprs_status: aprs_status, - version: get_app_version(), current_time: DateTime.utc_now(), health_score: calculate_health_score(aprs_status), loading: false @@ -83,23 +82,7 @@ defmodule AprsmeWeb.StatusLive.Index do {Calendar.strftime(@current_time, "%H:%M:%S UTC")} - - -
-

{gettext("Application Information")}

-
-
-
-
- {gettext("Version:")} - {@version} -
-
-
-
-
- - + <%= if not @aprs_status.connected do %>
+ +
+
+ + {gettext("Oldest Packet:")} + + + <%= if @aprs_status[:oldest_packet_timestamp] do %> + + {Calendar.strftime( + @aprs_status.oldest_packet_timestamp, + "%Y-%m-%d %H:%M:%S UTC" + )} + + + ({format_time_ago(@aprs_status.oldest_packet_timestamp)}) + + <% else %> + {gettext("No packets stored")} + <% end %> + +
+
@@ -320,14 +326,11 @@ defmodule AprsmeWeb.StatusLive.Index do packets_per_second: 0, last_packet_at: nil }, - stored_packet_count: 0 + stored_packet_count: 0, + oldest_packet_timestamp: nil } end - defp get_app_version do - :aprsme |> Application.spec(:vsn) |> List.to_string() - end - defp schedule_refresh do Process.send_after(self(), :refresh_status, @refresh_interval) end @@ -373,6 +376,8 @@ defmodule AprsmeWeb.StatusLive.Index do end end + def format_time_ago(nil), do: gettext("Never") + def format_time_ago(datetime) do diff_seconds = DateTime.diff(DateTime.utc_now(), datetime) diff --git a/test/aprsme/packets_oldest_test.exs b/test/aprsme/packets_oldest_test.exs new file mode 100644 index 0000000..5efb7b4 --- /dev/null +++ b/test/aprsme/packets_oldest_test.exs @@ -0,0 +1,66 @@ +defmodule Aprsme.PacketsOldestTest do + use Aprsme.DataCase + + alias Aprsme.Packets + alias Aprsme.Repo + + describe "get_oldest_packet_timestamp/0" do + test "returns nil when no packets exist" do + assert is_nil(Packets.get_oldest_packet_timestamp()) + end + + test "returns the timestamp of the oldest packet" do + # Create packets with different timestamps + oldest_time = ~U[2023-01-01 00:00:00Z] + middle_time = ~U[2023-06-15 12:30:00Z] + newest_time = ~U[2023-12-31 23:59:59Z] + + # Insert packets with different timestamps + {:ok, _} = create_test_packet("OLD-1", oldest_time) + {:ok, _} = create_test_packet("MID-1", middle_time) + {:ok, _} = create_test_packet("NEW-1", newest_time) + + # Should return the oldest timestamp + assert Packets.get_oldest_packet_timestamp() == oldest_time + end + + test "handles packets with microsecond precision" do + # Create a packet with microsecond precision + timestamp_with_microseconds = ~U[2023-07-15 14:30:45.123456Z] + {:ok, _} = create_test_packet("TEST-1", timestamp_with_microseconds) + + result = Packets.get_oldest_packet_timestamp() + # Database might truncate microseconds, so we check the timestamp is close + assert DateTime.diff(result, timestamp_with_microseconds, :microsecond) < 1000 + end + end + + # Helper function to create a test packet with specific timestamp + defp create_test_packet(callsign, received_at) do + # Extract SSID from callsign (e.g., "TEST-1" -> ssid = "1") + {base_callsign, ssid} = + case String.split(callsign, "-", parts: 2) do + [base] -> {base, "0"} + [base, ssid_part] -> {base, ssid_part} + end + + packet_data = %{ + sender: callsign, + base_callsign: base_callsign, + ssid: ssid, + destination: "APRS", + data_type: "position", + path: "WIDE1-1", + information_field: "!3216.46N/09647.82W>Test packet", + raw_packet: "#{callsign}>APRS,WIDE1-1:!3216.46N/09647.82W>Test packet", + lat: 32.274333, + lon: -96.797, + has_position: true, + received_at: received_at + } + + %Aprsme.Packet{} + |> Aprsme.Packet.changeset(packet_data) + |> Repo.insert() + end +end