add decoded path and other ssids
This commit is contained in:
parent
abdbe399f0
commit
f47b3d9f09
5 changed files with 459 additions and 27 deletions
|
|
@ -21,6 +21,7 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
packet = enrich_packet_with_device_info(packet)
|
||||
neighbors = get_neighbors(packet, normalized_callsign)
|
||||
has_weather_packets = PacketUtils.has_weather_packets?(normalized_callsign)
|
||||
other_ssids = get_other_ssids(normalized_callsign)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|
|
@ -29,6 +30,7 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
|> assign(:neighbors, neighbors)
|
||||
|> assign(:page_title, "APRS station #{normalized_callsign}")
|
||||
|> assign(:has_weather_packets, has_weather_packets)
|
||||
|> assign(:other_ssids, other_ssids)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
|
@ -42,12 +44,14 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
packet = enrich_packet_with_device_info(packet)
|
||||
neighbors = get_neighbors(packet, socket.assigns.callsign)
|
||||
has_weather_packets = PacketUtils.has_weather_packets?(socket.assigns.callsign)
|
||||
other_ssids = get_other_ssids(socket.assigns.callsign)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:packet, packet)
|
||||
|> assign(:neighbors, neighbors)
|
||||
|> assign(:has_weather_packets, has_weather_packets)
|
||||
|> assign(:other_ssids, other_ssids)
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
|
|
@ -63,9 +67,9 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
end
|
||||
|
||||
defp get_latest_packet(callsign) do
|
||||
%{callsign: callsign, limit: 1}
|
||||
|> Packets.get_recent_packets()
|
||||
|> List.first()
|
||||
# Get the most recent packet for this callsign, regardless of type
|
||||
# This ensures we show the most recent activity, not just position packets
|
||||
Packets.get_latest_packet_for_callsign(callsign)
|
||||
end
|
||||
|
||||
defp enrich_packet_with_device_info(nil), do: nil
|
||||
|
|
@ -145,6 +149,12 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
|
||||
defp haversine(lat1, lon1, lat2, lon2) do
|
||||
# Returns distance in km
|
||||
# Convert Decimal to float if needed
|
||||
lat1 = to_float(lat1)
|
||||
lon1 = to_float(lon1)
|
||||
lat2 = to_float(lat2)
|
||||
lon2 = to_float(lon2)
|
||||
|
||||
r = 6371
|
||||
dlat = :math.pi() / 180 * (lat2 - lat1)
|
||||
dlon = :math.pi() / 180 * (lon2 - lon1)
|
||||
|
|
@ -158,6 +168,19 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
r * c
|
||||
end
|
||||
|
||||
defp to_float(%Decimal{} = decimal), do: Decimal.to_float(decimal)
|
||||
defp to_float(value) when is_float(value), do: value
|
||||
defp to_float(value) when is_integer(value), do: value * 1.0
|
||||
|
||||
defp to_float(value) when is_binary(value) do
|
||||
case Float.parse(value) do
|
||||
{f, _} -> f
|
||||
:error -> 0.0
|
||||
end
|
||||
end
|
||||
|
||||
defp to_float(_), do: 0.0
|
||||
|
||||
defp format_distance(km) when km < 1.0 do
|
||||
"#{Float.round(km * 1000, 0)} m"
|
||||
end
|
||||
|
|
@ -168,6 +191,12 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
|
||||
defp calculate_course(lat1, lon1, lat2, lon2) do
|
||||
# Calculate bearing from point 1 to point 2
|
||||
# Convert Decimal to float if needed
|
||||
lat1 = to_float(lat1)
|
||||
lon1 = to_float(lon1)
|
||||
lat2 = to_float(lat2)
|
||||
lon2 = to_float(lon2)
|
||||
|
||||
dlon = :math.pi() / 180 * (lon2 - lon1)
|
||||
|
||||
lat1_rad = :math.pi() / 180 * lat1
|
||||
|
|
@ -180,4 +209,278 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
# Convert to 0-360 range
|
||||
if bearing < 0, do: bearing + 360, else: bearing
|
||||
end
|
||||
|
||||
defp get_other_ssids(callsign) do
|
||||
import Ecto.Query
|
||||
alias Aprsme.Packet
|
||||
alias Aprsme.Repo
|
||||
# Extract base callsign from the full callsign (remove SSID if present)
|
||||
base_callsign = extract_base_callsign(callsign)
|
||||
|
||||
# Query directly for packets with the same base_callsign
|
||||
|
||||
# Get recent packets for the base callsign to find other SSIDs
|
||||
one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second)
|
||||
|
||||
query =
|
||||
from p in Packet,
|
||||
where: p.base_callsign == ^base_callsign,
|
||||
where: p.received_at >= ^one_hour_ago,
|
||||
order_by: [desc: p.received_at],
|
||||
limit: 100
|
||||
|
||||
query
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn p ->
|
||||
%{
|
||||
callsign: p.sender,
|
||||
ssid: p.ssid,
|
||||
last_heard: PacketUtils.get_timestamp(p),
|
||||
packet: p
|
||||
}
|
||||
end)
|
||||
|> uniq_by(& &1.callsign)
|
||||
|> Enum.filter(fn ssid_info -> ssid_info.callsign != callsign end)
|
||||
|> Enum.sort_by(& &1.last_heard, :desc)
|
||||
|> Enum.take(10)
|
||||
end
|
||||
|
||||
defp extract_base_callsign(callsign) do
|
||||
case String.split(callsign, "-") do
|
||||
[base, _ssid] -> base
|
||||
[base] -> base
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_aprs_path(path) when is_binary(path) and path != "" do
|
||||
path_elements = String.split(path, ",")
|
||||
|
||||
decoded_elements = Enum.map(path_elements, &decode_path_element/1)
|
||||
|
||||
# Filter out nil results and join with explanations
|
||||
decoded_elements
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.join(" → ")
|
||||
end
|
||||
|
||||
defp decode_aprs_path(_), do: nil
|
||||
|
||||
defp decode_path_element(element) do
|
||||
element = String.trim(element)
|
||||
|
||||
cond do
|
||||
# WIDE digipeaters
|
||||
String.starts_with?(element, "WIDE") ->
|
||||
case element do
|
||||
"WIDE1-1" -> "WIDE1-1 (Wide area digipeater, 1 hop)"
|
||||
"WIDE2-1" -> "WIDE2-1 (Wide area digipeater, 2 hops)"
|
||||
"WIDE3-1" -> "WIDE3-1 (Wide area digipeater, 3 hops)"
|
||||
"WIDE4-1" -> "WIDE4-1 (Wide area digipeater, 4 hops)"
|
||||
"WIDE5-1" -> "WIDE5-1 (Wide area digipeater, 5 hops)"
|
||||
"WIDE6-1" -> "WIDE6-1 (Wide area digipeater, 6 hops)"
|
||||
"WIDE7-1" -> "WIDE7-1 (WIDE area digipeater, 7 hops)"
|
||||
"WIDE1-2" -> "WIDE1-2 (Wide area digipeater, 1 hop, 2nd attempt)"
|
||||
"WIDE2-2" -> "WIDE2-2 (Wide area digipeater, 2 hops, 2nd attempt)"
|
||||
_ -> "WIDE digipeater (#{element})"
|
||||
end
|
||||
|
||||
# TRACE digipeaters
|
||||
String.starts_with?(element, "TRACE") ->
|
||||
case element do
|
||||
"TRACE1-1" -> "TRACE1-1 (Trace digipeater, 1 hop)"
|
||||
"TRACE2-1" -> "TRACE2-1 (Trace digipeater, 2 hops)"
|
||||
"TRACE3-1" -> "TRACE3-1 (Trace digipeater, 3 hops)"
|
||||
"TRACE4-1" -> "TRACE4-1 (Trace digipeater, 4 hops)"
|
||||
"TRACE5-1" -> "TRACE5-1 (Trace digipeater, 5 hops)"
|
||||
"TRACE6-1" -> "TRACE6-1 (Trace digipeater, 6 hops)"
|
||||
"TRACE7-1" -> "TRACE7-1 (Trace digipeater, 7 hops)"
|
||||
_ -> "TRACE digipeater (#{element})"
|
||||
end
|
||||
|
||||
# RELAY digipeaters
|
||||
String.starts_with?(element, "RELAY") ->
|
||||
case element do
|
||||
"RELAY" -> "RELAY (Relay digipeater)"
|
||||
"RELAY-1" -> "RELAY-1 (Relay digipeater, 1 hop)"
|
||||
"RELAY-2" -> "RELAY-2 (Relay digipeater, 2 hops)"
|
||||
_ -> "RELAY digipeater (#{element})"
|
||||
end
|
||||
|
||||
# qAC (APRS-IS connection)
|
||||
element == "qAC" ->
|
||||
"qAC (APRS-IS connection)"
|
||||
|
||||
# qAO (APRS-IS origin)
|
||||
element == "qAO" ->
|
||||
"qAO (APRS-IS origin)"
|
||||
|
||||
# qAR (APRS-IS relay)
|
||||
element == "qAR" ->
|
||||
"qAR (APRS-IS relay)"
|
||||
|
||||
# qAS (APRS-IS server)
|
||||
element == "qAS" ->
|
||||
"qAS (APRS-IS server)"
|
||||
|
||||
# qAX (APRS-IS client)
|
||||
element == "qAX" ->
|
||||
"qAX (APRS-IS client)"
|
||||
|
||||
# qAY (APRS-IS gateway)
|
||||
element == "qAY" ->
|
||||
"qAY (APRS-IS gateway)"
|
||||
|
||||
# qAZ (APRS-IS zone)
|
||||
element == "qAZ" ->
|
||||
"qAZ (APRS-IS zone)"
|
||||
|
||||
# qBU (APRS-IS user)
|
||||
element == "qBU" ->
|
||||
"qBU (APRS-IS user)"
|
||||
|
||||
# qBV (APRS-IS vendor)
|
||||
element == "qBV" ->
|
||||
"qBV (APRS-IS vendor)"
|
||||
|
||||
# qBW (APRS-IS web)
|
||||
element == "qBW" ->
|
||||
"qBW (APRS-IS web)"
|
||||
|
||||
# qBX (APRS-IS experimental)
|
||||
element == "qBX" ->
|
||||
"qBX (APRS-IS experimental)"
|
||||
|
||||
# qBY (APRS-IS Y2K)
|
||||
element == "qBY" ->
|
||||
"qBY (APRS-IS Y2K)"
|
||||
|
||||
# qBZ (APRS-IS Zulu)
|
||||
element == "qBZ" ->
|
||||
"qBZ (APRS-IS Zulu)"
|
||||
|
||||
# qCA (APRS-IS client application)
|
||||
element == "qCA" ->
|
||||
"qCA (APRS-IS client application)"
|
||||
|
||||
# qCB (APRS-IS client browser)
|
||||
element == "qCB" ->
|
||||
"qCB (APRS-IS client browser)"
|
||||
|
||||
# qCC (APRS-IS client console)
|
||||
element == "qCC" ->
|
||||
"qCC (APRS-IS client console)"
|
||||
|
||||
# qCD (APRS-IS client daemon)
|
||||
element == "qCD" ->
|
||||
"qCD (APRS-IS client daemon)"
|
||||
|
||||
# qCE (APRS-IS client editor)
|
||||
element == "qCE" ->
|
||||
"qCE (APRS-IS client editor)"
|
||||
|
||||
# qCF (APRS-IS client filter)
|
||||
element == "qCF" ->
|
||||
"qCF (APRS-IS client filter)"
|
||||
|
||||
# qCG (APRS-IS client gateway)
|
||||
element == "qCG" ->
|
||||
"qCG (APRS-IS client gateway)"
|
||||
|
||||
# qCH (APRS-IS client host)
|
||||
element == "qCH" ->
|
||||
"qCH (APRS-IS client host)"
|
||||
|
||||
# qCI (APRS-IS client interface)
|
||||
element == "qCI" ->
|
||||
"qCI (APRS-IS client interface)"
|
||||
|
||||
# qCJ (APRS-IS client java)
|
||||
element == "qCJ" ->
|
||||
"qCJ (APRS-IS client java)"
|
||||
|
||||
# qCK (APRS-IS client kernel)
|
||||
element == "qCK" ->
|
||||
"qCK (APRS-IS client kernel)"
|
||||
|
||||
# qCL (APRS-IS client library)
|
||||
element == "qCL" ->
|
||||
"qCL (APRS-IS client library)"
|
||||
|
||||
# qCM (APRS-IS client module)
|
||||
element == "qCM" ->
|
||||
"qCM (APRS-IS client module)"
|
||||
|
||||
# qCN (APRS-IS client network)
|
||||
element == "qCN" ->
|
||||
"qCN (APRS-IS client network)"
|
||||
|
||||
# qCO (APRS-IS client object)
|
||||
element == "qCO" ->
|
||||
"qCO (APRS-IS client object)"
|
||||
|
||||
# qCP (APRS-IS client protocol)
|
||||
element == "qCP" ->
|
||||
"qCP (APRS-IS client protocol)"
|
||||
|
||||
# qCQ (APRS-IS client query)
|
||||
element == "qCQ" ->
|
||||
"qCQ (APRS-IS client query)"
|
||||
|
||||
# qCR (APRS-IS client router)
|
||||
element == "qCR" ->
|
||||
"qCR (APRS-IS client router)"
|
||||
|
||||
# qCS (APRS-IS client server)
|
||||
element == "qCS" ->
|
||||
"qCS (APRS-IS client server)"
|
||||
|
||||
# qCT (APRS-IS client terminal)
|
||||
element == "qCT" ->
|
||||
"qCT (APRS-IS client terminal)"
|
||||
|
||||
# qCU (APRS-IS client user)
|
||||
element == "qCU" ->
|
||||
"qCU (APRS-IS client user)"
|
||||
|
||||
# qCV (APRS-IS client vendor)
|
||||
element == "qCV" ->
|
||||
"qCV (APRS-IS client vendor)"
|
||||
|
||||
# qCW (APRS-IS client web)
|
||||
element == "qCW" ->
|
||||
"qCW (APRS-IS client web)"
|
||||
|
||||
# qCX (APRS-IS client experimental)
|
||||
element == "qCX" ->
|
||||
"qCX (APRS-IS client experimental)"
|
||||
|
||||
# qCY (APRS-IS client Y2K)
|
||||
element == "qCY" ->
|
||||
"qCY (APRS-IS client Y2K)"
|
||||
|
||||
# qCZ (APRS-IS client Zulu)
|
||||
element == "qCZ" ->
|
||||
"qCZ (APRS-IS client Zulu)"
|
||||
|
||||
# TCPIP digipeaters
|
||||
String.starts_with?(element, "TCPIP") ->
|
||||
case element do
|
||||
"TCPIP" -> "TCPIP (Internet gateway)"
|
||||
"TCPIP*" -> "TCPIP* (Internet gateway, no forward)"
|
||||
_ -> "TCPIP gateway (#{element})"
|
||||
end
|
||||
|
||||
# Generic callsign with SSID (likely a digipeater)
|
||||
Regex.match?(~r/^[A-Z0-9]+-\d+$/, element) ->
|
||||
"#{element} (Digipeater)"
|
||||
|
||||
# Generic callsign without SSID
|
||||
Regex.match?(~r/^[A-Z0-9]+$/, element) ->
|
||||
"#{element} (Station)"
|
||||
|
||||
# Default case
|
||||
true ->
|
||||
"#{element} (Unknown)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -116,10 +116,32 @@
|
|||
{@packet.lat || ""}, {@packet.lon || ""}
|
||||
</dd>
|
||||
</div>
|
||||
<%= if @packet.lat && @packet.lon do %>
|
||||
<div>
|
||||
<dt class="text-xs font-medium opacity-70">Grid Square</dt>
|
||||
<dd class="mt-1 text-sm font-semibold">
|
||||
<%= case Gridsquare.encode(@packet.lon, @packet.lat) do %>
|
||||
<% %Gridsquare.EncodeResult{grid_reference: grid_ref} -> %>
|
||||
{grid_ref}
|
||||
<% _ -> %>
|
||||
-
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
<div>
|
||||
<dt class="text-xs font-medium opacity-70">Last Position</dt>
|
||||
<dd class="mt-1 text-sm font-semibold">
|
||||
{AprsmeWeb.MapLive.PacketUtils.get_timestamp(@packet)}
|
||||
<dd class="mt-1 text-sm">
|
||||
<%= if @packet.received_at do %>
|
||||
<div class="font-semibold">
|
||||
{AprsmeWeb.TimeHelpers.time_ago_in_words(@packet.received_at)}
|
||||
</div>
|
||||
<div class="text-xs opacity-70 font-mono">
|
||||
{Calendar.strftime(@packet.received_at, "%Y-%m-%d %H:%M:%S UTC")}
|
||||
</div>
|
||||
<% else %>
|
||||
<span class="opacity-70">Unknown</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<%= if @packet.altitude do %>
|
||||
|
|
@ -193,6 +215,14 @@
|
|||
<dt class="text-xs font-medium opacity-70">Path</dt>
|
||||
<dd class="mt-1 text-sm font-semibold">{@packet.path || ""}</dd>
|
||||
</div>
|
||||
<%= if @packet.path && @packet.path != "" do %>
|
||||
<div class="col-span-2">
|
||||
<dt class="text-xs font-medium opacity-70">Decoded Path</dt>
|
||||
<dd class="mt-1 text-sm">
|
||||
{decode_aprs_path(@packet.path)}
|
||||
</dd>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="col-span-2">
|
||||
<dt class="text-xs font-medium opacity-70">Raw Packet</dt>
|
||||
<dd class="mt-1 text-xs font-mono break-all">
|
||||
|
|
@ -208,6 +238,125 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Other SSIDs -->
|
||||
<%= if length(@other_ssids) > 0 do %>
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
<svg
|
||||
class="h-4 w-4 opacity-70"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<h3 class="text-sm font-medium">Other SSIDs</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-xs font-medium uppercase tracking-wider">
|
||||
Callsign
|
||||
</th>
|
||||
<th class="text-xs font-medium uppercase tracking-wider">
|
||||
Distance & Direction
|
||||
</th>
|
||||
<th class="text-xs font-medium uppercase tracking-wider">
|
||||
Last Heard
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for ssid_info <- @other_ssids do %>
|
||||
<tr>
|
||||
<td class="font-medium">
|
||||
<div class="flex items-center space-x-2">
|
||||
<.link
|
||||
navigate={~p"/info/#{ssid_info.callsign}"}
|
||||
class="link link-primary"
|
||||
>
|
||||
{ssid_info.callsign}
|
||||
</.link>
|
||||
<%= if ssid_info.packet do %>
|
||||
<% {symbol_table_id, symbol_code} =
|
||||
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(ssid_info.packet) %>
|
||||
<% symbol_table =
|
||||
if symbol_table_id in ["/", "\\", "]"],
|
||||
do: symbol_table_id,
|
||||
else: "/" %>
|
||||
<% symbol_code = symbol_code || ">" %>
|
||||
<% table_id =
|
||||
if symbol_table == "/",
|
||||
do: "0",
|
||||
else: if(symbol_table == "]", do: "2", else: "1") %>
|
||||
<% symbol_code_ord =
|
||||
symbol_code
|
||||
|> String.to_charlist()
|
||||
|> List.first()
|
||||
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
|
||||
<% index = symbol_code_ord - 33 %>
|
||||
<% safe_index = max(0, min(index, 93)) %>
|
||||
<% column = rem(safe_index, 16) %>
|
||||
<% row = div(safe_index, 16) %>
|
||||
<% x = -column * 128 %>
|
||||
<% y = -row * 128 %>
|
||||
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
|
||||
<div
|
||||
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
|
||||
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
|
||||
>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="opacity-70">
|
||||
<%= if ssid_info.packet && ssid_info.packet.lat && ssid_info.packet.lon && @packet && @packet.lat && @packet.lon do %>
|
||||
<% dist =
|
||||
haversine(
|
||||
@packet.lat,
|
||||
@packet.lon,
|
||||
ssid_info.packet.lat,
|
||||
ssid_info.packet.lon
|
||||
) %>
|
||||
<% course =
|
||||
calculate_course(
|
||||
@packet.lat,
|
||||
@packet.lon,
|
||||
ssid_info.packet.lat,
|
||||
ssid_info.packet.lon
|
||||
) %>
|
||||
<%= if course do %>
|
||||
{format_distance(dist)} @ {Float.round(course, 0)}°
|
||||
<% else %>
|
||||
{format_distance(dist)}
|
||||
<% end %>
|
||||
<% else %>
|
||||
-
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="opacity-70">
|
||||
{ssid_info.last_heard || ""}
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Neighboring stations -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
|
|
|
|||
|
|
@ -208,28 +208,6 @@ defmodule AprsmeWeb.StatusLive.Index do
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Information -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Application Information</h2>
|
||||
<div class="card bg-base-200">
|
||||
<div class="card-body">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">Version:</span>
|
||||
<span class="text-sm font-mono">{@version}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium opacity-70 mr-2">Current Time:</span>
|
||||
<span class="text-sm font-mono">
|
||||
{Calendar.strftime(@current_time, "%Y-%m-%d %H:%M:%S UTC")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
1
mix.exs
1
mix.exs
|
|
@ -82,6 +82,7 @@ defmodule Aprsme.MixProject do
|
|||
github: "tailwindlabs/heroicons", tag: "v2.1.1", sparse: "optimized", app: false, compile: false, depth: 1},
|
||||
{:bandit, "~> 1.5"},
|
||||
{:req, "~> 0.5"},
|
||||
{:gridsquare, "~> 0.2.0"},
|
||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||
{:dialyxir, "~> 1.0", only: :dev, runtime: false},
|
||||
{:exvcr, "~> 0.15", only: :test},
|
||||
|
|
|
|||
1
mix.lock
1
mix.lock
|
|
@ -26,6 +26,7 @@
|
|||
"geo_postgis": {:hex, :geo_postgis, "3.7.1", "614f25b42334a615bd54bb09c22030b1aac7bac8f829bd823ab1faccf093a324", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:geo, "~> 3.6 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "c20d823c600d35b7fe9ddd5be03052bb7136c57d6f1775dbd46871545e405280"},
|
||||
"geocalc": {:hex, :geocalc, "0.8.5", "b9886679e44c323e5b72dcd90a64f834d775d2600af0b656ea9f07ccdacaa5a6", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3870c25c78513ec0456b69324c2be1af2202961002e81fb659559e3db162c802"},
|
||||
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
|
||||
"gridsquare": {:hex, :gridsquare, "0.2.0", "c556a5b5101db89743264b0d728601023035863487bbe36e9e50e93839adb4d7", [:mix], [], "hexpm", "dbf0dbb484681a1e97819b0667bfeaa8c8a48cf06a54bf96a8f26ee4158ad31a"},
|
||||
"hackney": {:hex, :hackney, "1.24.1", "f5205a125bba6ed4587f9db3cc7c729d11316fa8f215d3e57ed1c067a9703fa9", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "f4a7392a0b53d8bbc3eb855bdcc919cd677358e65b2afd3840b5b3690c4c8a39"},
|
||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue