Dark navbar header with radio icon, refined slideover control panel with branded header and section cards, polished cards/tables/badges on info, packets, and about pages using ring borders and shadow-xs.
150 lines
7.1 KiB
Text
150 lines
7.1 KiB
Text
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 my-8">
|
|
<%= if @error do %>
|
|
<div class="rounded-md bg-red-50 p-4 dark:bg-red-500/10">
|
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
<div>
|
|
<h3 class="font-medium">Error</h3>
|
|
<div class="text-sm">
|
|
{@error}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white shadow-xs sm:rounded-lg ring-1 ring-gray-950/5 dark:bg-gray-800/50 dark:shadow-none dark:ring-white/10">
|
|
<div class="px-6 py-6">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead>
|
|
<tr>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Time
|
|
</th>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Sender
|
|
</th>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Data Type
|
|
</th>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Information
|
|
</th>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Path
|
|
</th>
|
|
<th class="sticky top-0 bg-white py-3 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:bg-gray-800/50 dark:text-gray-400">
|
|
Device
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
|
|
<%= for packet <- @all_packets do %>
|
|
<tr class="even:bg-gray-50 dark:even:bg-white/[0.025]">
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="text-sm font-mono text-gray-500 dark:text-gray-400">
|
|
<%= case packet.received_at do %>
|
|
<% %DateTime{} = dt -> %>
|
|
{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S")}
|
|
<% dt when is_binary(dt) -> %>
|
|
<%= case DateTime.from_iso8601(dt) do %>
|
|
<% {:ok, parsed_dt, _} -> %>
|
|
{Calendar.strftime(parsed_dt, "%Y-%m-%d %H:%M:%S")}
|
|
<% _ -> %>
|
|
{dt}
|
|
<% end %>
|
|
<% _ -> %>
|
|
N/A
|
|
<% end %>
|
|
</span>
|
|
</td>
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="text-sm font-medium">{packet.sender}</span>
|
|
</td>
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10 dark:bg-indigo-400/10 dark:text-indigo-400 dark:ring-indigo-400/30">
|
|
{packet.data_type}
|
|
</span>
|
|
</td>
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="text-sm font-mono text-gray-700 dark:text-gray-300">
|
|
<% info_field = (Map.get(packet, :data) || %{})["information_field"] || "" %>
|
|
<%= if String.length(info_field) > 50 do %>
|
|
<span title={info_field}>
|
|
{String.slice(info_field, 0, 50)}...
|
|
</span>
|
|
<% else %>
|
|
{info_field}
|
|
<% end %>
|
|
</span>
|
|
</td>
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
{packet.path}
|
|
</span>
|
|
</td>
|
|
<td class="whitespace-nowrap py-3 pr-3">
|
|
<span class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
<%= if packet.device_model || packet.device_vendor do %>
|
|
{[packet.device_model, packet.device_vendor]
|
|
|> Enum.reject(&is_nil/1)
|
|
|> Enum.join(" ")}
|
|
<% else %>
|
|
{Map.get(
|
|
packet,
|
|
:device_identifier,
|
|
Map.get(packet, "device_identifier", "")
|
|
)}
|
|
<% end %>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<%= if length(@all_packets) == 0 do %>
|
|
<div class="text-center py-12">
|
|
<div class="flex justify-center mb-4">
|
|
<svg class="w-16 h-16 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-lg font-medium mb-2">No packets found for {@callsign}</h3>
|
|
<p class="text-gray-500 dark:text-gray-400">
|
|
Packets will appear here as they are received live or if there are stored packets for this callsign.
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="bg-white shadow-xs sm:rounded-lg ring-1 ring-gray-950/5 dark:bg-gray-800/50 dark:shadow-none dark:ring-white/10 mt-4">
|
|
<div class="px-6 py-4">
|
|
<div class="flex items-center justify-between text-sm">
|
|
<div class="flex items-center space-x-4">
|
|
<span>
|
|
Live packets: <span class="font-medium">{length(@live_packets)}</span>
|
|
</span>
|
|
<span>
|
|
Stored packets: <span class="font-medium">{length(@packets)}</span>
|
|
</span>
|
|
<span>
|
|
Total: <span class="font-medium">{length(@all_packets)}/100</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|