97 lines
3.1 KiB
Elixir
97 lines
3.1 KiB
Elixir
defmodule MicrowavepropWeb.PskrSpotsLive do
|
||
@moduledoc "`/pskreporter` — recent PSK Reporter spot aggregates."
|
||
use MicrowavepropWeb, :live_view
|
||
|
||
import Ecto.Query
|
||
|
||
alias Microwaveprop.Format
|
||
alias Microwaveprop.Pskr.SpotHourly
|
||
alias Microwaveprop.Repo
|
||
|
||
@limit 100
|
||
|
||
@impl true
|
||
def mount(_params, _session, socket) do
|
||
spots = fetch_recent_spots()
|
||
|
||
{:ok,
|
||
assign(socket,
|
||
page_title: "PSK Reporter Spots",
|
||
limit: @limit,
|
||
spots: spots
|
||
)}
|
||
end
|
||
|
||
defp fetch_recent_spots do
|
||
SpotHourly
|
||
|> order_by(desc: :last_spot_at)
|
||
|> limit(@limit)
|
||
|> Repo.all()
|
||
end
|
||
|
||
defp fmt_dt(nil), do: "—"
|
||
defp fmt_dt(dt), do: Calendar.strftime(dt, "%Y-%m-%d %H:%M UTC")
|
||
|
||
defp fmt_snr(min, max) when min == max, do: "#{min} dB"
|
||
defp fmt_snr(min, max), do: "#{min}–#{max} dB"
|
||
|
||
defp fmt_callsigns([]), do: "—"
|
||
defp fmt_callsigns(calls), do: Enum.join(calls, ", ")
|
||
|
||
@impl true
|
||
def render(assigns) do
|
||
~H"""
|
||
<Layouts.app flash={@flash} current_scope={@current_scope} max_width="max-w-none">
|
||
<.header>
|
||
PSK Reporter Spots
|
||
<:subtitle>Last {@limit} spot aggregates from the MQTT firehose</:subtitle>
|
||
</.header>
|
||
|
||
<div class="overflow-x-auto">
|
||
<table class="table table-sm table-zebra whitespace-nowrap">
|
||
<thead>
|
||
<tr>
|
||
<th>Last Heard (UTC)</th>
|
||
<th>Band</th>
|
||
<th>From</th>
|
||
<th>From Grid</th>
|
||
<th>To</th>
|
||
<th>To Grid</th>
|
||
<th>Distance</th>
|
||
<th class="text-right">Spots</th>
|
||
<th>SNR</th>
|
||
<th>Modes</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<%= for spot <- @spots do %>
|
||
<tr>
|
||
<td class="text-xs font-mono">{fmt_dt(spot.last_spot_at)}</td>
|
||
<td class="text-xs"><span class="badge badge-sm">{spot.band}</span></td>
|
||
<td class="text-xs font-mono whitespace-nowrap">
|
||
{fmt_callsigns(spot.sender_callsigns)}
|
||
</td>
|
||
<td class="text-xs font-mono">{spot.sender_grid}</td>
|
||
<td class="text-xs font-mono whitespace-nowrap">
|
||
{fmt_callsigns(spot.receiver_callsigns)}
|
||
</td>
|
||
<td class="text-xs font-mono">{spot.receiver_grid}</td>
|
||
<td class="text-xs font-mono">{Format.distance_km(spot.distance_km)}</td>
|
||
<td class="text-xs text-right font-mono">{Format.number(spot.spot_count)}</td>
|
||
<td class="text-xs font-mono">{fmt_snr(spot.min_snr_db, spot.max_snr_db)}</td>
|
||
<td class="text-xs">
|
||
<span :for={mode <- spot.modes} class="badge badge-xs mr-0.5">{mode}</span>
|
||
</td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p :if={@spots == []} class="text-sm text-base-content/50 italic mt-4">
|
||
No PSK Reporter spots received yet. The MQTT listener is running — spots will appear here as they arrive.
|
||
</p>
|
||
</Layouts.app>
|
||
"""
|
||
end
|
||
end
|