Apply TailwindCSS Plus design patterns across site
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.
This commit is contained in:
parent
ab356afe43
commit
0061c4b169
6 changed files with 458 additions and 375 deletions
|
|
@ -433,22 +433,30 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
|
||||
def header(assigns) do
|
||||
~H"""
|
||||
<header class="site-header bg-white shadow-sm dark:bg-gray-800/50 dark:shadow-none dark:border-b dark:border-white/10">
|
||||
<header class="site-header relative bg-gray-800 dark:bg-gray-800/50 dark:after:pointer-events-none dark:after:absolute dark:after:inset-x-0 dark:after:bottom-0 dark:after:h-px dark:after:bg-white/10">
|
||||
<nav class="mx-auto flex max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center">
|
||||
<.link
|
||||
navigate="/"
|
||||
class="text-xl font-bold text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
class="flex items-center gap-2 text-xl font-bold text-white hover:text-gray-200"
|
||||
>
|
||||
<svg class="h-6 w-6 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
|
||||
/>
|
||||
</svg>
|
||||
aprs.me
|
||||
<%= if @dev_mode do %>
|
||||
<span class="ml-2 inline-flex items-center rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-800 dark:bg-yellow-400/10 dark:text-yellow-500">
|
||||
<span class="ml-1 inline-flex items-center rounded-md bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-500 ring-1 ring-inset ring-yellow-400/20">
|
||||
DEV
|
||||
</span>
|
||||
<% end %>
|
||||
</.link>
|
||||
</div>
|
||||
<div class="hidden lg:flex lg:items-center lg:gap-x-8">
|
||||
<div class="hidden lg:flex lg:items-center lg:gap-x-1">
|
||||
<.navigation variant={:horizontal} current_user={@current_user} />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -458,15 +466,20 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
phx-click={
|
||||
JS.toggle(to: "#mobile-menu", in: "transition ease-out duration-100", out: "transition ease-in duration-75")
|
||||
}
|
||||
class="rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
class="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-white/5 hover:text-white focus:outline-2 focus:-outline-offset-1 focus:outline-indigo-500"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id="mobile-menu"
|
||||
class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-black/5 dark:bg-gray-800 dark:ring-white/10"
|
||||
class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-gray-800 py-2 shadow-lg ring-1 ring-white/10"
|
||||
>
|
||||
<.navigation variant={:vertical} current_user={@current_user} />
|
||||
</div>
|
||||
|
|
@ -487,7 +500,7 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
phx-click={
|
||||
JS.toggle(to: "#theme-menu", in: "transition ease-out duration-100", out: "transition ease-in duration-75")
|
||||
}
|
||||
class="rounded-full p-2 text-gray-400 hover:text-gray-500 dark:text-gray-300 dark:hover:text-white"
|
||||
class="relative rounded-full p-1.5 text-gray-400 hover:text-white focus:outline-2 focus:outline-offset-2 focus:outline-indigo-500"
|
||||
>
|
||||
<svg class="h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z" />
|
||||
|
|
@ -496,11 +509,11 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
<div
|
||||
id="theme-menu"
|
||||
phx-click-away={JS.hide(to: "#theme-menu")}
|
||||
class="hidden absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 dark:bg-gray-800 dark:ring-white/10"
|
||||
class="hidden absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-gray-800 py-1 shadow-lg ring-1 ring-white/10"
|
||||
>
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "light"}) |> JS.hide(to: "#theme-menu")}
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-200 hover:bg-white/5"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
|
|
@ -513,7 +526,7 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
</button>
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "dark"}) |> JS.hide(to: "#theme-menu")}
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-200 hover:bg-white/5"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
||||
|
|
@ -522,7 +535,7 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
</button>
|
||||
<button
|
||||
phx-click={JS.dispatch("phx:set-theme", detail: %{theme: "auto"}) |> JS.hide(to: "#theme-menu")}
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="flex w-full items-center gap-3 px-4 py-2 text-sm text-gray-200 hover:bg-white/5"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
|
|
@ -749,7 +762,7 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
Renders a navigation menu component that can be used in both regular pages and map sidebars.
|
||||
"""
|
||||
attr :class, :string, default: ""
|
||||
attr :variant, :atom, values: [:horizontal, :vertical], default: :horizontal
|
||||
attr :variant, :atom, values: [:horizontal, :vertical, :sidebar], default: :horizontal
|
||||
attr :current_user, :any, default: nil
|
||||
attr :map_state, :map, default: nil
|
||||
attr :tracked_callsign, :string, default: ""
|
||||
|
|
@ -775,93 +788,142 @@ defmodule AprsmeWeb.CoreComponents do
|
|||
<%= if @variant == :horizontal do %>
|
||||
<.link
|
||||
navigate={@home_url}
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Home")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/api"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
API
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/about"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("About")}
|
||||
</.link>
|
||||
<%= if @current_user do %>
|
||||
<.link
|
||||
navigate="/users/settings"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Settings")}
|
||||
</.link>
|
||||
<.link
|
||||
href="/users/log_out"
|
||||
method="delete"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Log out")}
|
||||
</.link>
|
||||
<% else %>
|
||||
<.link
|
||||
navigate="/users/register"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Register")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/users/log_in"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"
|
||||
class="rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-400"
|
||||
>
|
||||
{gettext("Log in")}
|
||||
</.link>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% end %>
|
||||
<%= if @variant == :vertical do %>
|
||||
<.link
|
||||
navigate={@home_url}
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Home")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/api"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
API
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/about"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("About")}
|
||||
</.link>
|
||||
<%= if @current_user do %>
|
||||
<.link
|
||||
navigate="/users/settings"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Settings")}
|
||||
</.link>
|
||||
<.link
|
||||
href="/users/log_out"
|
||||
method="delete"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Log out")}
|
||||
</.link>
|
||||
<% else %>
|
||||
<.link
|
||||
navigate="/users/register"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
|
||||
>
|
||||
{gettext("Register")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/users/log_in"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700"
|
||||
class="block rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-400"
|
||||
>
|
||||
{gettext("Log in")}
|
||||
</.link>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= if @variant == :sidebar do %>
|
||||
<.link
|
||||
navigate={@home_url}
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
{gettext("Home")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/api"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
API
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/about"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
{gettext("About")}
|
||||
</.link>
|
||||
<%= if @current_user do %>
|
||||
<.link
|
||||
navigate="/users/settings"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
{gettext("Settings")}
|
||||
</.link>
|
||||
<.link
|
||||
href="/users/log_out"
|
||||
method="delete"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
{gettext("Log out")}
|
||||
</.link>
|
||||
<% else %>
|
||||
<.link
|
||||
navigate="/users/register"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-white"
|
||||
>
|
||||
{gettext("Register")}
|
||||
</.link>
|
||||
<.link
|
||||
navigate="/users/log_in"
|
||||
class="block rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500"
|
||||
>
|
||||
{gettext("Log in")}
|
||||
</.link>
|
||||
|
|
|
|||
|
|
@ -1,40 +1,62 @@
|
|||
<div class="mx-auto max-w-2xl py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 mb-8">
|
||||
<div class="px-6 py-8 prose max-w-none dark:prose-invert">
|
||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white">{gettext("Under heavy construction")}</h2>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
|
||||
</p>
|
||||
<div class="bg-gray-50 p-6 rounded-lg mb-8 dark:bg-gray-700/50">
|
||||
<h4 class="text-lg font-semibold mb-3 text-gray-900 dark:text-white">
|
||||
<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 mb-8">
|
||||
<div class="px-6 py-8 sm:px-8">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-600">
|
||||
<svg class="h-5 w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">APRS.me</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{gettext("Real-time APRS packet tracking")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="prose max-w-none dark:prose-invert">
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
APRS.me is a real-time Automatic Packet Reporting System (APRS) viewer built for amateur radio operators. Track stations, view position reports, weather data, and telemetry from the APRS-IS network on an interactive map.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 rounded-lg bg-gray-50 p-5 ring-1 ring-gray-950/5 dark:bg-gray-900/50 dark:ring-white/10">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
||||
{gettext("Built with Modern Technologies")}
|
||||
</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-xs font-medium text-indigo-700 dark:bg-indigo-400/10 dark:text-indigo-400">
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2.5 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">
|
||||
Elixir
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-400/10 dark:text-green-400">
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2.5 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-700/10 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/30">
|
||||
Phoenix
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-purple-100 px-2 py-1 text-xs font-medium text-purple-700 dark:bg-purple-400/10 dark:text-purple-400">
|
||||
<span class="inline-flex items-center rounded-md bg-purple-50 px-2.5 py-1 text-xs font-medium text-purple-700 ring-1 ring-inset ring-purple-700/10 dark:bg-purple-400/10 dark:text-purple-400 dark:ring-purple-400/30">
|
||||
LiveView
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-800 dark:bg-yellow-400/10 dark:text-yellow-500">
|
||||
<span class="inline-flex items-center rounded-md bg-yellow-50 px-2.5 py-1 text-xs font-medium text-yellow-800 ring-1 ring-inset ring-yellow-700/10 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/30">
|
||||
PostgreSQL
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-700 dark:bg-red-400/10 dark:text-red-400">
|
||||
<span class="inline-flex items-center rounded-md bg-cyan-50 px-2.5 py-1 text-xs font-medium text-cyan-700 ring-1 ring-inset ring-cyan-700/10 dark:bg-cyan-400/10 dark:text-cyan-400 dark:ring-cyan-400/30">
|
||||
PostGIS
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2.5 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-700/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/30">
|
||||
Tailwind CSS
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30">
|
||||
Leaflet.js
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center text-gray-500 dark:text-gray-400">
|
||||
<p class="text-sm">
|
||||
© 2026 aprs.me.
|
||||
<div class="text-center text-gray-400 dark:text-gray-500">
|
||||
<p class="text-xs">
|
||||
© 2026 aprs.me
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<!-- Page header -->
|
||||
<div class="bg-white shadow-sm dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<div class="bg-white shadow-xs ring-1 ring-gray-950/5 dark:bg-gray-800/50 dark:shadow-none dark:ring-white/10">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="py-4 md:flex md:items-center md:justify-between">
|
||||
<div class="py-5 md:flex md:items-center md:justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-xl font-bold leading-7 sm:truncate sm:text-2xl">
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
{render_symbol_html(@packet)}
|
||||
</div>
|
||||
<% end %>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-xs font-medium text-indigo-700 dark:bg-indigo-400/10 dark:text-indigo-400">
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2.5 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">
|
||||
{@callsign}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<div class="flex space-x-2">
|
||||
<.link
|
||||
navigate={~p"/packets/#{@callsign}"}
|
||||
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-gray-300 ring-inset hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||
class="rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||
>
|
||||
{gettext("View packets")}
|
||||
</.link>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
<!-- Station details -->
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 mb-6 lg:grid-rows-1">
|
||||
<!-- Position information -->
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 h-full">
|
||||
<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 h-full">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -188,7 +188,7 @@
|
|||
|
||||
<!-- Map showing station location -->
|
||||
<%= if @packet && @packet.lat && @packet.lon do %>
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 h-full">
|
||||
<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 h-full">
|
||||
<% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(@packet) %>
|
||||
<% symbol_html = AprsmeWeb.AprsSymbol.render_marker_html(symbol_table, symbol_code, @callsign, 32) %>
|
||||
<.info_map
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
<!-- Row for Device Information and Other SSIDs -->
|
||||
<div class="lg:col-span-2 grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<!-- Device information -->
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<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="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
|
||||
<!-- Other SSIDs -->
|
||||
<%= if length(@other_ssids) > 0 do %>
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<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="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -319,7 +319,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700 text-sm">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10 text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-xs font-medium uppercase tracking-wider">
|
||||
|
|
@ -409,7 +409,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Neighboring stations -->
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<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="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -432,7 +432,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-xs font-medium uppercase tracking-wider">
|
||||
|
|
@ -505,7 +505,7 @@
|
|||
|
||||
<!-- Stations that heard this callsign on RF -->
|
||||
<%= if length(@heard_by_stations) > 0 do %>
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 mt-6">
|
||||
<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-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -532,7 +532,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-xs font-medium uppercase tracking-wider">
|
||||
|
|
@ -610,7 +610,7 @@
|
|||
|
||||
<!-- Stations heard directly by this callsign -->
|
||||
<%= if length(@stations_heard_by) > 0 do %>
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 mt-6">
|
||||
<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-6">
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
|
|
@ -637,7 +637,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-xs font-medium uppercase tracking-wider">
|
||||
|
|
|
|||
|
|
@ -1404,294 +1404,265 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
]}
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-6 border-b border-slate-200 dark:border-slate-700 bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
<h2 class="text-xl font-bold">APRS.me</h2>
|
||||
</div>
|
||||
|
||||
<!-- Close button for mobile -->
|
||||
<button
|
||||
class="lg:hidden text-white hover:text-slate-200 transition-colors"
|
||||
phx-click={toggle_slideover_js()}
|
||||
title={Gettext.gettext(AprsmeWeb.Gettext, "Close controls")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m18 6-12 12" />
|
||||
<path d="m6 6 12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 space-y-6 bg-slate-50 dark:bg-slate-900 flex-1 overflow-y-auto">
|
||||
<!-- Callsign Search -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center space-x-2">
|
||||
<svg class="w-4 h-4 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="bg-indigo-700 px-5 py-5 dark:bg-indigo-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<svg class="h-5 w-5 text-indigo-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{gettext("Search Callsign")}</span>
|
||||
</label>
|
||||
<form phx-submit="track_callsign" class="flex flex-col space-y-2">
|
||||
<div class="flex space-x-2 w-full">
|
||||
<input
|
||||
type="text"
|
||||
name="callsign"
|
||||
value={@tracked_callsign || @overlay_callsign}
|
||||
phx-change="update_callsign"
|
||||
placeholder={gettext("Enter callsign...")}
|
||||
class="flex-1 px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm uppercase placeholder-slate-400 dark:placeholder-slate-500 transition-all duration-200 hover:border-slate-400 dark:hover:border-slate-500 bg-white dark:bg-slate-800 text-gray-900 dark:text-white"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 text-white rounded-xl hover:from-indigo-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-200 text-sm font-semibold shadow-lg hover:shadow-xl whitespace-nowrap flex-shrink-0"
|
||||
>
|
||||
{gettext("Track")}
|
||||
</button>
|
||||
</div>
|
||||
<%= if @tracked_callsign != "" do %>
|
||||
<div class="flex items-center justify-between bg-blue-50 px-3 py-2 rounded-lg">
|
||||
<span class="text-sm text-blue-700 font-medium">
|
||||
{gettext("Tracking:")} <span class="font-bold">{@tracked_callsign}</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="clear_tracking"
|
||||
class="text-blue-600 hover:text-blue-800 font-medium text-sm"
|
||||
>
|
||||
{gettext("Clear")}
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</form>
|
||||
<h2 class="text-base font-semibold text-white">APRS.me</h2>
|
||||
</div>
|
||||
<button
|
||||
class="relative rounded-md text-indigo-200 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white lg:hidden"
|
||||
phx-click={toggle_slideover_js()}
|
||||
title={Gettext.gettext(AprsmeWeb.Gettext, "Close controls")}
|
||||
>
|
||||
<span class="absolute -inset-2.5"></span>
|
||||
<svg class="h-6 w-6" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
||||
<path d="M6 18 18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Other SSIDs -->
|
||||
<%= if @tracked_callsign != "" and @other_ssids != [] do %>
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center space-x-2">
|
||||
<svg class="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<p class="mt-1 text-sm text-indigo-300 dark:text-indigo-200">
|
||||
{gettext("Real-time APRS packet tracking")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="relative flex-1 overflow-y-auto bg-white px-5 py-5 dark:bg-gray-800 dark:after:absolute dark:after:inset-y-0 dark:after:left-0 dark:after:w-px dark:after:bg-white/10">
|
||||
<div class="space-y-5">
|
||||
<!-- Callsign Search -->
|
||||
<div class="rounded-lg bg-gray-50 p-4 ring-1 ring-gray-950/5 dark:bg-gray-900/50 dark:ring-white/10">
|
||||
<label class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<svg class="h-3.5 w-3.5 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
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"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{gettext("Other SSIDs")}</span>
|
||||
{gettext("Search Callsign")}
|
||||
</label>
|
||||
<div class="space-y-1">
|
||||
<%= for ssid_info <- @other_ssids do %>
|
||||
<div class="flex items-center justify-between px-3 py-2 rounded-lg bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:border-indigo-300 dark:hover:border-indigo-600 transition-all duration-200 text-sm group">
|
||||
<form phx-submit="track_callsign" class="space-y-2">
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
name="callsign"
|
||||
value={@tracked_callsign || @overlay_callsign}
|
||||
phx-change="update_callsign"
|
||||
placeholder={gettext("Enter callsign...")}
|
||||
class="block w-full rounded-md bg-white px-3 py-2 text-sm text-gray-900 shadow-xs outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 uppercase dark:bg-gray-800 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 whitespace-nowrap flex-shrink-0 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
||||
>
|
||||
{gettext("Track")}
|
||||
</button>
|
||||
</div>
|
||||
<%= if @tracked_callsign != "" do %>
|
||||
<div class="flex items-center justify-between rounded-md bg-indigo-50 px-3 py-2 dark:bg-indigo-500/10">
|
||||
<span class="text-sm text-indigo-700 font-medium dark:text-indigo-300">
|
||||
{gettext("Tracking:")} <span class="font-bold font-mono">{@tracked_callsign}</span>
|
||||
</span>
|
||||
<button
|
||||
phx-click="track_callsign"
|
||||
phx-value-callsign={ssid_info.callsign}
|
||||
class="font-semibold text-indigo-600 dark:text-indigo-400 group-hover:text-indigo-800 dark:group-hover:text-indigo-300 cursor-pointer"
|
||||
type="button"
|
||||
phx-click="clear_tracking"
|
||||
class="rounded-md bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-600 hover:bg-indigo-100 dark:bg-indigo-500/10 dark:text-indigo-400 dark:hover:bg-indigo-500/20"
|
||||
>
|
||||
{ssid_info.callsign}
|
||||
{gettext("Clear")}
|
||||
</button>
|
||||
<div class="flex items-center space-x-2">
|
||||
<%= if ssid_info.received_at do %>
|
||||
<span class="text-xs text-slate-500 dark:text-slate-400">
|
||||
{time_ago_in_words(ssid_info.received_at)}
|
||||
</span>
|
||||
<% end %>
|
||||
<.link
|
||||
navigate={~p"/info/#{ssid_info.callsign}"}
|
||||
class="text-xs text-slate-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
||||
title={gettext("Station info")}
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Trail Duration -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center space-x-2">
|
||||
<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{gettext("Trail Duration")}</span>
|
||||
</label>
|
||||
<form phx-change="update_trail_duration" class="relative">
|
||||
<select
|
||||
name="trail_duration"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm bg-white dark:bg-slate-800 text-gray-900 dark:text-white appearance-none transition-all duration-200 hover:border-slate-400 dark:hover:border-slate-500"
|
||||
>
|
||||
<option value="1" selected={@trail_duration == "1"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 1)}
|
||||
</option>
|
||||
<option value="6" selected={@trail_duration == "6"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 6)}
|
||||
</option>
|
||||
<option value="12" selected={@trail_duration == "12"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 12)}
|
||||
</option>
|
||||
<option value="24" selected={@trail_duration == "24"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 24)}
|
||||
</option>
|
||||
<option value="48" selected={@trail_duration == "48"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 48)}
|
||||
</option>
|
||||
<option value="168" selected={@trail_duration == "168"}>
|
||||
{gettext("1 Week")}
|
||||
</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Historical Data -->
|
||||
<div class="space-y-4">
|
||||
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center space-x-2">
|
||||
<svg class="w-4 h-4 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{gettext("Historical Data")}</span>
|
||||
</label>
|
||||
<form phx-change="update_historical_hours" class="relative">
|
||||
<select
|
||||
name="historical_hours"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm bg-white dark:bg-slate-800 text-gray-900 dark:text-white appearance-none transition-all duration-200 hover:border-slate-400 dark:hover:border-slate-500"
|
||||
>
|
||||
<option value="1" selected={@historical_hours == "1"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 1)}
|
||||
</option>
|
||||
<option value="3" selected={@historical_hours == "3"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 3)}
|
||||
</option>
|
||||
<option value="6" selected={@historical_hours == "6"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 6)}
|
||||
</option>
|
||||
<option value="12" selected={@historical_hours == "12"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 12)}
|
||||
</option>
|
||||
<option value="24" selected={@historical_hours == "24"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 24)}
|
||||
</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="pt-4 border-t border-slate-200 dark:border-slate-700 space-y-3">
|
||||
<div class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400 mb-3">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-medium">{gettext("Navigation")}</span>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<.navigation
|
||||
variant={:vertical}
|
||||
current_user={@current_user}
|
||||
map_state={%{lat: @map_center.lat, lng: @map_center.lng, zoom: @map_zoom}}
|
||||
tracked_callsign={@tracked_callsign}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Last Update -->
|
||||
<div class="pt-4 border-t border-slate-200 dark:border-slate-700 space-y-3">
|
||||
<div class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-medium">{gettext("Last Update")}</span>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400">
|
||||
<%= if @last_update_at do %>
|
||||
<div class="font-mono">{time_ago_in_words(@last_update_at)}</div>
|
||||
<div class="font-mono text-slate-400 dark:text-slate-500">
|
||||
{Calendar.strftime(@last_update_at, "%Y-%m-%d %H:%M UTC")}
|
||||
|
||||
<!-- Other SSIDs -->
|
||||
<%= if @tracked_callsign != "" and @other_ssids != [] do %>
|
||||
<div class="rounded-lg bg-gray-50 p-4 ring-1 ring-gray-950/5 dark:bg-gray-900/50 dark:ring-white/10">
|
||||
<label class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<svg class="h-3.5 w-3.5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
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>
|
||||
{gettext("Other SSIDs")}
|
||||
</label>
|
||||
<div class="space-y-1">
|
||||
<%= for ssid_info <- @other_ssids do %>
|
||||
<div class="group flex items-center justify-between rounded-md bg-white px-3 py-2 text-sm ring-1 ring-gray-950/5 hover:ring-indigo-300 dark:bg-gray-800 dark:ring-white/10 dark:hover:ring-indigo-500/50">
|
||||
<button
|
||||
phx-click="track_callsign"
|
||||
phx-value-callsign={ssid_info.callsign}
|
||||
class="font-semibold font-mono text-indigo-600 group-hover:text-indigo-500 cursor-pointer dark:text-indigo-400 dark:group-hover:text-indigo-300"
|
||||
>
|
||||
{ssid_info.callsign}
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= if ssid_info.received_at do %>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{time_ago_in_words(ssid_info.received_at)}
|
||||
</span>
|
||||
<% end %>
|
||||
<.link
|
||||
navigate={~p"/info/#{ssid_info.callsign}"}
|
||||
class="text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400"
|
||||
title={gettext("Station info")}
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</.link>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="font-mono text-slate-500 dark:text-slate-400">No updates yet</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Trail Duration -->
|
||||
<div class="rounded-lg bg-gray-50 p-4 ring-1 ring-gray-950/5 dark:bg-gray-900/50 dark:ring-white/10">
|
||||
<label class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<svg class="h-3.5 w-3.5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("Trail Duration")}
|
||||
</label>
|
||||
<form phx-change="update_trail_duration">
|
||||
<select
|
||||
name="trail_duration"
|
||||
class="block w-full rounded-md bg-white px-3 py-2 text-sm text-gray-900 shadow-xs outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 dark:bg-gray-800 dark:text-white dark:outline-white/10 dark:focus:outline-indigo-500"
|
||||
>
|
||||
<option value="1" selected={@trail_duration == "1"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 1)}
|
||||
</option>
|
||||
<option value="6" selected={@trail_duration == "6"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 6)}
|
||||
</option>
|
||||
<option value="12" selected={@trail_duration == "12"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 12)}
|
||||
</option>
|
||||
<option value="24" selected={@trail_duration == "24"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 24)}
|
||||
</option>
|
||||
<option value="48" selected={@trail_duration == "48"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 48)}
|
||||
</option>
|
||||
<option value="168" selected={@trail_duration == "168"}>
|
||||
{gettext("1 Week")}
|
||||
</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deployment Information -->
|
||||
<div class="pt-4 border-t border-slate-200 dark:border-slate-700 space-y-3">
|
||||
<div class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
|
||||
<!-- Historical Data -->
|
||||
<div class="rounded-lg bg-gray-50 p-4 ring-1 ring-gray-950/5 dark:bg-gray-900/50 dark:ring-white/10">
|
||||
<label class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<svg class="h-3.5 w-3.5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("Historical Data")}
|
||||
</label>
|
||||
<form phx-change="update_historical_hours">
|
||||
<select
|
||||
name="historical_hours"
|
||||
class="block w-full rounded-md bg-white px-3 py-2 text-sm text-gray-900 shadow-xs outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 dark:bg-gray-800 dark:text-white dark:outline-white/10 dark:focus:outline-indigo-500"
|
||||
>
|
||||
<option value="1" selected={@historical_hours == "1"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 1)}
|
||||
</option>
|
||||
<option value="3" selected={@historical_hours == "3"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 3)}
|
||||
</option>
|
||||
<option value="6" selected={@historical_hours == "6"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 6)}
|
||||
</option>
|
||||
<option value="12" selected={@historical_hours == "12"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 12)}
|
||||
</option>
|
||||
<option value="24" selected={@historical_hours == "24"}>
|
||||
{ngettext("1 Hour", "%{count} Hours", 24)}
|
||||
</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-white/10">
|
||||
<label class="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
{gettext("Navigation")}
|
||||
</label>
|
||||
<nav class="space-y-1">
|
||||
<.navigation
|
||||
variant={:sidebar}
|
||||
current_user={@current_user}
|
||||
map_state={%{lat: @map_center.lat, lng: @map_center.lng, zoom: @map_zoom}}
|
||||
tracked_callsign={@tracked_callsign}
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-medium">{gettext("Last Deploy")}</span>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400">
|
||||
<div class="font-mono">{time_ago_in_words(@deployed_at)}</div>
|
||||
<div class="font-mono text-slate-400">
|
||||
{Calendar.strftime(@deployed_at, "%Y-%m-%d %H:%M UTC")}
|
||||
|
||||
<!-- Status Footer -->
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-white/10">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
{gettext("Last Update")}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-gray-600 dark:text-gray-300">
|
||||
<%= if @last_update_at do %>
|
||||
<div class="font-mono font-medium">{time_ago_in_words(@last_update_at)}</div>
|
||||
<div class="font-mono text-gray-400 dark:text-gray-500">
|
||||
{Calendar.strftime(@last_update_at, "%H:%M UTC")}
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="font-mono text-gray-400">--</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
{gettext("Last Deploy")}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-gray-600 dark:text-gray-300">
|
||||
<div class="font-mono font-medium">{time_ago_in_words(@deployed_at)}</div>
|
||||
<div class="font-mono text-gray-400 dark:text-gray-500">
|
||||
{Calendar.strftime(@deployed_at, "%H:%M UTC")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,24 +16,36 @@
|
|||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<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-300 dark:divide-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Sender</th>
|
||||
<th>Data Type</th>
|
||||
<th>Information</th>
|
||||
<th>Path</th>
|
||||
<th>Device</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">
|
||||
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>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
|
||||
<%= for packet <- @all_packets do %>
|
||||
<tr>
|
||||
<td>
|
||||
<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 -> %>
|
||||
|
|
@ -50,16 +62,16 @@
|
|||
<% end %>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-sm font-medium">{packet.sender}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-xs font-medium text-indigo-700 dark:bg-indigo-400/10 dark:text-indigo-400">
|
||||
<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>
|
||||
<span class="text-sm font-mono">
|
||||
<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}>
|
||||
|
|
@ -70,12 +82,12 @@
|
|||
<% end %>
|
||||
</span>
|
||||
</td>
|
||||
<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>
|
||||
<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]
|
||||
|
|
@ -117,7 +129,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10 mt-4">
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -1,25 +1,43 @@
|
|||
<div class="container mx-auto max-w-7xl py-8">
|
||||
<div class="bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10">
|
||||
<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-300 dark:divide-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{gettext("Sender")}</th>
|
||||
<th>{gettext("SSID")}</th>
|
||||
<th>{gettext("Base Callsign")}</th>
|
||||
<th>{gettext("Data Type")}</th>
|
||||
<th>{gettext("Symbol")}</th>
|
||||
<th>{gettext("Path")}</th>
|
||||
<th>{gettext("Latitude")}</th>
|
||||
<th>{gettext("Longitude")}</th>
|
||||
<th>{gettext("Device")}</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">
|
||||
{gettext("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">
|
||||
{gettext("SSID")}
|
||||
</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">
|
||||
{gettext("Base Callsign")}
|
||||
</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">
|
||||
{gettext("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">
|
||||
{gettext("Symbol")}
|
||||
</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">
|
||||
{gettext("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">
|
||||
{gettext("Latitude")}
|
||||
</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">
|
||||
{gettext("Longitude")}
|
||||
</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">
|
||||
{gettext("Device")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
|
||||
<%= for packet <- @packets do %>
|
||||
<tr>
|
||||
<td>
|
||||
<tr class="even:bg-gray-50 dark:even:bg-white/[0.025]">
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<.link
|
||||
navigate={~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"}
|
||||
class="font-semibold text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
|
|
@ -27,12 +45,10 @@
|
|||
{Map.get(packet, :sender, Map.get(packet, "sender", ""))}
|
||||
</.link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm">
|
||||
{Map.get(packet, :ssid, Map.get(packet, "ssid", ""))}
|
||||
</span>
|
||||
<td class="whitespace-nowrap py-3 pr-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{Map.get(packet, :ssid, Map.get(packet, "ssid", ""))}
|
||||
</td>
|
||||
<td>
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<.link
|
||||
navigate={~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"}
|
||||
class="font-semibold text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
|
|
@ -40,13 +56,13 @@
|
|||
{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}
|
||||
</.link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-100 px-2 py-1 text-xs font-medium text-indigo-700 dark:bg-indigo-400/10 dark:text-indigo-400">
|
||||
<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">
|
||||
{Map.get(packet, :data_type, Map.get(packet, "data_type", ""))}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-xs font-mono">
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">
|
||||
<% data = Map.get(packet, :data_extended) || %{}
|
||||
|
||||
raw_table =
|
||||
|
|
@ -57,23 +73,23 @@
|
|||
{table}{code}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
||||
{Map.get(packet, :path, Map.get(packet, "path", ""))}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-xs font-mono">
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">
|
||||
{format_coordinate(extract_coordinate(packet, :lat))}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-xs font-mono">
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">
|
||||
{format_coordinate(extract_coordinate(packet, :lon))}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-xs font-mono">
|
||||
<td class="whitespace-nowrap py-3 pr-3">
|
||||
<span class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
||||
{Map.get(packet, :device_identifier, Map.get(packet, "device_identifier", ""))}
|
||||
</span>
|
||||
</td>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue