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