refactor: apply custom color palette across entire codebase
- Replace gray->cool-steel, blue/indigo->cerulean, red->sweet-salmon, yellow/amber->wheat - Dark sidebar: sidebar/footer use cool-steel-800 bg, text lightened for contrast - ~11,600 color replacements across ~99 files - Update tests for new color class names
This commit is contained in:
parent
ebcd54be5b
commit
5a7cdc7138
104 changed files with 6073 additions and 5476 deletions
|
|
@ -25,27 +25,27 @@ defmodule ToweropsWeb.Components.ConsentPrompt do
|
||||||
>
|
>
|
||||||
<div class="flex min-h-screen items-center justify-center p-4">
|
<div class="flex min-h-screen items-center justify-center p-4">
|
||||||
<!-- Backdrop -->
|
<!-- Backdrop -->
|
||||||
<div class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-80 transition-opacity">
|
<div class="fixed inset-0 bg-cool-steel-500 dark:bg-cool-steel-900 bg-opacity-75 dark:bg-opacity-80 transition-opacity">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal panel -->
|
<!-- Modal panel -->
|
||||||
<div class="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
<div class="relative transform overflow-hidden rounded-lg bg-white dark:bg-cool-steel-800 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900 sm:mx-0 sm:h-10 sm:w-10">
|
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-cerulean-100 dark:bg-cerulean-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-information-circle"
|
name="hero-information-circle"
|
||||||
class="h-6 w-6 text-blue-600 dark:text-blue-400"
|
class="h-6 w-6 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left w-full">
|
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left w-full">
|
||||||
<h3
|
<h3
|
||||||
class="text-lg font-semibold leading-6 text-gray-900 dark:text-white"
|
class="text-lg font-semibold leading-6 text-cool-steel-900 dark:text-white"
|
||||||
id="consent-modal-title"
|
id="consent-modal-title"
|
||||||
>
|
>
|
||||||
{gettext("Updated Policies - Action Required")}
|
{gettext("Updated Policies - Action Required")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{gettext(
|
{gettext(
|
||||||
"We've updated our policies. Please review and accept the changes to continue using Towerops."
|
"We've updated our policies. Please review and accept the changes to continue using Towerops."
|
||||||
)}
|
)}
|
||||||
|
|
@ -62,31 +62,31 @@ defmodule ToweropsWeb.Components.ConsentPrompt do
|
||||||
phx-click="toggle_consent"
|
phx-click="toggle_consent"
|
||||||
phx-value-policy={policy_type}
|
phx-value-policy={policy_type}
|
||||||
required
|
required
|
||||||
class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
class="h-4 w-4 rounded border-zinc-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-cerulean-500 dark:checked:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 text-sm">
|
<div class="ml-3 text-sm">
|
||||||
<label
|
<label
|
||||||
for={"consent-#{policy_type}"}
|
for={"consent-#{policy_type}"}
|
||||||
class="text-gray-700 dark:text-gray-300"
|
class="text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{gettext("I agree to the updated")}
|
{gettext("I agree to the updated")}
|
||||||
<.link
|
<.link
|
||||||
href={policy_link(policy_type)}
|
href={policy_link(policy_type)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{policy_name(policy_type)}
|
{policy_name(policy_type)}
|
||||||
</.link>
|
</.link>
|
||||||
<span class="text-red-600 dark:text-red-400">*</span>
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div class="mt-4 pt-4 border-t border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{gettext(
|
{gettext(
|
||||||
"By accepting, you acknowledge that you have read and agree to the updated policies. You can review your consent history at any time from your account settings."
|
"By accepting, you acknowledge that you have read and agree to the updated policies. You can review your consent history at any time from your account settings."
|
||||||
)}
|
)}
|
||||||
|
|
@ -100,7 +100,7 @@ defmodule ToweropsWeb.Components.ConsentPrompt do
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="accept_updated_policies"
|
phx-click="accept_updated_policies"
|
||||||
phx-value-user-id={@user_id}
|
phx-value-user-id={@user_id}
|
||||||
class="inline-flex w-full justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 sm:ml-3 sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
|
class="inline-flex w-full justify-center rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-cerulean-500 sm:ml-3 sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
id="accept-policies-button"
|
id="accept-policies-button"
|
||||||
>
|
>
|
||||||
{gettext("Accept and Continue")}
|
{gettext("Accept and Continue")}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ defmodule ToweropsWeb.Components.CookieConsent do
|
||||||
<%= if @requires_consent do %>
|
<%= if @requires_consent do %>
|
||||||
<div
|
<div
|
||||||
id="cookie-consent-banner"
|
id="cookie-consent-banner"
|
||||||
class="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 shadow-lg"
|
class="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-cool-steel-900 border-t border-cool-steel-200 dark:border-cool-steel-700 shadow-lg"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
aria-label="Cookie consent"
|
aria-label="Cookie consent"
|
||||||
|
|
@ -38,15 +38,15 @@ defmodule ToweropsWeb.Components.CookieConsent do
|
||||||
<div class="flex-1 flex items-start gap-3">
|
<div class="flex-1 flex items-start gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-information-circle"
|
name="hero-information-circle"
|
||||||
class="h-6 w-6 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-6 w-6 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
<div class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<p class="font-medium mb-1">We respect your privacy</p>
|
<p class="font-medium mb-1">We respect your privacy</p>
|
||||||
<p>
|
<p>
|
||||||
We use essential cookies to maintain your session and ensure the security of our service. We do not use tracking or advertising cookies.
|
We use essential cookies to maintain your session and ensure the security of our service. We do not use tracking or advertising cookies.
|
||||||
<a
|
<a
|
||||||
href="/privacy"
|
href="/privacy"
|
||||||
class="underline hover:text-gray-900 dark:hover:text-gray-100 font-medium"
|
class="underline hover:text-cool-steel-900 dark:hover:text-cool-steel-100 font-medium"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -58,7 +58,7 @@ defmodule ToweropsWeb.Components.CookieConsent do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-cookie-consent="accept"
|
data-cookie-consent="accept"
|
||||||
class="flex-1 sm:flex-none px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-900"
|
class="flex-1 sm:flex-none px-4 py-2 bg-cerulean-600 text-white text-sm font-medium rounded-lg hover:bg-cerulean-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500 dark:focus:ring-offset-cool-steel-900"
|
||||||
>
|
>
|
||||||
Accept
|
Accept
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
{@rest}
|
{@rest}
|
||||||
>
|
>
|
||||||
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
|
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
|
||||||
<div class="pointer-events-auto w-full max-w-sm translate-y-0 transform rounded-lg bg-white opacity-100 shadow-lg outline-1 outline-black/5 transition duration-300 ease-out sm:translate-x-0 dark:bg-gray-800 dark:-outline-offset-1 dark:outline-white/10 starting:translate-y-2 starting:opacity-0 starting:sm:translate-x-2 starting:sm:translate-y-0">
|
<div class="pointer-events-auto w-full max-w-sm translate-y-0 transform rounded-lg bg-white opacity-100 shadow-lg outline-1 outline-black/5 transition duration-300 ease-out sm:translate-x-0 dark:bg-cool-steel-800 dark:-outline-offset-1 dark:outline-white/10 starting:translate-y-2 starting:opacity-0 starting:sm:translate-x-2 starting:sm:translate-y-0">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
|
|
@ -73,15 +73,15 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<.icon
|
<.icon
|
||||||
:if={@kind == :error}
|
:if={@kind == :error}
|
||||||
name="hero-x-circle"
|
name="hero-x-circle"
|
||||||
class="size-6 text-red-400"
|
class="size-6 text-sweet-salmon-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 w-0 flex-1 pt-0.5">
|
<div class="ml-3 w-0 flex-1 pt-0.5">
|
||||||
<p :if={@title} class="text-sm font-medium text-gray-900 dark:text-white">
|
<p :if={@title} class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@title}
|
{@title}
|
||||||
</p>
|
</p>
|
||||||
<p class={[
|
<p class={[
|
||||||
"text-sm text-gray-500 dark:text-gray-400",
|
"text-sm text-cool-steel-500 dark:text-cool-steel-400",
|
||||||
@title && "mt-1"
|
@title && "mt-1"
|
||||||
]}>
|
]}>
|
||||||
{render_flash_message(msg)}
|
{render_flash_message(msg)}
|
||||||
|
|
@ -90,7 +90,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<div class="ml-4 flex shrink-0">
|
<div class="ml-4 flex shrink-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex rounded-md text-gray-400 hover:text-gray-500 focus:outline-2 focus:outline-offset-2 focus:outline-indigo-600 dark:hover:text-white dark:focus:outline-indigo-500"
|
class="inline-flex rounded-md text-cool-steel-400 hover:text-cool-steel-500 focus:outline-2 focus:outline-offset-2 focus:outline-cerulean-600 dark:hover:text-white dark:focus:outline-cerulean-500"
|
||||||
aria-label={gettext("close")}
|
aria-label={gettext("close")}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">Close</span>
|
||||||
|
|
@ -114,7 +114,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
{@text}
|
{@text}
|
||||||
<.link
|
<.link
|
||||||
navigate={@link_url}
|
navigate={@link_url}
|
||||||
class="font-medium text-indigo-600 hover:text-indigo-500 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="font-medium text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{@link_text}
|
{@link_text}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -142,15 +142,15 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
def button(%{rest: rest} = assigns) do
|
def button(%{rest: rest} = assigns) do
|
||||||
variants = %{
|
variants = %{
|
||||||
"primary" =>
|
"primary" =>
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 active:bg-blue-700 focus:ring-blue-500 shadow-md hover:shadow-lg dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-600",
|
"bg-cerulean-600 text-white hover:bg-cerulean-500 active:bg-cerulean-700 focus:ring-cerulean-500 shadow-md hover:shadow-lg dark:bg-cerulean-500 dark:hover:bg-cerulean-400 dark:active:bg-cerulean-600",
|
||||||
"secondary" =>
|
"secondary" =>
|
||||||
"bg-white text-gray-700 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:ring-blue-500 dark:bg-gray-800 dark:text-gray-200 dark:ring-gray-600 dark:hover:bg-gray-700",
|
"bg-white text-cool-steel-700 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 focus:ring-cerulean-500 dark:bg-cool-steel-800 dark:text-cool-steel-200 dark:ring-cool-steel-600 dark:hover:bg-cool-steel-700",
|
||||||
"danger" =>
|
"danger" =>
|
||||||
"bg-red-600 text-white hover:bg-red-500 active:bg-red-700 focus:ring-red-500 dark:bg-red-500 dark:hover:bg-red-400",
|
"bg-sweet-salmon-600 text-white hover:bg-sweet-salmon-500 active:bg-sweet-salmon-700 focus:ring-sweet-salmon-500 dark:bg-sweet-salmon-500 dark:hover:bg-sweet-salmon-400",
|
||||||
"ghost" =>
|
"ghost" =>
|
||||||
"bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-blue-500 shadow-none dark:text-gray-300 dark:hover:bg-gray-800",
|
"bg-transparent text-cool-steel-700 hover:bg-cool-steel-100 focus:ring-cerulean-500 shadow-none dark:text-cool-steel-300 dark:hover:bg-cool-steel-800",
|
||||||
nil =>
|
nil =>
|
||||||
"bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:ring-gray-700 dark:hover:bg-gray-700"
|
"bg-white text-cool-steel-900 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 focus:ring-cerulean-500 dark:bg-cool-steel-800 dark:text-white dark:ring-cool-steel-700 dark:hover:bg-cool-steel-700"
|
||||||
}
|
}
|
||||||
|
|
||||||
sizes = %{
|
sizes = %{
|
||||||
|
|
@ -162,7 +162,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
assigns =
|
assigns =
|
||||||
assign_new(assigns, :class, fn ->
|
assign_new(assigns, :class, fn ->
|
||||||
[
|
[
|
||||||
"inline-flex items-center justify-center gap-2 rounded-lg font-semibold shadow-sm transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed dark:focus:ring-offset-gray-900",
|
"inline-flex items-center justify-center gap-2 rounded-lg font-semibold shadow-sm transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed dark:focus:ring-offset-cool-steel-900",
|
||||||
Map.fetch!(sizes, assigns.size),
|
Map.fetch!(sizes, assigns.size),
|
||||||
Map.fetch!(variants, assigns[:variant])
|
Map.fetch!(variants, assigns[:variant])
|
||||||
]
|
]
|
||||||
|
|
@ -325,11 +325,11 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
aria-invalid={to_string(@errors != [])}
|
aria-invalid={to_string(@errors != [])}
|
||||||
class={
|
class={
|
||||||
@class ||
|
@class ||
|
||||||
"h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800"
|
"h-4 w-4 rounded border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
}
|
}
|
||||||
{@rest}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">{@label}</span>
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">{@label}</span>
|
||||||
</label>
|
</label>
|
||||||
<.error :if={@errors != []} id={@error_id}>
|
<.error :if={@errors != []} id={@error_id}>
|
||||||
<span :for={msg <- @errors}>{msg}</span>
|
<span :for={msg <- @errors}>{msg}</span>
|
||||||
|
|
@ -343,7 +343,11 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label :if={@label} for={@id} class="block text-sm/6 font-medium text-gray-900 dark:text-white">
|
<label
|
||||||
|
:if={@label}
|
||||||
|
for={@id}
|
||||||
|
class="block text-sm/6 font-medium text-cool-steel-900 dark:text-white"
|
||||||
|
>
|
||||||
{@label}
|
{@label}
|
||||||
</label>
|
</label>
|
||||||
<div class={["mt-2 grid grid-cols-1", @label && ""]}>
|
<div class={["mt-2 grid grid-cols-1", @label && ""]}>
|
||||||
|
|
@ -354,12 +358,12 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
aria-invalid={to_string(@errors != [])}
|
aria-invalid={to_string(@errors != [])}
|
||||||
class={[
|
class={[
|
||||||
@class ||
|
@class ||
|
||||||
"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800",
|
"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-cool-steel-900 outline-1 -outline-offset-1 placeholder:text-cool-steel-400 focus:outline-2 focus:-outline-offset-2 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-cool-steel-800",
|
||||||
@errors == [] &&
|
@errors == [] &&
|
||||||
"outline-gray-300 focus:outline-indigo-600 dark:focus:outline-indigo-500",
|
"outline-cool-steel-300 focus:outline-cerulean-600 dark:focus:outline-cerulean-500",
|
||||||
@errors != [] &&
|
@errors != [] &&
|
||||||
(@error_class ||
|
(@error_class ||
|
||||||
"outline-red-300 focus:outline-red-500 dark:outline-red-700 dark:focus:outline-red-500")
|
"outline-sweet-salmon-300 focus:outline-sweet-salmon-500 dark:outline-sweet-salmon-700 dark:focus:outline-sweet-salmon-500")
|
||||||
]}
|
]}
|
||||||
multiple={@multiple}
|
multiple={@multiple}
|
||||||
{@rest}
|
{@rest}
|
||||||
|
|
@ -372,7 +376,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
data-slot="icon"
|
data-slot="icon"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-400 sm:size-4"
|
class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cool-steel-400 sm:size-4"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
|
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
|
||||||
|
|
@ -394,7 +398,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label>
|
<label>
|
||||||
<span :if={@label} class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span :if={@label} class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{@label}
|
{@label}
|
||||||
</span>
|
</span>
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -404,12 +408,12 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
aria-invalid={to_string(@errors != [])}
|
aria-invalid={to_string(@errors != [])}
|
||||||
class={[
|
class={[
|
||||||
@class ||
|
@class ||
|
||||||
"block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 dark:bg-gray-800 dark:text-white",
|
"block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 dark:bg-cool-steel-800 dark:text-white",
|
||||||
@errors == [] &&
|
@errors == [] &&
|
||||||
"ring-gray-300 focus:ring-blue-600 dark:ring-gray-700 dark:focus:ring-blue-500",
|
"ring-cool-steel-300 focus:ring-cerulean-600 dark:ring-cool-steel-700 dark:focus:ring-cerulean-500",
|
||||||
@errors != [] &&
|
@errors != [] &&
|
||||||
(@error_class ||
|
(@error_class ||
|
||||||
"ring-red-300 focus:ring-red-500 dark:ring-red-700 dark:focus:ring-red-500")
|
"ring-sweet-salmon-300 focus:ring-sweet-salmon-500 dark:ring-sweet-salmon-700 dark:focus:ring-sweet-salmon-500")
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
|
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
|
||||||
|
|
@ -428,7 +432,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label>
|
<label>
|
||||||
<span :if={@label} class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span :if={@label} class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{@label}
|
{@label}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
|
|
@ -440,12 +444,12 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
aria-invalid={to_string(@errors != [])}
|
aria-invalid={to_string(@errors != [])}
|
||||||
class={[
|
class={[
|
||||||
@class ||
|
@class ||
|
||||||
"block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 dark:bg-gray-800 dark:text-white",
|
"block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 dark:bg-cool-steel-800 dark:text-white",
|
||||||
@errors == [] &&
|
@errors == [] &&
|
||||||
"ring-gray-300 focus:ring-blue-600 dark:ring-gray-700 dark:focus:ring-blue-500",
|
"ring-cool-steel-300 focus:ring-cerulean-600 dark:ring-cool-steel-700 dark:focus:ring-cerulean-500",
|
||||||
@errors != [] &&
|
@errors != [] &&
|
||||||
(@error_class ||
|
(@error_class ||
|
||||||
"ring-red-300 focus:ring-red-500 dark:ring-red-700 dark:focus:ring-red-500")
|
"ring-sweet-salmon-300 focus:ring-sweet-salmon-500 dark:ring-sweet-salmon-700 dark:focus:ring-sweet-salmon-500")
|
||||||
]}
|
]}
|
||||||
{@rest}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
|
|
@ -460,7 +464,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
# Helper used by inputs to generate form errors
|
# Helper used by inputs to generate form errors
|
||||||
defp error(assigns) do
|
defp error(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<p class="mt-2 flex gap-2 items-center text-sm text-red-600 dark:text-red-400">
|
<p class="mt-2 flex gap-2 items-center text-sm text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
<.icon name="hero-exclamation-circle" class="h-5 w-5 flex-shrink-0" />
|
<.icon name="hero-exclamation-circle" class="h-5 w-5 flex-shrink-0" />
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -478,10 +482,10 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
~H"""
|
~H"""
|
||||||
<header class={[@actions != [] && "flex flex-wrap items-start justify-between gap-3", "mb-8"]}>
|
<header class={[@actions != [] && "flex flex-wrap items-start justify-between gap-3", "mb-8"]}>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</h1>
|
</h1>
|
||||||
<p :if={@subtitle != []} class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p :if={@subtitle != []} class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{render_slot(@subtitle)}
|
{render_slot(@subtitle)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -527,12 +531,12 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="overflow-hidden shadow-sm outline-1 outline-black/5 sm:rounded-lg dark:shadow-none dark:-outline-offset-1 dark:outline-white/10">
|
<div class="overflow-hidden shadow-sm outline-1 outline-black/5 sm:rounded-lg dark:shadow-none dark:-outline-offset-1 dark:outline-white/10">
|
||||||
<table class="relative min-w-full divide-y divide-gray-300 dark:divide-white/15">
|
<table class="relative min-w-full divide-y divide-cool-steel-300 dark:divide-white/15">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/75">
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
:for={col <- @col}
|
:for={col <- @col}
|
||||||
class="py-3.5 pr-3 pl-4 text-left text-sm font-semibold text-gray-900 sm:pl-6 dark:text-gray-200"
|
class="py-3.5 pr-3 pl-4 text-left text-sm font-semibold text-cool-steel-900 sm:pl-6 dark:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{col[:label]}
|
{col[:label]}
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -544,19 +548,19 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<tbody
|
<tbody
|
||||||
id={@id}
|
id={@id}
|
||||||
phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}
|
phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}
|
||||||
class="divide-y divide-gray-200 bg-white dark:divide-white/10 dark:bg-gray-800/50"
|
class="divide-y divide-cool-steel-200 bg-white dark:divide-white/10 dark:bg-cool-steel-800/50"
|
||||||
>
|
>
|
||||||
<tr
|
<tr
|
||||||
:for={row <- @rows}
|
:for={row <- @rows}
|
||||||
id={@row_id && @row_id.(row)}
|
id={@row_id && @row_id.(row)}
|
||||||
class="transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80"
|
class="transition-colors hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/80"
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
:for={{col, i} <- Enum.with_index(@col)}
|
:for={{col, i} <- Enum.with_index(@col)}
|
||||||
phx-click={@row_click && @row_click.(row)}
|
phx-click={@row_click && @row_click.(row)}
|
||||||
class={[
|
class={[
|
||||||
i == 0 && "font-medium text-gray-900 dark:text-white",
|
i == 0 && "font-medium text-cool-steel-900 dark:text-white",
|
||||||
i > 0 && "text-gray-500 dark:text-gray-400",
|
i > 0 && "text-cool-steel-500 dark:text-cool-steel-400",
|
||||||
@row_link && "p-0 text-sm whitespace-nowrap",
|
@row_link && "p-0 text-sm whitespace-nowrap",
|
||||||
!@row_link && "py-4 pr-3 pl-4 text-sm whitespace-nowrap sm:pl-6",
|
!@row_link && "py-4 pr-3 pl-4 text-sm whitespace-nowrap sm:pl-6",
|
||||||
@row_click && "cursor-pointer"
|
@row_click && "cursor-pointer"
|
||||||
|
|
@ -606,10 +610,10 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
|
|
||||||
def list(assigns) do
|
def list(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="overflow-hidden bg-white shadow rounded-lg divide-y divide-gray-200 dark:bg-gray-800/50 dark:divide-white/10">
|
<div class="overflow-hidden bg-white shadow rounded-lg divide-y divide-cool-steel-200 dark:bg-cool-steel-800/50 dark:divide-white/10">
|
||||||
<div :for={item <- @item} class="px-6 py-4">
|
<div :for={item <- @item} class="px-6 py-4">
|
||||||
<dt class="text-sm font-medium text-gray-900 dark:text-white">{item.title}</dt>
|
<dt class="text-sm font-medium text-cool-steel-900 dark:text-white">{item.title}</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-700 dark:text-gray-300">{render_slot(item)}</dd>
|
<dd class="mt-1 text-sm text-cool-steel-700 dark:text-cool-steel-300">{render_slot(item)}</dd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
@ -690,20 +694,20 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
def pagination(assigns) do
|
def pagination(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<%= if @meta.total_pages > 1 do %>
|
<%= if @meta.total_pages > 1 do %>
|
||||||
<div class="mt-6 flex items-center justify-between border-t border-gray-200 dark:border-white/10 pt-6">
|
<div class="mt-6 flex items-center justify-between border-t border-cool-steel-200 dark:border-white/10 pt-6">
|
||||||
<!-- Mobile: Previous/Next only -->
|
<!-- Mobile: Previous/Next only -->
|
||||||
<div class="flex flex-1 justify-between sm:hidden">
|
<div class="flex flex-1 justify-between sm:hidden">
|
||||||
<.link
|
<.link
|
||||||
:if={@meta.page > 1}
|
:if={@meta.page > 1}
|
||||||
patch={build_pagination_path(@path, @params, @meta.page - 1)}
|
patch={build_pagination_path(@path, @params, @meta.page - 1)}
|
||||||
class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
class="relative inline-flex items-center rounded-md border border-cool-steel-300 bg-white px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
:if={@meta.page < @meta.total_pages}
|
:if={@meta.page < @meta.total_pages}
|
||||||
patch={build_pagination_path(@path, @params, @meta.page + 1)}
|
patch={build_pagination_path(@path, @params, @meta.page + 1)}
|
||||||
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
class="relative ml-3 inline-flex items-center rounded-md border border-cool-steel-300 bg-white px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -711,7 +715,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<!-- Desktop: Full pagination with page numbers -->
|
<!-- Desktop: Full pagination with page numbers -->
|
||||||
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-700 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-700 dark:text-cool-steel-400">
|
||||||
Showing <span class="font-medium">{(@meta.page - 1) * @meta.per_page + 1}</span>
|
Showing <span class="font-medium">{(@meta.page - 1) * @meta.per_page + 1}</span>
|
||||||
to
|
to
|
||||||
<span class="font-medium">{min(@meta.page * @meta.per_page, @meta.total_count)}</span>
|
<span class="font-medium">{min(@meta.page * @meta.per_page, @meta.total_count)}</span>
|
||||||
|
|
@ -725,7 +729,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<.link
|
<.link
|
||||||
:if={@meta.page > 1}
|
:if={@meta.page > 1}
|
||||||
patch={build_pagination_path(@path, @params, @meta.page - 1)}
|
patch={build_pagination_path(@path, @params, @meta.page - 1)}
|
||||||
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:ring-white/10 dark:hover:bg-gray-800"
|
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-cool-steel-400 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:ring-white/10 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Previous</span>
|
<span class="sr-only">Previous</span>
|
||||||
<.icon name="hero-chevron-left" class="h-5 w-5" />
|
<.icon name="hero-chevron-left" class="h-5 w-5" />
|
||||||
|
|
@ -733,18 +737,18 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<!-- Page numbers with ellipsis -->
|
<!-- Page numbers with ellipsis -->
|
||||||
<%= for page_num <- pagination_range(@meta.page, @meta.total_pages) do %>
|
<%= for page_num <- pagination_range(@meta.page, @meta.total_pages) do %>
|
||||||
<%= if page_num == :ellipsis do %>
|
<%= if page_num == :ellipsis do %>
|
||||||
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300 dark:text-gray-400 dark:ring-white/10">
|
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-cool-steel-700 ring-1 ring-inset ring-cool-steel-300 dark:text-cool-steel-400 dark:ring-white/10">
|
||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
patch={build_pagination_path(@path, @params, page_num)}
|
patch={build_pagination_path(@path, @params, page_num)}
|
||||||
class={[
|
class={[
|
||||||
"relative inline-flex items-center px-4 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 dark:ring-white/10",
|
"relative inline-flex items-center px-4 py-2 text-sm font-semibold ring-1 ring-inset ring-cool-steel-300 dark:ring-white/10",
|
||||||
if @meta.page == page_num do
|
if @meta.page == page_num do
|
||||||
"z-10 bg-blue-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
"z-10 bg-cerulean-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cerulean-600"
|
||||||
else
|
else
|
||||||
"text-gray-900 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"text-cool-steel-900 hover:bg-cool-steel-50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -756,7 +760,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
<.link
|
<.link
|
||||||
:if={@meta.page < @meta.total_pages}
|
:if={@meta.page < @meta.total_pages}
|
||||||
patch={build_pagination_path(@path, @params, @meta.page + 1)}
|
patch={build_pagination_path(@path, @params, @meta.page + 1)}
|
||||||
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:ring-white/10 dark:hover:bg-gray-800"
|
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-cool-steel-400 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:ring-white/10 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Next</span>
|
<span class="sr-only">Next</span>
|
||||||
<.icon name="hero-chevron-right" class="h-5 w-5" />
|
<.icon name="hero-chevron-right" class="h-5 w-5" />
|
||||||
|
|
@ -878,7 +882,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
|
|
||||||
def signal_badge(%{value: nil} = assigns) do
|
def signal_badge(%{value: nil} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-gray-500/10 ring-inset dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
|
<span class="inline-flex items-center rounded-md bg-cool-steel-50 px-2 py-1 text-xs font-medium text-cool-steel-600 ring-1 ring-cool-steel-500/10 ring-inset dark:bg-cool-steel-400/10 dark:text-cool-steel-400 dark:ring-cool-steel-400/20">
|
||||||
—
|
—
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -903,13 +907,13 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
@color == "green" &&
|
@color == "green" &&
|
||||||
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/20",
|
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/20",
|
||||||
@color == "blue" &&
|
@color == "blue" &&
|
||||||
"bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/20",
|
"bg-cerulean-50 text-cerulean-700 ring-cerulean-600/20 dark:bg-cerulean-400/10 dark:text-cerulean-400 dark:ring-cerulean-400/20",
|
||||||
@color == "yellow" &&
|
@color == "yellow" &&
|
||||||
"bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20",
|
"bg-wheat-50 text-wheat-800 ring-wheat-600/20 dark:bg-wheat-400/10 dark:text-wheat-500 dark:ring-wheat-400/20",
|
||||||
@color == "orange" &&
|
@color == "orange" &&
|
||||||
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/20",
|
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/20",
|
||||||
@color == "red" &&
|
@color == "red" &&
|
||||||
"bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20"
|
"bg-sweet-salmon-50 text-sweet-salmon-700 ring-sweet-salmon-600/10 dark:bg-sweet-salmon-400/10 dark:text-sweet-salmon-400 dark:ring-sweet-salmon-400/20"
|
||||||
]}
|
]}
|
||||||
title={@label}
|
title={@label}
|
||||||
>
|
>
|
||||||
|
|
@ -937,7 +941,7 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
|
|
||||||
def snr_badge(%{value: nil} = assigns) do
|
def snr_badge(%{value: nil} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-gray-500/10 ring-inset dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20">
|
<span class="inline-flex items-center rounded-md bg-cool-steel-50 px-2 py-1 text-xs font-medium text-cool-steel-600 ring-1 ring-cool-steel-500/10 ring-inset dark:bg-cool-steel-400/10 dark:text-cool-steel-400 dark:ring-cool-steel-400/20">
|
||||||
—
|
—
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -962,13 +966,13 @@ defmodule ToweropsWeb.CoreComponents do
|
||||||
@color == "green" &&
|
@color == "green" &&
|
||||||
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/20",
|
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/20",
|
||||||
@color == "blue" &&
|
@color == "blue" &&
|
||||||
"bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/20",
|
"bg-cerulean-50 text-cerulean-700 ring-cerulean-600/20 dark:bg-cerulean-400/10 dark:text-cerulean-400 dark:ring-cerulean-400/20",
|
||||||
@color == "yellow" &&
|
@color == "yellow" &&
|
||||||
"bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20",
|
"bg-wheat-50 text-wheat-800 ring-wheat-600/20 dark:bg-wheat-400/10 dark:text-wheat-500 dark:ring-wheat-400/20",
|
||||||
@color == "orange" &&
|
@color == "orange" &&
|
||||||
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/20",
|
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/20",
|
||||||
@color == "red" &&
|
@color == "red" &&
|
||||||
"bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20"
|
"bg-sweet-salmon-50 text-sweet-salmon-700 ring-sweet-salmon-600/10 dark:bg-sweet-salmon-400/10 dark:text-sweet-salmon-400 dark:ring-sweet-salmon-400/20"
|
||||||
]}
|
]}
|
||||||
title={@label}
|
title={@label}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -49,25 +49,25 @@ defmodule ToweropsWeb.Layouts do
|
||||||
assigns = Map.put_new(assigns, :requires_cookie_consent, false)
|
assigns = Map.put_new(assigns, :requires_cookie_consent, false)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex min-h-screen flex-col bg-gray-50 dark:bg-gray-950">
|
<div class="flex min-h-screen flex-col bg-cool-steel-50 dark:bg-cool-steel-950">
|
||||||
<header class="border-b border-gray-200 bg-white dark:border-white/10 dark:bg-gray-900">
|
<header class="border-b border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
<nav class="flex h-16 items-center justify-between">
|
<nav class="flex h-16 items-center justify-between">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<.link navigate={~p"/"} class="text-xl font-bold text-gray-900 dark:text-white">
|
<.link navigate={~p"/"} class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
Towerops
|
Towerops
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/log-in"}
|
navigate={~p"/users/log-in"}
|
||||||
class="text-sm font-medium text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
class="text-sm font-medium text-cool-steel-700 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Log in
|
Log in
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/register"}
|
navigate={~p"/users/register"}
|
||||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
class="rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500"
|
||||||
>
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -142,11 +142,11 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<div
|
<div
|
||||||
id="sidebar-wrapper"
|
id="sidebar-wrapper"
|
||||||
phx-hook="SidebarCollapse"
|
phx-hook="SidebarCollapse"
|
||||||
class="min-h-screen bg-gray-50 dark:bg-gray-950"
|
class="min-h-screen bg-cool-steel-50 dark:bg-cool-steel-950"
|
||||||
>
|
>
|
||||||
<!-- Impersonation Banner -->
|
<!-- Impersonation Banner -->
|
||||||
<%= if @current_scope && @current_scope.impersonating? do %>
|
<%= if @current_scope && @current_scope.impersonating? do %>
|
||||||
<div class="bg-yellow-400 border-b-2 border-yellow-600 relative z-50">
|
<div class="bg-wheat-400 border-b-2 border-wheat-600 relative z-50">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -158,7 +158,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
href={~p"/admin/impersonate"}
|
href={~p"/admin/impersonate"}
|
||||||
method="delete"
|
method="delete"
|
||||||
class="px-3 py-1 bg-white dark:bg-gray-900 text-yellow-900 dark:text-yellow-200 hover:bg-yellow-50 dark:hover:bg-gray-800 rounded font-medium text-sm"
|
class="px-3 py-1 bg-white dark:bg-cool-steel-900 text-wheat-900 dark:text-wheat-200 hover:bg-wheat-50 dark:hover:bg-cool-steel-800 rounded font-medium text-sm"
|
||||||
>
|
>
|
||||||
Exit
|
Exit
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -168,7 +168,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Slim Top Bar -->
|
<!-- Slim Top Bar -->
|
||||||
<header class="sticky top-0 z-40 border-b border-gray-200 bg-white dark:border-white/10 dark:bg-gray-900">
|
<header class="sticky top-0 z-40 border-b border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="flex h-14 items-center justify-between px-4">
|
<div class="flex h-14 items-center justify-between px-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Mobile hamburger -->
|
<!-- Mobile hamburger -->
|
||||||
|
|
@ -184,7 +184,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
to: "#mobile-menu-button"
|
to: "#mobile-menu-button"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="inline-flex items-center justify-center rounded-md p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 focus:outline-none dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 lg:hidden"
|
class="inline-flex items-center justify-center rounded-md p-2 text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 focus:outline-none dark:text-cool-steel-400 dark:hover:bg-cool-steel-800 dark:hover:text-cool-steel-200 lg:hidden"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{t("Toggle main menu")}</span>
|
<span class="sr-only">{t("Toggle main menu")}</span>
|
||||||
<.icon name="hero-bars-3" class="size-6" />
|
<.icon name="hero-bars-3" class="size-6" />
|
||||||
|
|
@ -192,7 +192,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/dashboard"}
|
navigate={~p"/dashboard"}
|
||||||
class="text-lg font-bold text-gray-900 dark:text-white"
|
class="text-lg font-bold text-cool-steel-900 dark:text-white"
|
||||||
>
|
>
|
||||||
Towerops
|
Towerops
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -205,12 +205,12 @@ defmodule ToweropsWeb.Layouts do
|
||||||
type="button"
|
type="button"
|
||||||
id="global-search-trigger"
|
id="global-search-trigger"
|
||||||
phx-hook="GlobalSearchTrigger"
|
phx-hook="GlobalSearchTrigger"
|
||||||
class="inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg border border-cool-steel-200 bg-cool-steel-50 px-3 py-1.5 text-sm text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:border-white/10 dark:bg-white/5 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-cool-steel-200 transition-colors"
|
||||||
title="Search (⌘K)"
|
title="Search (⌘K)"
|
||||||
>
|
>
|
||||||
<.icon name="hero-magnifying-glass" class="h-4 w-4" />
|
<.icon name="hero-magnifying-glass" class="h-4 w-4" />
|
||||||
<span class="hidden sm:inline text-xs">{t("Search")}</span>
|
<span class="hidden sm:inline text-xs">{t("Search")}</span>
|
||||||
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border border-gray-300 bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium text-gray-500 dark:border-white/20 dark:bg-white/10 dark:text-gray-400">
|
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border border-cool-steel-300 bg-cool-steel-100 px-1.5 py-0.5 text-[10px] font-medium text-cool-steel-500 dark:border-white/20 dark:bg-white/10 dark:text-cool-steel-400">
|
||||||
⌘K
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -219,12 +219,12 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
:if={@current_organization}
|
:if={@current_organization}
|
||||||
navigate={~p"/alerts"}
|
navigate={~p"/alerts"}
|
||||||
class="relative p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors"
|
class="relative p-2 rounded-md text-cool-steel-500 hover:text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:text-cool-steel-200 dark:hover:bg-cool-steel-800 transition-colors"
|
||||||
title={t("Alerts")}
|
title={t("Alerts")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-bell" class="h-5 w-5" />
|
<.icon name="hero-bell" class="h-5 w-5" />
|
||||||
<%= if @unresolved_alert_count > 0 do %>
|
<%= if @unresolved_alert_count > 0 do %>
|
||||||
<span class="absolute top-0.5 right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-red-500 px-0.5 text-[10px] font-bold leading-none text-white ring-1 ring-white dark:ring-gray-950">
|
<span class="absolute top-0.5 right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-sweet-salmon-500 px-0.5 text-[10px] font-bold leading-none text-white ring-1 ring-white dark:ring-cool-steel-950">
|
||||||
<%= if @unresolved_alert_count > 100 do %>
|
<%= if @unresolved_alert_count > 100 do %>
|
||||||
|
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -237,7 +237,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<!-- Help icon -->
|
<!-- Help icon -->
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/help"}
|
navigate={~p"/help"}
|
||||||
class="p-2 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors"
|
class="p-2 rounded-md text-cool-steel-500 hover:text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:text-cool-steel-200 dark:hover:bg-cool-steel-800 transition-colors"
|
||||||
title={t("Help")}
|
title={t("Help")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-question-mark-circle" class="h-5 w-5" />
|
<.icon name="hero-question-mark-circle" class="h-5 w-5" />
|
||||||
|
|
@ -254,7 +254,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
JS.toggle(to: "#org-menu")
|
JS.toggle(to: "#org-menu")
|
||||||
|> JS.toggle_attribute({"aria-expanded", "true", "false"}, to: "#org-menu-button")
|
|> JS.toggle_attribute({"aria-expanded", "true", "false"}, to: "#org-menu-button")
|
||||||
}
|
}
|
||||||
class="inline-flex items-center justify-center rounded-full p-1.5 text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors"
|
class="inline-flex items-center justify-center rounded-full p-1.5 text-cool-steel-500 hover:text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:text-cool-steel-200 dark:hover:bg-cool-steel-800 transition-colors"
|
||||||
title={t("Menu")}
|
title={t("Menu")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-user-circle" class="h-6 w-6" />
|
<.icon name="hero-user-circle" class="h-6 w-6" />
|
||||||
|
|
@ -263,7 +263,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<div
|
<div
|
||||||
id="org-menu"
|
id="org-menu"
|
||||||
role="menu"
|
role="menu"
|
||||||
class="hidden absolute top-full right-0 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 z-50 dark:bg-gray-800 dark:ring-white/10"
|
class="hidden absolute top-full right-0 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 z-50 dark:bg-cool-steel-800 dark:ring-white/10"
|
||||||
phx-click-away={
|
phx-click-away={
|
||||||
JS.hide(to: "#org-menu")
|
JS.hide(to: "#org-menu")
|
||||||
|> JS.set_attribute({"aria-expanded", "false"}, to: "#org-menu-button")
|
|> JS.set_attribute({"aria-expanded", "false"}, to: "#org-menu-button")
|
||||||
|
|
@ -274,7 +274,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/admin"}
|
navigate={~p"/admin"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#org-menu")}
|
phx-click={JS.hide(to: "#org-menu")}
|
||||||
>
|
>
|
||||||
{t("Admin Panel")}
|
{t("Admin Panel")}
|
||||||
|
|
@ -283,7 +283,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/users/settings"}
|
navigate={~p"/users/settings"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#org-menu")}
|
phx-click={JS.hide(to: "#org-menu")}
|
||||||
>
|
>
|
||||||
{t("User Settings")}
|
{t("User Settings")}
|
||||||
|
|
@ -291,7 +291,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/mobile/qr-login"}
|
navigate={~p"/mobile/qr-login"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#org-menu")}
|
phx-click={JS.hide(to: "#org-menu")}
|
||||||
>
|
>
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
|
|
@ -302,7 +302,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/users/my-data"}
|
navigate={~p"/users/my-data"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#org-menu")}
|
phx-click={JS.hide(to: "#org-menu")}
|
||||||
>
|
>
|
||||||
{t("My Data")}
|
{t("My Data")}
|
||||||
|
|
@ -310,17 +310,17 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/changelog"}
|
navigate={~p"/changelog"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#org-menu")}
|
phx-click={JS.hide(to: "#org-menu")}
|
||||||
>
|
>
|
||||||
{t("What's New")}
|
{t("What's New")}
|
||||||
</.link>
|
</.link>
|
||||||
<div class="border-t border-gray-100 dark:border-white/10"></div>
|
<div class="border-t border-cool-steel-100 dark:border-white/10"></div>
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
href={~p"/users/log-out"}
|
href={~p"/users/log-out"}
|
||||||
method="delete"
|
method="delete"
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
{t("Log out")}
|
{t("Log out")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -337,14 +337,14 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<aside
|
<aside
|
||||||
:if={@current_organization || (@current_scope && @current_scope.user)}
|
:if={@current_organization || (@current_scope && @current_scope.user)}
|
||||||
id="app-sidebar"
|
id="app-sidebar"
|
||||||
class="sticky top-14 left-0 z-30 hidden lg:flex lg:flex-col bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-white/10 overflow-y-auto overflow-x-hidden h-[calc(100vh-3.5rem)]"
|
class="sticky top-14 left-0 z-30 hidden lg:flex lg:flex-col bg-cool-steel-800 dark:bg-cool-steel-900 border-r border-cool-steel-700 dark:border-white/10 overflow-y-auto overflow-x-hidden h-[calc(100vh-3.5rem)]"
|
||||||
>
|
>
|
||||||
<!-- Nav sections -->
|
<!-- Nav sections -->
|
||||||
<nav class="flex-1 px-2 py-4 space-y-1">
|
<nav class="flex-1 px-2 py-4 space-y-1">
|
||||||
<!-- MONITOR section -->
|
<!-- MONITOR section -->
|
||||||
<p
|
<p
|
||||||
data-sidebar-section
|
data-sidebar-section
|
||||||
class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
|
class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
>
|
>
|
||||||
{t("Monitor")}
|
{t("Monitor")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -389,7 +389,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<!-- RESPOND section -->
|
<!-- RESPOND section -->
|
||||||
<p
|
<p
|
||||||
data-sidebar-section
|
data-sidebar-section
|
||||||
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
|
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-300 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Respond")}
|
{t("Respond")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -416,7 +416,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<!-- ANALYZE section -->
|
<!-- ANALYZE section -->
|
||||||
<p
|
<p
|
||||||
data-sidebar-section
|
data-sidebar-section
|
||||||
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500"
|
class="px-3 mt-4 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-300 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Analyze")}
|
{t("Analyze")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -455,7 +455,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Bottom pinned: org switcher + settings + collapse toggle -->
|
<!-- Bottom pinned: org switcher + settings + collapse toggle -->
|
||||||
<div class="border-t border-gray-200 dark:border-white/10 px-2 py-3.5 space-y-1">
|
<div class="border-t border-cool-steel-200 dark:border-white/10 px-2 py-3.5 space-y-1">
|
||||||
<!-- Org switcher -->
|
<!-- Org switcher -->
|
||||||
<div :if={@current_organization} class="relative">
|
<div :if={@current_organization} class="relative">
|
||||||
<button
|
<button
|
||||||
|
|
@ -469,7 +469,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
to: "#sidebar-org-switcher-button"
|
to: "#sidebar-org-switcher-button"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="flex items-center gap-2 w-full rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white transition-colors"
|
class="flex items-center gap-2 w-full rounded-md px-3 py-2 text-sm font-medium text-cool-steel-200 hover:bg-cool-steel-700 hover:text-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700 dark:hover:text-cool-steel-100 transition-colors"
|
||||||
data-sidebar-tooltip={@current_organization.name}
|
data-sidebar-tooltip={@current_organization.name}
|
||||||
>
|
>
|
||||||
<.icon name="hero-building-office-2" class="h-5 w-5 shrink-0" />
|
<.icon name="hero-building-office-2" class="h-5 w-5 shrink-0" />
|
||||||
|
|
@ -483,7 +483,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<div
|
<div
|
||||||
id="sidebar-org-switcher-menu"
|
id="sidebar-org-switcher-menu"
|
||||||
role="menu"
|
role="menu"
|
||||||
class="hidden absolute bottom-full left-0 mb-1 w-48 origin-bottom-left rounded-md bg-white shadow-lg ring-1 ring-black/5 z-50 dark:bg-gray-800 dark:ring-white/10"
|
class="hidden absolute bottom-full left-0 mb-1 w-48 origin-bottom-left rounded-md bg-white shadow-lg ring-1 ring-black/5 z-50 dark:bg-cool-steel-800 dark:ring-white/10"
|
||||||
phx-click-away={
|
phx-click-away={
|
||||||
JS.hide(to: "#sidebar-org-switcher-menu")
|
JS.hide(to: "#sidebar-org-switcher-menu")
|
||||||
|> JS.set_attribute({"aria-expanded", "false"}, to: "#sidebar-org-switcher-button")
|
|> JS.set_attribute({"aria-expanded", "false"}, to: "#sidebar-org-switcher-button")
|
||||||
|
|
@ -493,7 +493,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/orgs"}
|
navigate={~p"/orgs"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#sidebar-org-switcher-menu")}
|
phx-click={JS.hide(to: "#sidebar-org-switcher-menu")}
|
||||||
>
|
>
|
||||||
{t("Switch Organization")}
|
{t("Switch Organization")}
|
||||||
|
|
@ -501,7 +501,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<.link
|
<.link
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
navigate={~p"/orgs/#{@current_organization.slug}/settings"}
|
navigate={~p"/orgs/#{@current_organization.slug}/settings"}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
class="block px-4 py-2 text-sm text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
phx-click={JS.hide(to: "#sidebar-org-switcher-menu")}
|
phx-click={JS.hide(to: "#sidebar-org-switcher-menu")}
|
||||||
>
|
>
|
||||||
{t("Organization Settings")}
|
{t("Organization Settings")}
|
||||||
|
|
@ -523,7 +523,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-sidebar-toggle
|
data-sidebar-toggle
|
||||||
class="flex items-center justify-center w-full rounded-md px-3 py-2 text-gray-400 hover:bg-gray-50 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300 transition-colors"
|
class="flex items-center justify-center w-full rounded-md px-3 py-2 text-cool-steel-300 hover:bg-cool-steel-700 hover:text-cool-steel-100 dark:hover:bg-cool-steel-700 dark:hover:text-cool-steel-200 transition-colors"
|
||||||
title={t("Toggle sidebar")}
|
title={t("Toggle sidebar")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-double-left" class="h-5 w-5 sidebar-collapse-icon" />
|
<.icon name="hero-chevron-double-left" class="h-5 w-5 sidebar-collapse-icon" />
|
||||||
|
|
@ -544,13 +544,13 @@ defmodule ToweropsWeb.Layouts do
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- Panel -->
|
<!-- Panel -->
|
||||||
<div class="fixed inset-y-0 left-0 w-72 bg-white dark:bg-gray-900 shadow-xl overflow-y-auto">
|
<div class="fixed inset-y-0 left-0 w-72 bg-cool-steel-800 dark:bg-cool-steel-900 shadow-xl overflow-y-auto">
|
||||||
<div class="flex items-center justify-between px-4 py-4 border-b border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between px-4 py-4 border-b border-cool-steel-700 dark:border-white/10">
|
||||||
<span class="text-lg font-bold text-gray-900 dark:text-white">Towerops</span>
|
<span class="text-lg font-bold text-cool-steel-100 dark:text-white">Towerops</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click={JS.hide(to: "#mobile-menu")}
|
phx-click={JS.hide(to: "#mobile-menu")}
|
||||||
class="p-2 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800"
|
class="p-2 rounded-md text-cool-steel-300 hover:bg-cool-steel-700 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="size-5" />
|
<.icon name="hero-x-mark" class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -559,19 +559,19 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<!-- Org name -->
|
<!-- Org name -->
|
||||||
<div
|
<div
|
||||||
:if={@current_organization}
|
:if={@current_organization}
|
||||||
class="px-4 py-3 border-b border-gray-100 dark:border-white/5"
|
class="px-4 py-3 border-b border-cool-steel-100 dark:border-white/5"
|
||||||
>
|
>
|
||||||
<p class="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<p class="text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Organization")}
|
{t("Organization")}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm font-semibold text-gray-900 dark:text-white mt-0.5">
|
<p class="text-sm font-semibold text-cool-steel-900 dark:text-white mt-0.5">
|
||||||
{@current_organization.name}
|
{@current_organization.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MONITOR -->
|
<!-- MONITOR -->
|
||||||
<div class="px-2 py-3">
|
<div class="px-2 py-3">
|
||||||
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Monitor")}
|
{t("Monitor")}
|
||||||
</p>
|
</p>
|
||||||
<.mobile_nav_link navigate={~p"/dashboard"} active={@active_page == "dashboard"}>
|
<.mobile_nav_link navigate={~p"/dashboard"} active={@active_page == "dashboard"}>
|
||||||
|
|
@ -596,8 +596,8 @@ defmodule ToweropsWeb.Layouts do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RESPOND -->
|
<!-- RESPOND -->
|
||||||
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
|
<div class="px-2 py-3 border-t border-cool-steel-100 dark:border-white/5">
|
||||||
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Respond")}
|
{t("Respond")}
|
||||||
</p>
|
</p>
|
||||||
<.mobile_nav_link navigate={~p"/alerts"} active={@active_page == "alerts"}>
|
<.mobile_nav_link navigate={~p"/alerts"} active={@active_page == "alerts"}>
|
||||||
|
|
@ -612,8 +612,8 @@ defmodule ToweropsWeb.Layouts do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ANALYZE -->
|
<!-- ANALYZE -->
|
||||||
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
|
<div class="px-2 py-3 border-t border-cool-steel-100 dark:border-white/5">
|
||||||
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Analyze")}
|
{t("Analyze")}
|
||||||
</p>
|
</p>
|
||||||
<.mobile_nav_link navigate={~p"/insights"} active={@active_page == "insights"}>
|
<.mobile_nav_link navigate={~p"/insights"} active={@active_page == "insights"}>
|
||||||
|
|
@ -628,8 +628,8 @@ defmodule ToweropsWeb.Layouts do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Account -->
|
<!-- Account -->
|
||||||
<div class="px-2 py-3 border-t border-gray-100 dark:border-white/5">
|
<div class="px-2 py-3 border-t border-cool-steel-100 dark:border-white/5">
|
||||||
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<p class="px-3 mb-1 text-[10px] font-semibold uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Account")}
|
{t("Account")}
|
||||||
</p>
|
</p>
|
||||||
<.mobile_nav_link
|
<.mobile_nav_link
|
||||||
|
|
@ -661,22 +661,22 @@ defmodule ToweropsWeb.Layouts do
|
||||||
id="beta-banner"
|
id="beta-banner"
|
||||||
phx-hook="BetaBannerDismiss"
|
phx-hook="BetaBannerDismiss"
|
||||||
data-always-show={@always_show_banner}
|
data-always-show={@always_show_banner}
|
||||||
class="mb-6 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4 border border-blue-200 dark:border-blue-800"
|
class="mb-6 rounded-lg bg-cerulean-50 dark:bg-cerulean-900/20 p-4 border border-cerulean-200 dark:border-cerulean-800"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-heart"
|
name="hero-heart"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
<h3 class="text-sm font-semibold text-cerulean-900 dark:text-cerulean-300 mb-1">
|
||||||
Thanks for testing Towerops!
|
Thanks for testing Towerops!
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-blue-800 dark:text-blue-300">
|
<p class="text-sm text-cerulean-800 dark:text-cerulean-300">
|
||||||
We'd love to hear your feedback, suggestions, or help you with any questions. Please email us at
|
We'd love to hear your feedback, suggestions, or help you with any questions. Please email us at
|
||||||
<a
|
<a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="font-medium underline hover:text-blue-900 dark:hover:text-blue-200"
|
class="font-medium underline hover:text-cerulean-900 dark:hover:text-cerulean-200"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -686,7 +686,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-dismiss
|
data-dismiss
|
||||||
class="flex-shrink-0 rounded-md p-1.5 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-800/50 focus:outline-none focus:ring-2 focus:ring-blue-600 dark:focus:ring-blue-400"
|
class="flex-shrink-0 rounded-md p-1.5 text-cerulean-600 dark:text-cerulean-400 hover:bg-cerulean-100 dark:hover:bg-cerulean-800/50 focus:outline-none focus:ring-2 focus:ring-cerulean-600 dark:focus:ring-cerulean-400"
|
||||||
aria-label="Dismiss banner"
|
aria-label="Dismiss banner"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-5 w-5" />
|
<.icon name="hero-x-mark" class="h-5 w-5" />
|
||||||
|
|
@ -763,9 +763,9 @@ defmodule ToweropsWeb.Layouts do
|
||||||
"relative flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
"relative flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
||||||
if(@active,
|
if(@active,
|
||||||
do:
|
do:
|
||||||
"bg-blue-50 text-blue-700 border-l-2 border-blue-600 dark:bg-blue-900/20 dark:text-blue-400",
|
"bg-cerulean-800/30 text-cerulean-300 border-l-2 border-cerulean-400 dark:bg-cerulean-900/20 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
"text-cool-steel-200 hover:bg-cool-steel-700 hover:text-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700 dark:hover:text-cool-steel-100"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
data-sidebar-tooltip={@label}
|
data-sidebar-tooltip={@label}
|
||||||
|
|
@ -774,7 +774,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<%= if @badge > 0 do %>
|
<%= if @badge > 0 do %>
|
||||||
<span
|
<span
|
||||||
data-sidebar-text
|
data-sidebar-text
|
||||||
class="ml-auto inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
class="ml-auto inline-flex items-center rounded-full bg-sweet-salmon-100 px-2 py-0.5 text-xs font-medium text-sweet-salmon-700 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
>
|
>
|
||||||
{if @badge > 99, do: "99+", else: @badge}
|
{if @badge > 99, do: "99+", else: @badge}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -794,9 +794,9 @@ defmodule ToweropsWeb.Layouts do
|
||||||
class={[
|
class={[
|
||||||
"flex items-center gap-3 rounded-md px-3 py-2 text-base font-medium",
|
"flex items-center gap-3 rounded-md px-3 py-2 text-base font-medium",
|
||||||
if(@active,
|
if(@active,
|
||||||
do: "bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-white",
|
do: "bg-cool-steel-100 text-cool-steel-900 dark:bg-cool-steel-800 dark:text-white",
|
||||||
else:
|
else:
|
||||||
"text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white"
|
"text-cool-steel-600 hover:bg-cool-steel-50 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800 dark:hover:text-white"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -874,8 +874,8 @@ defmodule ToweropsWeb.Layouts do
|
||||||
assigns = Map.put_new(assigns, :requires_cookie_consent, false)
|
assigns = Map.put_new(assigns, :requires_cookie_consent, false)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex min-h-screen flex-col bg-gray-50 dark:bg-gray-950">
|
<div class="flex min-h-screen flex-col bg-cool-steel-50 dark:bg-cool-steel-950">
|
||||||
<nav class="bg-white border-b border-gray-200 dark:border-white/10 dark:bg-gray-900">
|
<nav class="bg-white border-b border-cool-steel-200 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex justify-between h-14 sm:h-16">
|
<div class="flex justify-between h-14 sm:h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|
@ -888,7 +888,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
|> JS.toggle(to: "#admin-menu-icon-open")
|
|> JS.toggle(to: "#admin-menu-icon-open")
|
||||||
|> JS.toggle(to: "#admin-menu-icon-close")
|
|> JS.toggle(to: "#admin-menu-icon-close")
|
||||||
}
|
}
|
||||||
class="inline-flex items-center justify-center rounded-md p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 focus:outline-none dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200 sm:hidden"
|
class="inline-flex items-center justify-center rounded-md p-2 text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 focus:outline-none dark:text-cool-steel-400 dark:hover:bg-cool-steel-800 dark:hover:text-cool-steel-200 sm:hidden"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Open admin menu</span>
|
<span class="sr-only">Open admin menu</span>
|
||||||
<span id="admin-menu-icon-open"><.icon name="hero-bars-3" class="size-6" /></span>
|
<span id="admin-menu-icon-open"><.icon name="hero-bars-3" class="size-6" /></span>
|
||||||
|
|
@ -899,7 +899,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin"}
|
navigate={~p"/admin"}
|
||||||
class="flex items-center text-lg sm:text-xl font-bold text-gray-900 dark:text-white ml-2 sm:ml-0"
|
class="flex items-center text-lg sm:text-xl font-bold text-cool-steel-900 dark:text-white ml-2 sm:ml-0"
|
||||||
>
|
>
|
||||||
<span class="hidden sm:inline">Towerops Admin</span>
|
<span class="hidden sm:inline">Towerops Admin</span>
|
||||||
<span class="sm:hidden">Admin</span>
|
<span class="sm:hidden">Admin</span>
|
||||||
|
|
@ -909,25 +909,25 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/users"}
|
navigate={~p"/admin/users"}
|
||||||
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-cool-steel-700 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Users
|
Users
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/organizations"}
|
navigate={~p"/admin/organizations"}
|
||||||
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-cool-steel-700 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Organizations
|
Organizations
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/security"}
|
navigate={~p"/admin/security"}
|
||||||
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-cool-steel-700 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
IP Allowlist
|
IP Allowlist
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/errors"}
|
navigate={~p"/admin/errors"}
|
||||||
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white"
|
class="inline-flex items-center px-1 pt-1 pb-4 text-sm font-medium text-cool-steel-700 hover:text-cool-steel-900 dark:text-cool-steel-300 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Exceptions
|
Exceptions
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -936,7 +936,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
<div class="flex items-center gap-1 sm:gap-2">
|
<div class="flex items-center gap-1 sm:gap-2">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs"}
|
navigate={~p"/orgs"}
|
||||||
class="text-xs sm:text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="text-xs sm:text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<span class="hidden sm:inline">Back to App</span>
|
<span class="hidden sm:inline">Back to App</span>
|
||||||
<span class="sm:hidden">App</span>
|
<span class="sm:hidden">App</span>
|
||||||
|
|
@ -947,7 +947,7 @@ defmodule ToweropsWeb.Layouts do
|
||||||
|
|
||||||
<!-- Mobile menu -->
|
<!-- Mobile menu -->
|
||||||
<div class="hidden sm:hidden" id="admin-mobile-menu">
|
<div class="hidden sm:hidden" id="admin-mobile-menu">
|
||||||
<div class="space-y-1 px-2 pb-3 pt-2 border-t border-gray-200 dark:border-white/10">
|
<div class="space-y-1 px-2 pb-3 pt-2 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<.mobile_nav_link navigate={~p"/admin/users"} active={false}>
|
<.mobile_nav_link navigate={~p"/admin/users"} active={false}>
|
||||||
Users
|
Users
|
||||||
</.mobile_nav_link>
|
</.mobile_nav_link>
|
||||||
|
|
@ -994,28 +994,28 @@ defmodule ToweropsWeb.Layouts do
|
||||||
|> assign(:formatted_time, TimeHelpers.format_iso8601(build_time, timezone))
|
|> assign(:formatted_time, TimeHelpers.format_iso8601(build_time, timezone))
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<footer class="border-t border-gray-200 bg-white dark:border-white/10 dark:bg-gray-900">
|
<footer class="border-t border-cool-steel-700 bg-cool-steel-800 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
||||||
<div class="flex flex-col items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
<div class="flex flex-col items-center gap-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p>Copyright © {Date.utc_today().year} Towerops</p>
|
<p>Copyright © {Date.utc_today().year} Towerops</p>
|
||||||
<p class="text-xs">
|
<p class="text-xs">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/privacy"}
|
navigate={~p"/privacy"}
|
||||||
class="hover:text-gray-900 dark:hover:text-gray-200 underline decoration-dotted underline-offset-2"
|
class="hover:text-cool-steel-900 dark:hover:text-cool-steel-200 underline decoration-dotted underline-offset-2"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</.link>
|
</.link>
|
||||||
<span class="mx-2">·</span>
|
<span class="mx-2">·</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/terms"}
|
navigate={~p"/terms"}
|
||||||
class="hover:text-gray-900 dark:hover:text-gray-200 underline decoration-dotted underline-offset-2"
|
class="hover:text-cool-steel-900 dark:hover:text-cool-steel-200 underline decoration-dotted underline-offset-2"
|
||||||
>
|
>
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</.link>
|
</.link>
|
||||||
<span class="mx-2">·</span>
|
<span class="mx-2">·</span>
|
||||||
<.link
|
<.link
|
||||||
href="/changelog.txt"
|
href="/changelog.txt"
|
||||||
class="hover:text-gray-900 dark:hover:text-gray-200 underline decoration-dotted underline-offset-2"
|
class="hover:text-cool-steel-900 dark:hover:text-cool-steel-200 underline decoration-dotted underline-offset-2"
|
||||||
>
|
>
|
||||||
Changelog
|
Changelog
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
<body>
|
<body>
|
||||||
<a
|
<a
|
||||||
href="#main-content"
|
href="#main-content"
|
||||||
class="sr-only focus:not-sr-only focus:absolute focus:top-0 focus:left-0 focus:z-50 focus:p-4 focus:bg-blue-600 focus:text-white focus:rounded-md focus:m-2"
|
class="sr-only focus:not-sr-only focus:absolute focus:top-0 focus:left-0 focus:z-50 focus:p-4 focus:bg-cerulean-600 focus:text-white focus:rounded-md focus:m-2"
|
||||||
>
|
>
|
||||||
{t("Skip to main content")}
|
{t("Skip to main content")}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ defmodule ToweropsWeb.MarketingLayouts do
|
||||||
</div>
|
</div>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/register"}
|
navigate={~p"/users/register"}
|
||||||
class="group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600"
|
class="group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 bg-cerulean-600 text-white hover:text-slate-100 hover:bg-cerulean-500 active:bg-cerulean-800 active:text-cerulean-100 focus-visible:outline-cerulean-600"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Get started <span class="hidden lg:inline">today</span>
|
Get started <span class="hidden lg:inline">today</span>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
assigns = assign_new(assigns, :class, fn -> "" end)
|
assigns = assign_new(assigns, :class, fn -> "" end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class={["rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer", @class]}></div>
|
<div class={["rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer", @class]}></div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
def skeleton_card(assigns) do
|
def skeleton_card(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class={[
|
<div class={[
|
||||||
"animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50",
|
"animate-pulse rounded-lg border border-cool-steel-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-cool-steel-800/50",
|
||||||
@class
|
@class
|
||||||
]}>
|
]}>
|
||||||
<.skel class="h-3 w-24" />
|
<.skel class="h-3 w-24" />
|
||||||
|
|
@ -39,13 +39,13 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
|
|
||||||
def skeleton_table(assigns) do
|
def skeleton_table(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="border-b border-gray-200 bg-gray-50 px-6 py-3.5 dark:border-white/10 dark:bg-gray-800/75">
|
<div class="border-b border-cool-steel-200 bg-cool-steel-50 px-6 py-3.5 dark:border-white/10 dark:bg-cool-steel-800/75">
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<div
|
<div
|
||||||
:for={_ <- 1..@cols}
|
:for={_ <- 1..@cols}
|
||||||
class="h-3 w-20 animate-pulse rounded bg-gray-300 dark:bg-gray-600"
|
class="h-3 w-20 animate-pulse rounded bg-cool-steel-300 dark:bg-cool-steel-600"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -54,16 +54,16 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
<div
|
<div
|
||||||
:for={i <- 1..@rows}
|
:for={i <- 1..@rows}
|
||||||
class={[
|
class={[
|
||||||
"border-b border-gray-100 px-6 py-4 last:border-0 dark:border-white/5",
|
"border-b border-cool-steel-100 px-6 py-4 last:border-0 dark:border-white/5",
|
||||||
rem(i, 2) == 0 && "bg-gray-50/50 dark:bg-gray-800/25"
|
rem(i, 2) == 0 && "bg-cool-steel-50/50 dark:bg-cool-steel-800/25"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-8">
|
<div class="flex items-center gap-8">
|
||||||
<div class="h-3 w-24 animate-pulse rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer">
|
<div class="h-3 w-24 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:for={_ <- 2..@cols}
|
:for={_ <- 2..@cols}
|
||||||
class="h-3 w-16 animate-pulse rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"
|
class="h-3 w-16 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,15 +80,17 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
def skeleton_stat(assigns) do
|
def skeleton_stat(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class={[
|
<div class={[
|
||||||
"animate-pulse rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-800/50",
|
"animate-pulse rounded-lg border border-cool-steel-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-cool-steel-800/50",
|
||||||
@class
|
@class
|
||||||
]}>
|
]}>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="h-5 w-5 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-5 w-5 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"></div>
|
||||||
<div class="h-3 w-20 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-3 w-20 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 h-8 w-16 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 h-2.5 w-14 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 h-8 w-16 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
|
||||||
<div class="mt-1.5 h-2.5 w-14 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
@ -107,7 +109,7 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
<div
|
<div
|
||||||
:for={w <- [28, 24, 32, 20]}
|
:for={w <- [28, 24, 32, 20]}
|
||||||
class={[
|
class={[
|
||||||
"h-9 animate-pulse rounded-lg bg-gray-200 dark:bg-gray-700 skeleton-shimmer",
|
"h-9 animate-pulse rounded-lg bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer",
|
||||||
"w-#{w}"
|
"w-#{w}"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -117,37 +119,42 @@ defmodule ToweropsWeb.Components.Skeletons do
|
||||||
<div class="mt-8 grid gap-8 lg:grid-cols-5">
|
<div class="mt-8 grid gap-8 lg:grid-cols-5">
|
||||||
<!-- Main column: alerts/activity -->
|
<!-- Main column: alerts/activity -->
|
||||||
<div class="lg:col-span-3">
|
<div class="lg:col-span-3">
|
||||||
<div class="h-5 w-32 animate-pulse rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer mb-4">
|
<div class="h-5 w-32 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer mb-4">
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div
|
<div
|
||||||
:for={_ <- 1..3}
|
:for={_ <- 1..3}
|
||||||
class="animate-pulse rounded-lg border border-gray-200 bg-white p-4 dark:border-white/10 dark:bg-gray-800/50"
|
class="animate-pulse rounded-lg border border-cool-steel-200 bg-white p-4 dark:border-white/10 dark:bg-cool-steel-800/50"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="h-8 w-8 rounded-full bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-8 w-8 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="h-3.5 w-40 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-3.5 w-40 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
<div class="mt-2 h-3 w-64 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer">
|
</div>
|
||||||
|
<div class="mt-2 h-3 w-64 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-3 w-12 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-3 w-12 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Sidebar column: recent/quick stats -->
|
<!-- Sidebar column: recent/quick stats -->
|
||||||
<div class="lg:col-span-2">
|
<div class="lg:col-span-2">
|
||||||
<div class="h-5 w-24 animate-pulse rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer mb-4">
|
<div class="h-5 w-24 animate-pulse rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer mb-4">
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div
|
<div
|
||||||
:for={_ <- 1..4}
|
:for={_ <- 1..4}
|
||||||
class="animate-pulse rounded-lg border border-gray-200 bg-white p-3 dark:border-white/10 dark:bg-gray-800/50"
|
class="animate-pulse rounded-lg border border-cool-steel-200 bg-white p-3 dark:border-white/10 dark:bg-cool-steel-800/50"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="h-3 w-28 rounded bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
<div class="h-3 w-28 rounded bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
<div class="h-5 w-10 rounded-full bg-gray-200 dark:bg-gray-700 skeleton-shimmer"></div>
|
</div>
|
||||||
|
<div class="h-5 w-10 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 skeleton-shimmer">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -6,14 +6,14 @@
|
||||||
<title>{t("404 - Page Not Found")}</title>
|
<title>{t("404 - Page Not Found")}</title>
|
||||||
<link phx-track-static rel="stylesheet" href="/assets/css/app.css" />
|
<link phx-track-static rel="stylesheet" href="/assets/css/app.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white dark:bg-gray-900">
|
<body class="bg-white dark:bg-cool-steel-900">
|
||||||
<div class="min-h-screen flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
<div class="min-h-screen flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-2xl w-full text-center">
|
<div class="max-w-2xl w-full text-center">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<img src="/images/towerops_logo.png" alt="Towerops" class="h-16 w-auto mx-auto mb-8" />
|
<img src="/images/towerops_logo.png" alt="Towerops" class="h-16 w-auto mx-auto mb-8" />
|
||||||
|
|
||||||
<!-- Error Code -->
|
<!-- Error Code -->
|
||||||
<h1 class="text-9xl font-bold text-blue-600 mb-4">404</h1>
|
<h1 class="text-9xl font-bold text-cerulean-600 mb-4">404</h1>
|
||||||
|
|
||||||
<!-- Squirrel Image -->
|
<!-- Squirrel Image -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<!-- Back Home Button -->
|
<!-- Back Home Button -->
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
class="inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold bg-blue-600 text-white hover:bg-blue-500 active:bg-blue-800 transition-colors"
|
class="inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold bg-cerulean-600 text-white hover:bg-cerulean-500 active:bg-cerulean-800 transition-colors"
|
||||||
>
|
>
|
||||||
{t("Go back home")}
|
{t("Go back home")}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,14 @@
|
||||||
<title>{t("500 - Server Error")}</title>
|
<title>{t("500 - Server Error")}</title>
|
||||||
<link phx-track-static rel="stylesheet" href="/assets/css/app.css" />
|
<link phx-track-static rel="stylesheet" href="/assets/css/app.css" />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white dark:bg-gray-900">
|
<body class="bg-white dark:bg-cool-steel-900">
|
||||||
<div class="min-h-screen flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
<div class="min-h-screen flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
<div class="max-w-2xl w-full text-center">
|
<div class="max-w-2xl w-full text-center">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<img src="/images/towerops_logo.png" alt="Towerops" class="h-16 w-auto mx-auto mb-8" />
|
<img src="/images/towerops_logo.png" alt="Towerops" class="h-16 w-auto mx-auto mb-8" />
|
||||||
|
|
||||||
<!-- Error Code -->
|
<!-- Error Code -->
|
||||||
<h1 class="text-9xl font-bold text-blue-600 mb-4">500</h1>
|
<h1 class="text-9xl font-bold text-cerulean-600 mb-4">500</h1>
|
||||||
|
|
||||||
<!-- Error Message -->
|
<!-- Error Message -->
|
||||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">
|
<h2 class="text-3xl font-bold text-slate-900 mb-4">
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<!-- Back Home Button -->
|
<!-- Back Home Button -->
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
class="inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold bg-blue-600 text-white hover:bg-blue-500 active:bg-blue-800 transition-colors"
|
class="inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold bg-cerulean-600 text-white hover:bg-cerulean-500 active:bg-cerulean-800 transition-colors"
|
||||||
>
|
>
|
||||||
{t("Go back home")}
|
{t("Go back home")}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#introduction"
|
href="#introduction"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Introduction
|
Introduction
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#authentication"
|
href="#authentication"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Authentication
|
Authentication
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#endpoint"
|
href="#endpoint"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Endpoint
|
Endpoint
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-devices"
|
href="#query-devices"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Devices
|
Devices
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-sites"
|
href="#query-sites"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Sites
|
Sites
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-alerts"
|
href="#query-alerts"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Alerts
|
Alerts
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-agents"
|
href="#query-agents"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Agents
|
Agents
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-organization"
|
href="#query-organization"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Organization
|
Organization
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-members"
|
href="#query-members"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-integrations"
|
href="#query-integrations"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Integrations
|
Integrations
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-activity"
|
href="#query-activity"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Activity
|
Activity
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -113,7 +113,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-metrics"
|
href="#query-metrics"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Metrics & Interfaces
|
Metrics & Interfaces
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-time-series"
|
href="#query-time-series"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Time-Series
|
Time-Series
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-checks"
|
href="#query-checks"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Service Checks
|
Service Checks
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-schedules"
|
href="#query-schedules"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
On-Call Schedules
|
On-Call Schedules
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -145,7 +145,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-escalation-policies"
|
href="#query-escalation-policies"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Escalation Policies
|
Escalation Policies
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#query-maintenance-windows"
|
href="#query-maintenance-windows"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Maintenance Windows
|
Maintenance Windows
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-devices"
|
href="#mutation-devices"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Device CRUD
|
Device CRUD
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-sites"
|
href="#mutation-sites"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Site CRUD
|
Site CRUD
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -184,7 +184,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-alerts"
|
href="#mutation-alerts"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Alert Actions
|
Alert Actions
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -192,7 +192,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-agents"
|
href="#mutation-agents"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Agent Management
|
Agent Management
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-organization"
|
href="#mutation-organization"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Organization
|
Organization
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-members"
|
href="#mutation-members"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -216,7 +216,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-integrations"
|
href="#mutation-integrations"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Integrations
|
Integrations
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -224,7 +224,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-checks"
|
href="#mutation-checks"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Service Checks
|
Service Checks
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -232,7 +232,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-schedules"
|
href="#mutation-schedules"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Schedules
|
Schedules
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -240,7 +240,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-escalation-policies"
|
href="#mutation-escalation-policies"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Escalation Policies
|
Escalation Policies
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -248,7 +248,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#mutation-maintenance-windows"
|
href="#mutation-maintenance-windows"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Maintenance Windows
|
Maintenance Windows
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -263,7 +263,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#subscriptions"
|
href="#subscriptions"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Real-Time Events
|
Real-Time Events
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -278,7 +278,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#types"
|
href="#types"
|
||||||
class="nav-link block py-1 pl-4 pr-3 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="nav-link block py-1 pl-4 pr-3 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
All Types
|
All Types
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -290,7 +290,7 @@
|
||||||
<li class="mt-auto">
|
<li class="mt-auto">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/docs/api"}
|
navigate={~p"/docs/api"}
|
||||||
class="block py-1 text-sm text-gray-600 transition hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="block py-1 text-sm text-cool-steel-600 transition hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
← REST API Docs
|
← REST API Docs
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -307,25 +307,25 @@
|
||||||
<!-- Introduction -->
|
<!-- Introduction -->
|
||||||
<section id="introduction" class="mb-16">
|
<section id="introduction" class="mb-16">
|
||||||
<h1 class="text-3xl font-bold tracking-tight text-zinc-900 dark:text-white">GraphQL API</h1>
|
<h1 class="text-3xl font-bold tracking-tight text-zinc-900 dark:text-white">GraphQL API</h1>
|
||||||
<p class="mt-4 text-lg text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-lg text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
The TowerOps GraphQL API provides a flexible, strongly-typed interface for querying and managing your network infrastructure.
|
The TowerOps GraphQL API provides a flexible, strongly-typed interface for querying and managing your network infrastructure.
|
||||||
Request exactly the data you need in a single request — no over-fetching, no under-fetching.
|
Request exactly the data you need in a single request — no over-fetching, no under-fetching.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
The GraphQL API uses the same authentication as the
|
The GraphQL API uses the same authentication as the
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/docs/api"}
|
navigate={~p"/docs/api"}
|
||||||
class="text-blue-600 hover:text-blue-500 dark:text-blue-400"
|
class="text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
REST API
|
REST API
|
||||||
</.link>
|
</.link>
|
||||||
and provides equivalent functionality with the added benefit of nested queries and precise field selection.
|
and provides equivalent functionality with the added benefit of nested queries and precise field selection.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 rounded-lg border border-zinc-900/10 bg-gray-50 p-4 dark:border-white/10 dark:bg-gray-900/40">
|
<div class="mt-6 rounded-lg border border-zinc-900/10 bg-cool-steel-50 p-4 dark:border-white/10 dark:bg-cool-steel-900/40">
|
||||||
<h3 class="text-sm font-semibold text-zinc-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-white">
|
||||||
Official clients
|
Official clients
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Prefer infrastructure-as-code? The Towerops Ansible collection covers every resource exposed here: <a
|
Prefer infrastructure-as-code? The Towerops Ansible collection covers every resource exposed here: <a
|
||||||
href="https://codeberg.org/towerops/ansible"
|
href="https://codeberg.org/towerops/ansible"
|
||||||
class="font-medium text-emerald-600 hover:underline dark:text-emerald-400"
|
class="font-medium text-emerald-600 hover:underline dark:text-emerald-400"
|
||||||
|
|
@ -339,7 +339,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Authentication
|
Authentication
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Authenticate using a Bearer token in the
|
Authenticate using a Bearer token in the
|
||||||
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
||||||
Authorization
|
Authorization
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
<!-- Endpoint -->
|
<!-- Endpoint -->
|
||||||
<section id="endpoint" class="mb-16">
|
<section id="endpoint" class="mb-16">
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">Endpoint</h2>
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">Endpoint</h2>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
All GraphQL requests are sent as
|
All GraphQL requests are sent as
|
||||||
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
||||||
POST
|
POST
|
||||||
|
|
@ -365,7 +365,7 @@
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
<pre class="text-sm text-green-400"><code>POST https://app.towerops.net/api/graphql</code></pre>
|
<pre class="text-sm text-green-400"><code>POST https://app.towerops.net/api/graphql</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Send a JSON body with a
|
Send a JSON body with a
|
||||||
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
||||||
query
|
query
|
||||||
|
|
@ -388,7 +388,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">devices</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">devices</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
List all devices. Optionally filter by site or status.
|
List all devices. Optionally filter by site or status.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -404,7 +404,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">siteId</td>
|
<td class="py-2 pr-4 font-mono text-xs">siteId</td>
|
||||||
<td class="py-2 pr-4">ID</td>
|
<td class="py-2 pr-4">ID</td>
|
||||||
|
|
@ -444,7 +444,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">device</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">device</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Get a single device by ID.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">Get a single device by ID.</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
||||||
device(id: "device-uuid") {
|
device(id: "device-uuid") {
|
||||||
|
|
@ -469,7 +469,9 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">sites</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">sites</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">List all sites for the organization.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
List all sites for the organization.
|
||||||
|
</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
||||||
sites {
|
sites {
|
||||||
|
|
@ -485,7 +487,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">site</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">site</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Get a single site by ID.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">Get a single site by ID.</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{ site(id: "site-uuid") { id name location } }]) %></code></pre>
|
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{ site(id: "site-uuid") { id name location } }]) %></code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -498,7 +500,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">alerts</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">alerts</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
List alerts. Filter by status or device.
|
List alerts. Filter by status or device.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -514,7 +516,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">status</td>
|
<td class="py-2 pr-4 font-mono text-xs">status</td>
|
||||||
<td class="py-2 pr-4">String</td>
|
<td class="py-2 pr-4">String</td>
|
||||||
|
|
@ -669,7 +671,9 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">deviceMetrics</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">deviceMetrics</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Get time-series metrics for a device.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Get time-series metrics for a device.
|
||||||
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
<table class="min-w-full text-sm">
|
<table class="min-w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -683,7 +687,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -715,7 +719,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">deviceInterfaces</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">deviceInterfaces</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Get SNMP interfaces for a device.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Get SNMP interfaces for a device.
|
||||||
|
</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
<pre class="text-sm text-green-400 overflow-x-auto"><code><%= raw(~S[{
|
||||||
deviceInterfaces(deviceId: "device-uuid") {
|
deviceInterfaces(deviceId: "device-uuid") {
|
||||||
|
|
@ -738,7 +744,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Query: Time-Series
|
Query: Time-Series
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Query historical sensor readings, interface traffic rates, and monitoring check results.
|
Query historical sensor readings, interface traffic rates, and monitoring check results.
|
||||||
All time-series queries support a
|
All time-series queries support a
|
||||||
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
||||||
|
|
@ -748,7 +754,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">deviceSensors</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">deviceSensors</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
List all SNMP sensors discovered on a device.
|
List all SNMP sensors discovered on a device.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -764,7 +770,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -788,7 +794,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">sensorReadings</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">sensorReadings</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Get historical readings for a specific sensor.
|
Get historical readings for a specific sensor.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -804,7 +810,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">sensorId</td>
|
<td class="py-2 pr-4 font-mono text-xs">sensorId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -830,7 +836,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">interfaceTraffic</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">interfaceTraffic</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Get traffic rates (bits per second) computed from SNMP counter deltas for a network interface.
|
Get traffic rates (bits per second) computed from SNMP counter deltas for a network interface.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -846,7 +852,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">interfaceId</td>
|
<td class="py-2 pr-4 font-mono text-xs">interfaceId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -873,7 +879,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">checkResults</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">checkResults</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Get historical results for a monitoring check (ping, HTTP, etc.).
|
Get historical results for a monitoring check (ping, HTTP, etc.).
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -889,7 +895,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
<tr class="border-b border-zinc-100 dark:border-zinc-800">
|
||||||
<td class="py-2 pr-4 font-mono text-xs">checkId</td>
|
<td class="py-2 pr-4 font-mono text-xs">checkId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -921,7 +927,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Query: Service Checks
|
Query: Service Checks
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-2 text-base text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-base text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
List and inspect HTTP, TCP, DNS, and ping service checks.
|
List and inspect HTTP, TCP, DNS, and ping service checks.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -961,7 +967,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Query: On-Call Schedules
|
Query: On-Call Schedules
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-2 text-base text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-base text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Inspect schedules and find out who is currently on call.
|
Inspect schedules and find out who is currently on call.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -1022,7 +1028,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Query: Escalation Policies
|
Query: Escalation Policies
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-2 text-base text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-base text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Inspect escalation policies and their rules + targets.
|
Inspect escalation policies and their rules + targets.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -1068,7 +1074,7 @@
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Query: Maintenance Windows
|
Query: Maintenance Windows
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-2 text-base text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-base text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Inspect planned and active maintenance windows. The optional <code>filter</code>
|
Inspect planned and active maintenance windows. The optional <code>filter</code>
|
||||||
argument accepts <code>active</code>, <code>upcoming</code>, or <code>past</code>.
|
argument accepts <code>active</code>, <code>upcoming</code>, or <code>past</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -1163,7 +1169,9 @@
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
||||||
updateSite / deleteSite
|
updateSite / deleteSite
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Same pattern as device mutations.</p>
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Same pattern as device mutations.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Mutations: Alerts -->
|
<!-- Mutations: Alerts -->
|
||||||
|
|
@ -1201,7 +1209,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">createAgent</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">createAgent</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Creates a new agent token. The raw token is only returned once.
|
Creates a new agent token. The raw token is only returned once.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
|
|
@ -1541,7 +1549,7 @@ mutation {
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Subscriptions
|
Subscriptions
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Subscribe to real-time events via WebSocket. Connect to
|
Subscribe to real-time events via WebSocket. Connect to
|
||||||
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
<code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm font-mono text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200">
|
||||||
wss://app.towerops.net/socket/graphql
|
wss://app.towerops.net/socket/graphql
|
||||||
|
|
@ -1550,7 +1558,7 @@ mutation {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Connecting</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Connecting</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Pass your API token in the connection parameters when establishing the WebSocket:
|
Pass your API token in the connection parameters when establishing the WebSocket:
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
|
|
@ -1568,7 +1576,7 @@ const absintheSocket = AbsintheSocket.create(phoenixSocket);]) %></code></pre>
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
||||||
deviceStatusChanged
|
deviceStatusChanged
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Subscribe to device status changes. Optionally filter by a specific device ID, or omit to receive all device events in your organization.
|
Subscribe to device status changes. Optionally filter by a specific device ID, or omit to receive all device events in your organization.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -1584,7 +1592,7 @@ const absintheSocket = AbsintheSocket.create(phoenixSocket);]) %></code></pre>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
||||||
<td class="py-2 pr-4">ID</td>
|
<td class="py-2 pr-4">ID</td>
|
||||||
|
|
@ -1615,7 +1623,7 @@ subscription {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">alertEvent</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">alertEvent</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Subscribe to alert lifecycle events (created, acknowledged, resolved) for your organization.
|
Subscribe to alert lifecycle events (created, acknowledged, resolved) for your organization.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
<div class="mt-4 rounded-lg bg-zinc-900 p-4 dark:bg-zinc-800">
|
||||||
|
|
@ -1635,7 +1643,7 @@ subscription {
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">
|
||||||
sensorReadingsUpdated
|
sensorReadingsUpdated
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Subscribe to live sensor reading updates for a specific device. Delivers batched readings as they are polled.
|
Subscribe to live sensor reading updates for a specific device. Delivers batched readings as they are polled.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
|
|
@ -1651,7 +1659,7 @@ subscription {
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="text-gray-600 dark:text-gray-400">
|
<tbody class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
<td class="py-2 pr-4 font-mono text-xs">deviceId</td>
|
||||||
<td class="py-2 pr-4">ID!</td>
|
<td class="py-2 pr-4">ID!</td>
|
||||||
|
|
@ -1683,12 +1691,12 @@ subscription {
|
||||||
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
<h2 class="text-2xl font-bold tracking-tight text-zinc-900 dark:text-white">
|
||||||
Types Reference
|
Types Reference
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
All field names use camelCase in GraphQL queries (Absinthe automatically converts from Elixir's snake_case).
|
All field names use camelCase in GraphQL queries (Absinthe automatically converts from Elixir's snake_case).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">Device</h3>
|
<h3 class="mt-8 text-lg font-semibold text-zinc-900 dark:text-white">Device</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, name, ipAddress, description, displayOrder, status, lastCheckedAt, lastStatusChangeAt,
|
id, name, ipAddress, description, displayOrder, status, lastCheckedAt, lastStatusChangeAt,
|
||||||
monitoringEnabled, checkIntervalSeconds, snmpEnabled, snmpVersion, snmpCommunity, snmpCommunitySource,
|
monitoringEnabled, checkIntervalSeconds, snmpEnabled, snmpVersion, snmpCommunity, snmpCommunitySource,
|
||||||
snmpPort, snmpTransport, snmpTransportSource, lastDiscoveryAt, lastSnmpPollAt,
|
snmpPort, snmpTransport, snmpTransportSource, lastDiscoveryAt, lastSnmpPollAt,
|
||||||
|
|
@ -1698,7 +1706,7 @@ subscription {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Site</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Site</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, name, description, location, displayOrder, snmpVersion, snmpCommunity, snmpPort, snmpTransport,
|
id, name, description, location, displayOrder, snmpVersion, snmpCommunity, snmpPort, snmpTransport,
|
||||||
snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol,
|
snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol,
|
||||||
mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl, mikrotikEnabled,
|
mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl, mikrotikEnabled,
|
||||||
|
|
@ -1706,19 +1714,19 @@ subscription {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Alert</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Alert</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, alertType, message, triggeredAt, acknowledgedAt, resolvedAt, emailSentAt,
|
id, alertType, message, triggeredAt, acknowledgedAt, resolvedAt, emailSentAt,
|
||||||
deviceId, acknowledgedById, gaiiaImpact, insertedAt, device, acknowledgedByEmail
|
deviceId, acknowledgedById, gaiiaImpact, insertedAt, device, acknowledgedByEmail
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Agent</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Agent</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, name, enabled, isCloudPoller, allowRemoteDebug, lastSeenAt, lastIp, metadata,
|
id, name, enabled, isCloudPoller, allowRemoteDebug, lastSeenAt, lastIp, metadata,
|
||||||
organizationId, insertedAt, updatedAt, deviceCount
|
organizationId, insertedAt, updatedAt, deviceCount
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Organization</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Organization</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, name, slug, subscriptionPlan, useSites, snmpVersion, snmpCommunity, snmpPort, snmpTransport,
|
id, name, slug, subscriptionPlan, useSites, snmpVersion, snmpCommunity, snmpPort, snmpTransport,
|
||||||
snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol,
|
snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol,
|
||||||
mikrotikEnabled, mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl,
|
mikrotikEnabled, mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl,
|
||||||
|
|
@ -1726,61 +1734,61 @@ subscription {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Member</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Member</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, email, role, isDefault, userId, insertedAt
|
id, email, role, isDefault, userId, insertedAt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Integration</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Integration</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, provider, enabled, syncIntervalMinutes, lastSyncedAt, lastSyncStatus, organizationId, insertedAt, updatedAt
|
id, provider, enabled, syncIntervalMinutes, lastSyncedAt, lastSyncStatus, organizationId, insertedAt, updatedAt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">ActivityItem</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">ActivityItem</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
summary, detail, timestamp, severity, icon, deviceName, siteName, link, type
|
summary, detail, timestamp, severity, icon, deviceName, siteName, link, type
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Sensor</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Sensor</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
id, sensorType, sensorUnit, sensorDescr, currentValue, lastCheckedAt, monitored
|
id, sensorType, sensorUnit, sensorDescr, currentValue, lastCheckedAt, monitored
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">SensorReading</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">SensorReading</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
sensorId, value, status, checkedAt
|
sensorId, value, status, checkedAt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">
|
||||||
InterfaceTrafficPoint
|
InterfaceTrafficPoint
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
timestamp, inBps (Float), outBps (Float), inErrors (Int), outErrors (Int)
|
timestamp, inBps (Float), outBps (Float), inErrors (Int), outErrors (Int)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">CheckResultPoint</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">CheckResultPoint</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
checkId, value, status, checkedAt, responseTimeMs
|
checkId, value, status, checkedAt, responseTimeMs
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">DeviceStatusEvent</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">DeviceStatusEvent</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
deviceId, deviceName, status, changedAt
|
deviceId, deviceName, status, changedAt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">AlertEvent</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">AlertEvent</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
alertId, alertType, severity, message, deviceId, eventType, triggeredAt
|
alertId, alertType, severity, message, deviceId, eventType, triggeredAt
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">
|
||||||
SensorReadingsEvent
|
SensorReadingsEvent
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
deviceId, deviceName, readings (list of SensorReadingSnapshot: sensorId, sensorType, sensorDescr, sensorUnit, value, checkedAt)
|
deviceId, deviceName, readings (list of SensorReadingSnapshot: sensorId, sensorType, sensorDescr, sensorUnit, value, checkedAt)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Utility Types</h3>
|
<h3 class="mt-6 text-lg font-semibold text-zinc-900 dark:text-white">Utility Types</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<strong>DeleteResult:</strong>
|
<strong>DeleteResult:</strong>
|
||||||
success (Boolean!), message (String)<br />
|
success (Boolean!), message (String)<br />
|
||||||
<strong>TestResult:</strong>
|
<strong>TestResult:</strong>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<MarketingLayouts.marketing flash={@flash}>
|
<MarketingLayouts.marketing flash={@flash}>
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-20 pb-16 text-center lg:pt-32">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 pt-20 pb-16 text-center lg:pt-32">
|
||||||
<div class="inline-flex items-center rounded-full px-4 py-1.5 mb-8 bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300 text-sm font-medium ring-1 ring-blue-600/20">
|
<div class="inline-flex items-center rounded-full px-4 py-1.5 mb-8 bg-cerulean-50 dark:bg-cerulean-950 text-cerulean-700 dark:text-cerulean-300 text-sm font-medium ring-1 ring-cerulean-600/20">
|
||||||
<span class="mr-2">📡</span> Built specifically for WISP & ISP operators
|
<span class="mr-2">📡</span> Built specifically for WISP & ISP operators
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-slate-900 dark:text-white sm:text-7xl">
|
<h1 class="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-slate-900 dark:text-white sm:text-7xl">
|
||||||
See the
|
See the
|
||||||
<span class="relative whitespace-nowrap text-blue-600 dark:text-blue-400">
|
<span class="relative whitespace-nowrap text-cerulean-600 dark:text-cerulean-400">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
viewBox="0 0 418 42"
|
viewBox="0 0 418 42"
|
||||||
class="absolute top-2/3 left-0 h-[0.58em] w-full fill-blue-300/70 dark:fill-blue-500/40"
|
class="absolute top-2/3 left-0 h-[0.58em] w-full fill-cerulean-300/70 dark:fill-cerulean-500/40"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
>
|
>
|
||||||
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
|
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
|
||||||
|
|
@ -24,19 +24,19 @@
|
||||||
When a tower goes down, instantly see which customers are affected, how much MRR is at risk,
|
When a tower goes down, instantly see which customers are affected, how much MRR is at risk,
|
||||||
and what to fix first.
|
and what to fix first.
|
||||||
</p>
|
</p>
|
||||||
<p class="mx-auto mt-4 max-w-2xl text-xl font-semibold tracking-tight text-blue-600 dark:text-blue-400">
|
<p class="mx-auto mt-4 max-w-2xl text-xl font-semibold tracking-tight text-cerulean-600 dark:text-cerulean-400">
|
||||||
First 10 devices always free. No credit card required.
|
First 10 devices always free. No credit card required.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-10 flex justify-center gap-x-6">
|
<div class="mt-10 flex justify-center gap-x-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/register"}
|
navigate={~p"/users/register"}
|
||||||
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600"
|
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 bg-cerulean-600 text-white hover:text-slate-100 hover:bg-cerulean-500 active:bg-cerulean-800 active:text-cerulean-100 focus-visible:outline-cerulean-600"
|
||||||
>
|
>
|
||||||
Start monitoring free →
|
Start monitoring free →
|
||||||
</.link>
|
</.link>
|
||||||
<a
|
<a
|
||||||
href="#features"
|
href="#features"
|
||||||
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold ring-1 ring-slate-200 dark:ring-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:ring-slate-300 dark:hover:ring-slate-600 active:bg-slate-100 dark:active:bg-slate-800 focus-visible:outline-blue-600"
|
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold ring-1 ring-slate-200 dark:ring-slate-700 text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:ring-slate-300 dark:hover:ring-slate-600 active:bg-slate-100 dark:active:bg-slate-800 focus-visible:outline-cerulean-600"
|
||||||
>
|
>
|
||||||
See how it works
|
See how it works
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -58,24 +58,24 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-16 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="mt-16 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
||||||
<div class="text-3xl font-bold text-red-400">45 min</div>
|
<div class="text-3xl font-bold text-sweet-salmon-400">45 min</div>
|
||||||
<div class="mt-2 text-sm text-slate-400">Average time to assess outage impact</div>
|
<div class="mt-2 text-sm text-slate-400">Average time to assess outage impact</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
||||||
<div class="text-3xl font-bold text-red-400">5+</div>
|
<div class="text-3xl font-bold text-sweet-salmon-400">5+</div>
|
||||||
<div class="mt-2 text-sm text-slate-400">Dashboards to check during incidents</div>
|
<div class="mt-2 text-sm text-slate-400">Dashboards to check during incidents</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
||||||
<div class="text-3xl font-bold text-red-400">???</div>
|
<div class="text-3xl font-bold text-sweet-salmon-400">???</div>
|
||||||
<div class="mt-2 text-sm text-slate-400">Subscribers affected right now</div>
|
<div class="mt-2 text-sm text-slate-400">Subscribers affected right now</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
<div class="rounded-lg bg-slate-800 p-6 text-center">
|
||||||
<div class="text-3xl font-bold text-red-400">$???</div>
|
<div class="text-3xl font-bold text-sweet-salmon-400">$???</div>
|
||||||
<div class="mt-2 text-sm text-slate-400">Revenue at risk from this outage</div>
|
<div class="mt-2 text-sm text-slate-400">Revenue at risk from this outage</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-12 text-center">
|
<div class="mt-12 text-center">
|
||||||
<p class="text-lg font-medium text-blue-400">
|
<p class="text-lg font-medium text-cerulean-400">
|
||||||
TowerOps gives you one place to see it all.
|
TowerOps gives you one place to see it all.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,14 +86,14 @@
|
||||||
<section
|
<section
|
||||||
id="features"
|
id="features"
|
||||||
aria-label="Features for WISP operators"
|
aria-label="Features for WISP operators"
|
||||||
class="relative overflow-hidden bg-blue-600 pt-20 pb-28 sm:py-32"
|
class="relative overflow-hidden bg-cerulean-600 pt-20 pb-28 sm:py-32"
|
||||||
>
|
>
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
|
||||||
<div class="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
|
<div class="max-w-2xl md:mx-auto md:text-center xl:max-w-none">
|
||||||
<h2 class="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
|
<h2 class="font-display text-3xl tracking-tight text-white sm:text-4xl md:text-5xl">
|
||||||
Operational intelligence built for WISPs.
|
Operational intelligence built for WISPs.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-6 text-lg tracking-tight text-blue-100">
|
<p class="mt-6 text-lg tracking-tight text-cerulean-100">
|
||||||
Not another generic monitoring tool. TowerOps understands tower sites, subscribers, and revenue.
|
Not another generic monitoring tool. TowerOps understands tower sites, subscribers, and revenue.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-white">Impact Dashboard</h3>
|
<h3 class="text-lg font-semibold text-white">Impact Dashboard</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-blue-100">
|
<p class="mt-4 text-sm text-cerulean-100">
|
||||||
See MRR at risk, affected subscriber counts, and active incidents at a glance.
|
See MRR at risk, affected subscriber counts, and active incidents at a glance.
|
||||||
Know the business impact of every network event before your customers call.
|
Know the business impact of every network event before your customers call.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -119,7 +119,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-white">Subscriber Trace</h3>
|
<h3 class="text-lg font-semibold text-white">Subscriber Trace</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-blue-100">
|
<p class="mt-4 text-sm text-cerulean-100">
|
||||||
Help desk calls in? Search by name or account and instantly see their device status,
|
Help desk calls in? Search by name or account and instantly see their device status,
|
||||||
QoE scores, alerts, and whether nearby subscribers are also affected.
|
QoE scores, alerts, and whether nearby subscribers are also affected.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-white">Smart Alert Triage</h3>
|
<h3 class="text-lg font-semibold text-white">Smart Alert Triage</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-blue-100">
|
<p class="mt-4 text-sm text-cerulean-100">
|
||||||
Alerts grouped by tower site and ranked by severity and subscriber impact.
|
Alerts grouped by tower site and ranked by severity and subscriber impact.
|
||||||
Filter by critical/unresolved/all. Resolve directly from the alert view.
|
Filter by critical/unresolved/all. Resolve directly from the alert view.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-white">Tower Site Intelligence</h3>
|
<h3 class="text-lg font-semibold text-white">Tower Site Intelligence</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-blue-100">
|
<p class="mt-4 text-sm text-cerulean-100">
|
||||||
Enriched site pages with subscriber counts, device health grids, active alerts,
|
Enriched site pages with subscriber counts, device health grids, active alerts,
|
||||||
latency charts, and active alerts — all on one screen.
|
latency charts, and active alerts — all on one screen.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -159,7 +159,7 @@
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-white">Billing & QoE Integrations</h3>
|
<h3 class="text-lg font-semibold text-white">Billing & QoE Integrations</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-blue-100">
|
<p class="mt-4 text-sm text-cerulean-100">
|
||||||
Connect Gaiia, Sonar, Splynx, or VISP for subscriber data, Preseem for QoE metrics,
|
Connect Gaiia, Sonar, Splynx, or VISP for subscriber data, Preseem for QoE metrics,
|
||||||
NetBox for DCIM, and PagerDuty for alerting. TowerOps pulls it all together.
|
NetBox for DCIM, and PagerDuty for alerting. TowerOps pulls it all together.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -187,7 +187,7 @@
|
||||||
<!-- Step 1 -->
|
<!-- Step 1 -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600 text-white font-semibold text-lg">
|
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-cerulean-600 text-white font-semibold text-lg">
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
||||||
|
|
@ -205,7 +205,7 @@
|
||||||
<!-- Step 2 -->
|
<!-- Step 2 -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600 text-white font-semibold text-lg">
|
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-cerulean-600 text-white font-semibold text-lg">
|
||||||
2
|
2
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
||||||
|
|
@ -217,25 +217,25 @@
|
||||||
TowerOps automatically maps subscribers to devices and tower sites.
|
TowerOps automatically maps subscribers to devices and tower sites.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 flex flex-wrap gap-2">
|
<div class="mt-6 flex flex-wrap gap-2">
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
Gaiia
|
Gaiia
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
Sonar
|
Sonar
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
Preseem
|
Preseem
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
Splynx
|
Splynx
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
VISP
|
VISP
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
NetBox
|
NetBox
|
||||||
</span>
|
</span>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-950 px-3 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 ring-1 ring-blue-600/20">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 dark:bg-cerulean-950 px-3 py-1 text-xs font-medium text-cerulean-700 dark:text-cerulean-300 ring-1 ring-cerulean-600/20">
|
||||||
PagerDuty
|
PagerDuty
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -243,7 +243,7 @@
|
||||||
<!-- Step 3 -->
|
<!-- Step 3 -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600 text-white font-semibold text-lg">
|
<div class="flex h-10 w-10 items-center justify-center rounded-full bg-cerulean-600 text-white font-semibold text-lg">
|
||||||
3
|
3
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">
|
||||||
|
|
@ -254,9 +254,12 @@
|
||||||
Your dashboard lights up with subscriber impact, revenue at risk, device health,
|
Your dashboard lights up with subscriber impact, revenue at risk, device health,
|
||||||
and site status. Help desk gets subscriber trace. NOC gets alert triage. Everyone wins.
|
and site status. Help desk gets subscriber trace. NOC gets alert triage. Everyone wins.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 rounded-lg border-2 border-blue-200 dark:border-blue-800 bg-white dark:bg-white/5 p-4">
|
<div class="mt-6 rounded-lg border-2 border-cerulean-200 dark:border-cerulean-800 bg-white dark:bg-white/5 p-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-chart-bar" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<.icon
|
||||||
|
name="hero-chart-bar"
|
||||||
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
<span class="text-sm font-medium text-slate-900 dark:text-white">
|
<span class="text-sm font-medium text-slate-900 dark:text-white">
|
||||||
Impact Dashboard
|
Impact Dashboard
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -325,7 +328,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-16 mx-auto max-w-xl">
|
<div class="mt-16 mx-auto max-w-xl">
|
||||||
<div class="rounded-2xl bg-white dark:bg-slate-800 p-8 shadow-lg dark:shadow-black/30 ring-2 ring-blue-600 text-center">
|
<div class="rounded-2xl bg-white dark:bg-slate-800 p-8 shadow-lg dark:shadow-black/30 ring-2 ring-cerulean-600 text-center">
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<span class="text-5xl font-bold text-slate-900 dark:text-white">$2</span>
|
<span class="text-5xl font-bold text-slate-900 dark:text-white">$2</span>
|
||||||
<span class="text-lg text-slate-500 dark:text-slate-400">/device/month</span>
|
<span class="text-lg text-slate-500 dark:text-slate-400">/device/month</span>
|
||||||
|
|
@ -340,63 +343,63 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Unlimited devices
|
/> Unlimited devices
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Unlimited agents & networks
|
/> Unlimited agents & networks
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> SNMP monitoring & alerts
|
/> SNMP monitoring & alerts
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Impact dashboard
|
/> Impact dashboard
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> All billing integrations
|
/> All billing integrations
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Preseem & PagerDuty
|
/> Preseem & PagerDuty
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Subscriber trace & alert triage
|
/> Subscriber trace & alert triage
|
||||||
</div>
|
</div>
|
||||||
<%!-- Config change tracking hidden — feature temporarily disabled --%>
|
<%!-- Config change tracking hidden — feature temporarily disabled --%>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> Team access & email alerts
|
/> Team access & email alerts
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check"
|
name="hero-check"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/> API access
|
/> API access
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/register"}
|
navigate={~p"/users/register"}
|
||||||
class="mt-10 inline-block rounded-full px-8 py-3 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500"
|
class="mt-10 inline-block rounded-full px-8 py-3 text-sm font-semibold text-white bg-cerulean-600 hover:bg-cerulean-500"
|
||||||
>
|
>
|
||||||
Get started free
|
Get started free
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -405,7 +408,7 @@
|
||||||
<p class="mt-8 text-center text-sm text-slate-500 dark:text-slate-400">
|
<p class="mt-8 text-center text-sm text-slate-500 dark:text-slate-400">
|
||||||
No credit card required. Need a custom plan for 500+ devices? <a
|
No credit card required. Need a custom plan for 500+ devices? <a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300 underline"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:text-cerulean-500 dark:hover:text-cerulean-300 underline"
|
||||||
>Talk to us</a>.
|
>Talk to us</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -452,19 +455,19 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Final CTA -->
|
<!-- Final CTA -->
|
||||||
<section aria-label="Get started" class="relative overflow-hidden bg-blue-600 py-32">
|
<section aria-label="Get started" class="relative overflow-hidden bg-cerulean-600 py-32">
|
||||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative">
|
||||||
<div class="mx-auto max-w-lg text-center">
|
<div class="mx-auto max-w-lg text-center">
|
||||||
<h2 class="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
<h2 class="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
||||||
Stop guessing. Start seeing impact.
|
Stop guessing. Start seeing impact.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-4 text-lg tracking-tight text-blue-100">
|
<p class="mt-4 text-lg tracking-tight text-cerulean-100">
|
||||||
First 10 devices always free. Deploy an agent, connect your billing system,
|
First 10 devices always free. Deploy an agent, connect your billing system,
|
||||||
and see the difference in your first outage.
|
and see the difference in your first outage.
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/register"}
|
navigate={~p"/users/register"}
|
||||||
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 mt-10 bg-white text-slate-900 hover:bg-blue-50 active:bg-blue-200 active:text-slate-600 focus-visible:outline-white"
|
class="group inline-flex items-center justify-center rounded-full py-3 px-6 text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 mt-10 bg-white text-slate-900 hover:bg-cerulean-50 active:bg-cerulean-200 active:text-slate-600 focus-visible:outline-white"
|
||||||
>
|
>
|
||||||
Start monitoring free →
|
Start monitoring free →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
<% content = fn -> %>
|
<% content = fn -> %>
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Privacy Policy</h1>
|
<h1 class="text-3xl font-bold text-cool-steel-900 dark:text-white">Privacy Policy</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Version 1.0 | Effective Date: January 29, 2026
|
Version 1.0 | Effective Date: January 29, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prose prose-sm dark:prose-invert max-w-none">
|
<div class="prose prose-sm dark:prose-invert max-w-none">
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
Our Commitment to Privacy
|
Our Commitment to Privacy
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
At Towerops, we value your privacy. This policy outlines how we collect, use, and protect your data when you use our network monitoring platform.
|
At Towerops, we value your privacy. This policy outlines how we collect, use, and protect your data when you use our network monitoring platform.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
Data Collection and Use
|
Data Collection and Use
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We collect and store information necessary to provide our network monitoring services, including:
|
We collect and store information necessary to provide our network monitoring services, including:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Account information (email address, organization details)</li>
|
<li>Account information (email address, organization details)</li>
|
||||||
<li>Network device data (IP addresses, SNMP data, device configurations)</li>
|
<li>Network device data (IP addresses, SNMP data, device configurations)</li>
|
||||||
<li>Monitoring metrics and alerts</li>
|
<li>Monitoring metrics and alerts</li>
|
||||||
<li>Usage information to improve our services</li>
|
<li>Usage information to improve our services</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<strong class="font-semibold">
|
<strong class="font-semibold">
|
||||||
Your data is used exclusively for providing our monitoring services to you.
|
Your data is used exclusively for providing our monitoring services to you.
|
||||||
</strong>
|
</strong>
|
||||||
|
|
@ -39,11 +39,11 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Data Protection</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">Data Protection</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We implement industry-standard security measures to protect your data:
|
We implement industry-standard security measures to protect your data:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Encrypted connections (HTTPS/TLS) for all data transmission</li>
|
<li>Encrypted connections (HTTPS/TLS) for all data transmission</li>
|
||||||
<li>Secure database storage with encryption at rest</li>
|
<li>Secure database storage with encryption at rest</li>
|
||||||
<li>Access controls and authentication mechanisms</li>
|
<li>Access controls and authentication mechanisms</li>
|
||||||
|
|
@ -52,33 +52,35 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Data Retention</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">Data Retention</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We retain your data for as long as your account is active and as necessary to provide our services. Time-series monitoring data is retained according to your subscription plan. When you delete your account, we will delete your data within 30 days, except where we are required to retain it for legal or compliance purposes.
|
We retain your data for as long as your account is active and as necessary to provide our services. Time-series monitoring data is retained according to your subscription plan. When you delete your account, we will delete your data within 30 days, except where we are required to retain it for legal or compliance purposes.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Third-Party Services</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
Third-Party Services
|
||||||
|
</h2>
|
||||||
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We use limited third-party services to operate our platform:
|
We use limited third-party services to operate our platform:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Cloud infrastructure providers for hosting</li>
|
<li>Cloud infrastructure providers for hosting</li>
|
||||||
<li>Email delivery services for notifications</li>
|
<li>Email delivery services for notifications</li>
|
||||||
<li>Payment processors for subscription billing</li>
|
<li>Payment processors for subscription billing</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
These services are bound by their own privacy policies and are used solely to deliver our services to you.
|
These services are bound by their own privacy policies and are used solely to deliver our services to you.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Your Rights</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">Your Rights</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You have the right to:
|
You have the right to:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Access your data at any time through your account</li>
|
<li>Access your data at any time through your account</li>
|
||||||
<li>Export your data in standard formats</li>
|
<li>Export your data in standard formats</li>
|
||||||
<li>Request deletion of your account and associated data</li>
|
<li>Request deletion of your account and associated data</li>
|
||||||
|
|
@ -88,29 +90,31 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Cookie Policy</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">Cookie Policy</h2>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
What Are Cookies?
|
What Are Cookies?
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Cookies are small text files that are placed on your device when you visit our website. They help us provide you with a better experience by remembering your preferences and keeping you logged in.
|
Cookies are small text files that are placed on your device when you visit our website. They help us provide you with a better experience by remembering your preferences and keeping you logged in.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
What Cookies Do We Use?
|
What Cookies Do We Use?
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Towerops uses only <strong class="font-semibold">essential cookies</strong>
|
Towerops uses only <strong class="font-semibold">essential cookies</strong>
|
||||||
that are strictly necessary for the operation of our platform. We do not use third-party tracking, advertising, or analytics cookies.
|
that are strictly necessary for the operation of our platform. We do not use third-party tracking, advertising, or analytics cookies.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<h4 class="text-base font-semibold text-gray-900 dark:text-white">Essential Cookies</h4>
|
<h4 class="text-base font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-gray-700 dark:text-gray-300 mt-2">
|
Essential Cookies
|
||||||
|
</h4>
|
||||||
|
<p class="text-cool-steel-700 dark:text-cool-steel-300 mt-2">
|
||||||
These cookies are required for our website to function properly and cannot be disabled:
|
These cookies are required for our website to function properly and cannot be disabled:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-3 text-gray-700 dark:text-gray-300 mt-3">
|
<ul class="list-disc pl-6 space-y-3 text-cool-steel-700 dark:text-cool-steel-300 mt-3">
|
||||||
<li>
|
<li>
|
||||||
<strong class="font-semibold">Session Cookie (_towerops_web_key)</strong>
|
<strong class="font-semibold">Session Cookie (_towerops_web_key)</strong>
|
||||||
<br />
|
<br />
|
||||||
|
|
@ -152,18 +156,20 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
Third-Party Cookies
|
Third-Party Cookies
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We do not use any third-party cookies, tracking pixels, or analytics services that would place cookies on your device.
|
We do not use any third-party cookies, tracking pixels, or analytics services that would place cookies on your device.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">Managing Cookies</h3>
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
Managing Cookies
|
||||||
|
</h3>
|
||||||
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Since we only use essential cookies required for the website to function, disabling them will prevent you from using Towerops. However, you can manage cookies through your browser settings:
|
Since we only use essential cookies required for the website to function, disabling them will prevent you from using Towerops. However, you can manage cookies through your browser settings:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300 mt-3">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300 mt-3">
|
||||||
<li>
|
<li>
|
||||||
<strong class="font-semibold">Chrome:</strong>
|
<strong class="font-semibold">Chrome:</strong>
|
||||||
Settings → Privacy and security → Cookies and other site data
|
Settings → Privacy and security → Cookies and other site data
|
||||||
|
|
@ -182,36 +188,36 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
Cookie Consent (EU/EEA Users)
|
Cookie Consent (EU/EEA Users)
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If you are accessing Towerops from the European Union or European Economic Area, you will see a cookie consent banner on your first visit. Since we only use essential cookies necessary for the website to function, this banner is informational. Under GDPR, essential cookies do not require explicit consent as they are strictly necessary for the service you have requested.
|
If you are accessing Towerops from the European Union or European Economic Area, you will see a cookie consent banner on your first visit. Since we only use essential cookies necessary for the website to function, this banner is informational. Under GDPR, essential cookies do not require explicit consent as they are strictly necessary for the service you have requested.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mt-6">
|
||||||
Changes to Our Cookie Policy
|
Changes to Our Cookie Policy
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If we introduce new types of cookies in the future (such as analytics or marketing cookies), we will update this policy and, where required, request your consent before using them.
|
If we introduce new types of cookies in the future (such as analytics or marketing cookies), we will update this policy and, where required, request your consent before using them.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
Changes to This Policy
|
Changes to This Policy
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We may update this privacy policy from time to time. We will notify you of any significant changes by email or through our platform.
|
We may update this privacy policy from time to time. We will notify you of any significant changes by email or through our platform.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
|
<section class="space-y-4 mt-8 pt-8 border-t border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Contact Us</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">Contact Us</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If you have questions about this privacy policy or how we handle your data, please contact us at <a
|
If you have questions about this privacy policy or how we handle your data, please contact us at <a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>hi@towerops.net</a>.
|
>hi@towerops.net</a>.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,71 @@
|
||||||
<% content = fn -> %>
|
<% content = fn -> %>
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Terms of Service</h1>
|
<h1 class="text-3xl font-bold text-cool-steel-900 dark:text-white">Terms of Service</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Version 1.0 | Effective Date: January 29, 2026
|
Version 1.0 | Effective Date: January 29, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prose prose-sm dark:prose-invert max-w-none">
|
<div class="prose prose-sm dark:prose-invert max-w-none">
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
1. Acceptance of Terms
|
1. Acceptance of Terms
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
By accessing and using Towerops ("Service"), you accept and agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, please do not use the Service.
|
By accessing and using Towerops ("Service"), you accept and agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, please do not use the Service.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
These Terms constitute a legally binding agreement between you ("User", "you", or "your") and Towerops ("we", "us", or "our").
|
These Terms constitute a legally binding agreement between you ("User", "you", or "your") and Towerops ("we", "us", or "our").
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
2. Service Description
|
2. Service Description
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Towerops is a network monitoring and alerting platform that enables you to:
|
Towerops is a network monitoring and alerting platform that enables you to:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Monitor network devices via SNMP protocol</li>
|
<li>Monitor network devices via SNMP protocol</li>
|
||||||
<li>Collect and visualize time-series metrics</li>
|
<li>Collect and visualize time-series metrics</li>
|
||||||
<li>Receive alerts for device failures and performance issues</li>
|
<li>Receive alerts for device failures and performance issues</li>
|
||||||
<li>Manage network topology and device relationships</li>
|
<li>Manage network topology and device relationships</li>
|
||||||
<li>Deploy remote monitoring agents on your network</li>
|
<li>Deploy remote monitoring agents on your network</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We reserve the right to modify, suspend, or discontinue any part of the Service at any time with reasonable notice to users.
|
We reserve the right to modify, suspend, or discontinue any part of the Service at any time with reasonable notice to users.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
3. User Accounts and Registration
|
3. User Accounts and Registration
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
To use the Service, you must:
|
To use the Service, you must:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Register for an account with accurate and complete information</li>
|
<li>Register for an account with accurate and complete information</li>
|
||||||
<li>Be at least 18 years old or have parental/guardian consent</li>
|
<li>Be at least 18 years old or have parental/guardian consent</li>
|
||||||
<li>Maintain the security and confidentiality of your account credentials</li>
|
<li>Maintain the security and confidentiality of your account credentials</li>
|
||||||
<li>Notify us immediately of any unauthorized access to your account</li>
|
<li>Notify us immediately of any unauthorized access to your account</li>
|
||||||
<li>Be responsible for all activities that occur under your account</li>
|
<li>Be responsible for all activities that occur under your account</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You may not share your account with others, and you may not use another user's account without permission.
|
You may not share your account with others, and you may not use another user's account without permission.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
4. Acceptable Use Policy
|
4. Acceptable Use Policy
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You agree to use the Service only for lawful purposes and in accordance with these Terms. You agree NOT to:
|
You agree to use the Service only for lawful purposes and in accordance with these Terms. You agree NOT to:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>
|
<li>
|
||||||
Use the Service to monitor devices or networks you do not own or have explicit permission to monitor
|
Use the Service to monitor devices or networks you do not own or have explicit permission to monitor
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -86,25 +86,25 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
5. User Data and Privacy
|
5. User Data and Privacy
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Our collection and use of your personal data is governed by our <.link
|
Our collection and use of your personal data is governed by our <.link
|
||||||
navigate={~p"/privacy"}
|
navigate={~p"/privacy"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</.link>, which is incorporated into these Terms by reference.
|
</.link>, which is incorporated into these Terms by reference.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You retain all rights to the data you collect through the Service. We do not claim ownership of your monitoring data, device configurations, or network information.
|
You retain all rights to the data you collect through the Service. We do not claim ownership of your monitoring data, device configurations, or network information.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<strong class="font-semibold">GDPR Rights:</strong>
|
<strong class="font-semibold">GDPR Rights:</strong>
|
||||||
If you are located in the European Union or European Economic Area, you have specific rights under the General Data Protection Regulation (GDPR), including:
|
If you are located in the European Union or European Economic Area, you have specific rights under the General Data Protection Regulation (GDPR), including:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Right to access your personal data</li>
|
<li>Right to access your personal data</li>
|
||||||
<li>Right to rectification of inaccurate data</li>
|
<li>Right to rectification of inaccurate data</li>
|
||||||
<li>Right to erasure ("right to be forgotten")</li>
|
<li>Right to erasure ("right to be forgotten")</li>
|
||||||
|
|
@ -113,10 +113,10 @@
|
||||||
<li>Right to object to processing</li>
|
<li>Right to object to processing</li>
|
||||||
<li>Right to withdraw consent</li>
|
<li>Right to withdraw consent</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
To exercise these rights, please contact us at <a
|
To exercise these rights, please contact us at <a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>.
|
</a>.
|
||||||
|
|
@ -124,25 +124,25 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
6. Service Availability and Support
|
6. Service Availability and Support
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We strive to provide reliable and uninterrupted service, but we do not guarantee that the Service will be:
|
We strive to provide reliable and uninterrupted service, but we do not guarantee that the Service will be:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Available at all times without interruption</li>
|
<li>Available at all times without interruption</li>
|
||||||
<li>Error-free or free from bugs</li>
|
<li>Error-free or free from bugs</li>
|
||||||
<li>Secure from unauthorized access or cyberattacks</li>
|
<li>Secure from unauthorized access or cyberattacks</li>
|
||||||
<li>Compatible with all hardware, software, or network configurations</li>
|
<li>Compatible with all hardware, software, or network configurations</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We may perform scheduled maintenance that temporarily interrupts service. We will provide advance notice of scheduled downtime when possible.
|
We may perform scheduled maintenance that temporarily interrupts service. We will provide advance notice of scheduled downtime when possible.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Support is provided via email at <a
|
Support is provided via email at <a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>. Response times vary based on your subscription plan.
|
</a>. Response times vary based on your subscription plan.
|
||||||
|
|
@ -150,37 +150,37 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
7. Subscription and Payment
|
7. Subscription and Payment
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
The Service is provided on a subscription basis. By subscribing, you agree to:
|
The Service is provided on a subscription basis. By subscribing, you agree to:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Pay all fees associated with your selected subscription plan</li>
|
<li>Pay all fees associated with your selected subscription plan</li>
|
||||||
<li>Provide accurate and current billing information</li>
|
<li>Provide accurate and current billing information</li>
|
||||||
<li>Authorize recurring charges to your payment method</li>
|
<li>Authorize recurring charges to your payment method</li>
|
||||||
<li>Notify us of any changes to your payment information</li>
|
<li>Notify us of any changes to your payment information</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Subscriptions automatically renew unless canceled before the renewal date. You may cancel your subscription at any time through your account settings. No refunds are provided for partial subscription periods.
|
Subscriptions automatically renew unless canceled before the renewal date. You may cancel your subscription at any time through your account settings. No refunds are provided for partial subscription periods.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We reserve the right to change our pricing with 30 days' notice. Price changes will not affect your current subscription period.
|
We reserve the right to change our pricing with 30 days' notice. Price changes will not affect your current subscription period.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
8. Intellectual Property
|
8. Intellectual Property
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
The Service, including all software, designs, text, graphics, logos, and other content, is owned by Towerops and protected by copyright, trademark, and other intellectual property laws.
|
The Service, including all software, designs, text, graphics, logos, and other content, is owned by Towerops and protected by copyright, trademark, and other intellectual property laws.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We grant you a limited, non-exclusive, non-transferable license to access and use the Service for your internal business purposes. You may not:
|
We grant you a limited, non-exclusive, non-transferable license to access and use the Service for your internal business purposes. You may not:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Copy, modify, or create derivative works of the Service</li>
|
<li>Copy, modify, or create derivative works of the Service</li>
|
||||||
<li>Sell, resell, rent, or lease access to the Service</li>
|
<li>Sell, resell, rent, or lease access to the Service</li>
|
||||||
<li>Remove or alter any copyright, trademark, or proprietary notices</li>
|
<li>Remove or alter any copyright, trademark, or proprietary notices</li>
|
||||||
|
|
@ -189,15 +189,15 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
9. Limitation of Liability
|
9. Limitation of Liability
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<strong class="font-semibold uppercase">
|
<strong class="font-semibold uppercase">
|
||||||
To the maximum extent permitted by law:
|
To the maximum extent permitted by law:
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>
|
<li>
|
||||||
The Service is provided "as is" without warranties of any kind, either express or implied
|
The Service is provided "as is" without warranties of any kind, either express or implied
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -217,19 +217,19 @@
|
||||||
We are not liable for damages resulting from unauthorized access to your account
|
We are not liable for damages resulting from unauthorized access to your account
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Some jurisdictions do not allow the exclusion of certain warranties or limitation of liability, so these limitations may not apply to you.
|
Some jurisdictions do not allow the exclusion of certain warranties or limitation of liability, so these limitations may not apply to you.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
10. Indemnification
|
10. Indemnification
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You agree to indemnify, defend, and hold harmless Towerops and its officers, directors, employees, and agents from any claims, liabilities, damages, losses, and expenses (including legal fees) arising from:
|
You agree to indemnify, defend, and hold harmless Towerops and its officers, directors, employees, and agents from any claims, liabilities, damages, losses, and expenses (including legal fees) arising from:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Your use or misuse of the Service</li>
|
<li>Your use or misuse of the Service</li>
|
||||||
<li>Your violation of these Terms</li>
|
<li>Your violation of these Terms</li>
|
||||||
<li>Your violation of any rights of another person or entity</li>
|
<li>Your violation of any rights of another person or entity</li>
|
||||||
|
|
@ -238,127 +238,129 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
11. Termination
|
11. Termination
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
You may terminate your account at any time by:
|
You may terminate your account at any time by:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Canceling your subscription through account settings</li>
|
<li>Canceling your subscription through account settings</li>
|
||||||
<li>
|
<li>
|
||||||
Contacting us at
|
Contacting us at
|
||||||
<a
|
<a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We may suspend or terminate your account immediately if:
|
We may suspend or terminate your account immediately if:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>You violate these Terms</li>
|
<li>You violate these Terms</li>
|
||||||
<li>Your payment fails or your account is past due</li>
|
<li>Your payment fails or your account is past due</li>
|
||||||
<li>You engage in fraudulent or illegal activities</li>
|
<li>You engage in fraudulent or illegal activities</li>
|
||||||
<li>We are required to do so by law</li>
|
<li>We are required to do so by law</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Upon termination, your right to use the Service ceases immediately. We will delete your data within 30 days of account termination, except where we are required to retain it for legal purposes.
|
Upon termination, your right to use the Service ceases immediately. We will delete your data within 30 days of account termination, except where we are required to retain it for legal purposes.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
12. Data Export and Deletion
|
12. Data Export and Deletion
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Before terminating your account, you should export any data you wish to retain. We provide data export functionality through your account settings.
|
Before terminating your account, you should export any data you wish to retain. We provide data export functionality through your account settings.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<strong class="font-semibold">GDPR Right to Erasure:</strong>
|
<strong class="font-semibold">GDPR Right to Erasure:</strong>
|
||||||
You may request deletion of your account and all associated data at any time. We will process deletion requests within 30 days, except for data we are legally required to retain.
|
You may request deletion of your account and all associated data at any time. We will process deletion requests within 30 days, except for data we are legally required to retain.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
13. Changes to Terms
|
13. Changes to Terms
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
We may modify these Terms at any time. If we make material changes, we will notify you by:
|
We may modify these Terms at any time. If we make material changes, we will notify you by:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc pl-6 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-disc pl-6 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>Email to your registered email address</li>
|
<li>Email to your registered email address</li>
|
||||||
<li>Notice on the Service homepage</li>
|
<li>Notice on the Service homepage</li>
|
||||||
<li>In-app notification</li>
|
<li>In-app notification</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Your continued use of the Service after changes take effect constitutes acceptance of the modified Terms. If you do not agree to the changes, you must stop using the Service and terminate your account.
|
Your continued use of the Service after changes take effect constitutes acceptance of the modified Terms. If you do not agree to the changes, you must stop using the Service and terminate your account.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
14. Dispute Resolution
|
14. Dispute Resolution
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
In the event of any dispute arising from these Terms or your use of the Service, you agree to first contact us to seek an informal resolution at <a
|
In the event of any dispute arising from these Terms or your use of the Service, you agree to first contact us to seek an informal resolution at <a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline font-medium"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline font-medium"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>.
|
</a>.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If we cannot resolve the dispute informally within 30 days, either party may pursue formal dispute resolution through binding arbitration or in the courts, subject to the governing law provisions below.
|
If we cannot resolve the dispute informally within 30 days, either party may pursue formal dispute resolution through binding arbitration or in the courts, subject to the governing law provisions below.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
15. Governing Law and Jurisdiction
|
15. Governing Law and Jurisdiction
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
These Terms are governed by and construed in accordance with the laws of the State of Texas, United States, without regard to its conflict of law provisions.
|
These Terms are governed by and construed in accordance with the laws of the State of Texas, United States, without regard to its conflict of law provisions.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<strong class="font-semibold">For EU/EEA Users:</strong>
|
<strong class="font-semibold">For EU/EEA Users:</strong>
|
||||||
Nothing in these Terms affects your statutory rights as a consumer under EU or local law. If you are located in the EU/EEA, you may also have the right to bring legal proceedings in the courts of your country of residence.
|
Nothing in these Terms affects your statutory rights as a consumer under EU or local law. If you are located in the EU/EEA, you may also have the right to bring legal proceedings in the courts of your country of residence.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
16. Severability
|
16. Severability
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If any provision of these Terms is found to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that these Terms shall otherwise remain in full force and effect.
|
If any provision of these Terms is found to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that these Terms shall otherwise remain in full force and effect.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
17. Entire Agreement
|
17. Entire Agreement
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
These Terms, together with our Privacy Policy, constitute the entire agreement between you and Towerops regarding the Service and supersede all prior agreements and understandings.
|
These Terms, together with our Privacy Policy, constitute the entire agreement between you and Towerops regarding the Service and supersede all prior agreements and understandings.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Contact Information</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
Contact Information
|
||||||
|
</h2>
|
||||||
|
<p class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
If you have any questions about these Terms, please contact us:
|
If you have any questions about these Terms, please contact us:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-none space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="list-none space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>
|
<li>
|
||||||
<strong class="font-semibold">Email:</strong>
|
<strong class="font-semibold">Email:</strong>
|
||||||
<a
|
<a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -367,7 +369,7 @@
|
||||||
<strong class="font-semibold">Support:</strong>
|
<strong class="font-semibold">Support:</strong>
|
||||||
<a
|
<a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -376,7 +378,7 @@
|
||||||
<strong class="font-semibold">Privacy Inquiries:</strong>
|
<strong class="font-semibold">Privacy Inquiries:</strong>
|
||||||
<a
|
<a
|
||||||
href="mailto:hi@towerops.net"
|
href="mailto:hi@towerops.net"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
hi@towerops.net
|
hi@towerops.net
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -385,7 +387,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-4 mt-8">
|
<section class="space-y-4 mt-8">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 italic">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 italic">
|
||||||
By using Towerops, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
|
By using Towerops, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
<Layouts.app flash={@flash} current_scope={@current_scope}>
|
<Layouts.app flash={@flash} current_scope={@current_scope}>
|
||||||
<div class="mx-auto max-w-4xl">
|
<div class="mx-auto max-w-4xl">
|
||||||
<div class="overflow-hidden rounded-2xl bg-white shadow-xl dark:bg-gray-900 lg:grid lg:grid-cols-5">
|
<div class="overflow-hidden rounded-2xl bg-white shadow-xl dark:bg-cool-steel-900 lg:grid lg:grid-cols-5">
|
||||||
<!-- Left: Brand panel -->
|
<!-- Left: Brand panel -->
|
||||||
<div class="hidden lg:col-span-2 lg:flex lg:flex-col lg:justify-between bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800 p-8 text-white">
|
<div class="hidden lg:col-span-2 lg:flex lg:flex-col lg:justify-between bg-gradient-to-br from-cerulean-600 via-cerulean-700 to-cerulean-800 p-8 text-white">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-signal" class="size-8" />
|
<.icon name="hero-signal" class="size-8" />
|
||||||
<span class="text-xl font-bold">TowerOps</span>
|
<span class="text-xl font-bold">TowerOps</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-6 text-lg font-medium leading-relaxed text-blue-100">
|
<p class="mt-6 text-lg font-medium leading-relaxed text-cerulean-100">
|
||||||
{t("Start monitoring your network in minutes.")}
|
{t("Start monitoring your network in minutes.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-auto space-y-4">
|
<div class="mt-auto space-y-4">
|
||||||
<div class="rounded-lg bg-white/10 p-4 backdrop-blur-sm">
|
<div class="rounded-lg bg-white/10 p-4 backdrop-blur-sm">
|
||||||
<p class="text-sm font-semibold text-white">{t("Free tier includes:")}</p>
|
<p class="text-sm font-semibold text-white">{t("Free tier includes:")}</p>
|
||||||
<ul class="mt-2 space-y-1.5 text-sm text-blue-100">
|
<ul class="mt-2 space-y-1.5 text-sm text-cerulean-100">
|
||||||
<li class="flex items-center gap-2">
|
<li class="flex items-center gap-2">
|
||||||
<.icon name="hero-check" class="size-4 text-green-300" />
|
<.icon name="hero-check" class="size-4 text-green-300" />
|
||||||
{t("Monitor up to 10 devices")}
|
{t("Monitor up to 10 devices")}
|
||||||
|
|
@ -41,23 +41,23 @@
|
||||||
<div class="col-span-3 px-6 py-10 sm:px-12">
|
<div class="col-span-3 px-6 py-10 sm:px-12">
|
||||||
<!-- Mobile brand mark -->
|
<!-- Mobile brand mark -->
|
||||||
<div class="mb-6 flex items-center justify-center gap-2 lg:hidden">
|
<div class="mb-6 flex items-center justify-center gap-2 lg:hidden">
|
||||||
<.icon name="hero-signal" class="size-7 text-blue-600 dark:text-blue-400" />
|
<.icon name="hero-signal" class="size-7 text-cerulean-600 dark:text-cerulean-400" />
|
||||||
<span class="text-lg font-bold text-gray-900 dark:text-white">TowerOps</span>
|
<span class="text-lg font-bold text-cool-steel-900 dark:text-white">TowerOps</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center lg:text-left">
|
<div class="text-center lg:text-left">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
<%= if @invitation do %>
|
<%= if @invitation do %>
|
||||||
{t_auth("Join %{organization}", organization: @invitation.organization.name)}
|
{t_auth("Join %{organization}", organization: @invitation.organization.name)}
|
||||||
<% else %>
|
<% else %>
|
||||||
{t_auth("Create your account")}
|
{t_auth("Create your account")}
|
||||||
<% end %>
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t_auth("Already registered?")}
|
{t_auth("Already registered?")}
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/log-in"}
|
navigate={~p"/users/log-in"}
|
||||||
class="font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="font-semibold text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t_auth("Log in")}
|
{t_auth("Log in")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -75,8 +75,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @invitation do %>
|
<%= if @invitation do %>
|
||||||
<div class="mt-5 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4 border border-blue-200 dark:border-blue-800">
|
<div class="mt-5 rounded-lg bg-cerulean-50 dark:bg-cerulean-900/20 p-4 border border-cerulean-200 dark:border-cerulean-800">
|
||||||
<p class="text-sm text-blue-900 dark:text-blue-100">
|
<p class="text-sm text-cerulean-900 dark:text-cerulean-100">
|
||||||
{t_auth("You've been invited to join %{organization} as a %{role}.",
|
{t_auth("You've been invited to join %{organization} as a %{role}.",
|
||||||
organization: @invitation.organization.name,
|
organization: @invitation.organization.name,
|
||||||
role: @invitation.role
|
role: @invitation.role
|
||||||
|
|
@ -110,27 +110,27 @@
|
||||||
<div class="mb-4 -mt-2">
|
<div class="mb-4 -mt-2">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<div
|
<div
|
||||||
class="h-1 flex-1 rounded-full bg-gray-200 dark:bg-gray-700 password-strength-bar"
|
class="h-1 flex-1 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 password-strength-bar"
|
||||||
data-index="1"
|
data-index="1"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-1 flex-1 rounded-full bg-gray-200 dark:bg-gray-700 password-strength-bar"
|
class="h-1 flex-1 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 password-strength-bar"
|
||||||
data-index="2"
|
data-index="2"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-1 flex-1 rounded-full bg-gray-200 dark:bg-gray-700 password-strength-bar"
|
class="h-1 flex-1 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 password-strength-bar"
|
||||||
data-index="3"
|
data-index="3"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-1 flex-1 rounded-full bg-gray-200 dark:bg-gray-700 password-strength-bar"
|
class="h-1 flex-1 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 password-strength-bar"
|
||||||
data-index="4"
|
data-index="4"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-500">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-500">
|
||||||
{t_auth("Use 12+ characters for a strong password")}
|
{t_auth("Use 12+ characters for a strong password")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -154,18 +154,21 @@
|
||||||
name="user[privacy_policy_consent]"
|
name="user[privacy_policy_consent]"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
required
|
required
|
||||||
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
class="h-4 w-4 rounded border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:checked:bg-cerulean-500 dark:checked:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 text-sm">
|
<div class="ml-3 text-sm">
|
||||||
<label for="privacy_policy_consent" class="text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="privacy_policy_consent"
|
||||||
|
class="text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
{t_auth("I agree to the %{link}",
|
{t_auth("I agree to the %{link}",
|
||||||
link:
|
link:
|
||||||
raw(
|
raw(
|
||||||
~s(<a href="/privacy" target="_blank" class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">#{t_auth("Privacy Policy")}</a>)
|
~s(<a href="/privacy" target="_blank" class="font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300">#{t_auth("Privacy Policy")}</a>)
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<span class="text-red-600 dark:text-red-400">*</span>
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,18 +180,21 @@
|
||||||
name="user[terms_of_service_consent]"
|
name="user[terms_of_service_consent]"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
required
|
required
|
||||||
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
class="h-4 w-4 rounded border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:checked:bg-cerulean-500 dark:checked:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 text-sm">
|
<div class="ml-3 text-sm">
|
||||||
<label for="terms_of_service_consent" class="text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="terms_of_service_consent"
|
||||||
|
class="text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
{t_auth("I agree to the %{link}",
|
{t_auth("I agree to the %{link}",
|
||||||
link:
|
link:
|
||||||
raw(
|
raw(
|
||||||
~s(<a href="/terms" target="_blank" class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">#{t_auth("Terms of Service")}</a>)
|
~s(<a href="/terms" target="_blank" class="font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300">#{t_auth("Terms of Service")}</a>)
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<span class="text-red-600 dark:text-red-400">*</span>
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/log-in"}
|
navigate={~p"/users/log-in"}
|
||||||
class="text-sm font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm font-semibold text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t_auth("Back to log in")}
|
{t_auth("Back to log in")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<p
|
<p
|
||||||
:if={!@user.confirmed_at}
|
:if={!@user.confirmed_at}
|
||||||
class="mt-6 rounded-lg border border-blue-200 bg-blue-50 p-4 text-sm text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200"
|
class="mt-6 rounded-lg border border-cerulean-200 bg-cerulean-50 p-4 text-sm text-cerulean-700 dark:border-cerulean-800 dark:bg-cerulean-950 dark:text-cerulean-200"
|
||||||
>
|
>
|
||||||
{t_auth("Tip: If you prefer passwords, you can enable them in the user settings.")}
|
{t_auth("Tip: If you prefer passwords, you can enable them in the user settings.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
<Layouts.app flash={@flash} current_scope={@current_scope}>
|
<Layouts.app flash={@flash} current_scope={@current_scope}>
|
||||||
<div class="mx-auto max-w-4xl">
|
<div class="mx-auto max-w-4xl">
|
||||||
<div class="overflow-hidden rounded-2xl bg-white shadow-xl dark:bg-gray-900 lg:grid lg:grid-cols-5">
|
<div class="overflow-hidden rounded-2xl bg-white shadow-xl dark:bg-cool-steel-900 lg:grid lg:grid-cols-5">
|
||||||
<!-- Left: Brand panel -->
|
<!-- Left: Brand panel -->
|
||||||
<div class="hidden lg:col-span-2 lg:flex lg:flex-col lg:justify-between bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800 p-8 text-white">
|
<div class="hidden lg:col-span-2 lg:flex lg:flex-col lg:justify-between bg-gradient-to-br from-cerulean-600 via-cerulean-700 to-cerulean-800 p-8 text-white">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-signal" class="size-8" />
|
<.icon name="hero-signal" class="size-8" />
|
||||||
<span class="text-xl font-bold">TowerOps</span>
|
<span class="text-xl font-bold">TowerOps</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-6 text-lg font-medium leading-relaxed text-blue-100">
|
<p class="mt-6 text-lg font-medium leading-relaxed text-cerulean-100">
|
||||||
{t("Monitor your entire network from a single dashboard.")}
|
{t("Monitor your entire network from a single dashboard.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
<div class="rounded-lg bg-white/10 p-4 backdrop-blur-sm">
|
<div class="rounded-lg bg-white/10 p-4 backdrop-blur-sm">
|
||||||
<p class="text-sm italic text-blue-100">
|
<p class="text-sm italic text-cerulean-100">
|
||||||
{t(
|
{t(
|
||||||
"\"TowerOps cut our response time to network issues by 80%. We can see problems before customers even notice.\""
|
"\"TowerOps cut our response time to network issues by 80%. We can see problems before customers even notice.\""
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 text-xs font-semibold text-blue-200">{t("— WISP Network Operator")}</p>
|
<p class="mt-3 text-xs font-semibold text-cerulean-200">
|
||||||
|
{t("— WISP Network Operator")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -28,16 +30,16 @@
|
||||||
<div class="col-span-3 px-6 py-10 sm:px-12">
|
<div class="col-span-3 px-6 py-10 sm:px-12">
|
||||||
<!-- Mobile brand mark -->
|
<!-- Mobile brand mark -->
|
||||||
<div class="mb-6 flex items-center justify-center gap-2 lg:hidden">
|
<div class="mb-6 flex items-center justify-center gap-2 lg:hidden">
|
||||||
<.icon name="hero-signal" class="size-7 text-blue-600 dark:text-blue-400" />
|
<.icon name="hero-signal" class="size-7 text-cerulean-600 dark:text-cerulean-400" />
|
||||||
<span class="text-lg font-bold text-gray-900 dark:text-white">TowerOps</span>
|
<span class="text-lg font-bold text-cool-steel-900 dark:text-white">TowerOps</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center lg:text-left">
|
<div class="text-center lg:text-left">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{t_auth("Log in")}
|
{t_auth("Log in")}
|
||||||
</h1>
|
</h1>
|
||||||
<%= if @current_scope do %>
|
<%= if @current_scope do %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t_auth("You need to reauthenticate to perform sensitive actions on your account.")}
|
{t_auth("You need to reauthenticate to perform sensitive actions on your account.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -53,18 +55,18 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:if={local_mail_adapter?()}
|
:if={local_mail_adapter?()}
|
||||||
class="mt-5 rounded-lg bg-blue-50 p-4 dark:bg-blue-950"
|
class="mt-5 rounded-lg bg-cerulean-50 p-4 dark:bg-cerulean-950"
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-information-circle"
|
name="hero-information-circle"
|
||||||
class="h-6 w-6 flex-shrink-0 text-blue-600 dark:text-blue-400"
|
class="h-6 w-6 flex-shrink-0 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm text-blue-700 dark:text-blue-200">
|
<p class="text-sm text-cerulean-700 dark:text-cerulean-200">
|
||||||
{t_auth("You are running the local mail adapter.")}
|
{t_auth("You are running the local mail adapter.")}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-blue-700 dark:text-blue-200">
|
<p class="mt-1 text-sm text-cerulean-700 dark:text-cerulean-200">
|
||||||
{t_auth("To see sent emails, visit")}
|
{t_auth("To see sent emails, visit")}
|
||||||
<a href="/dev/mailbox" class="font-medium underline">{t_auth("the mailbox page")}</a>.
|
<a href="/dev/mailbox" class="font-medium underline">{t_auth("the mailbox page")}</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -100,18 +102,18 @@
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<label class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
<label class="flex items-center gap-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name={@form[:remember_me].name}
|
name={@form[:remember_me].name}
|
||||||
value="true"
|
value="true"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800"
|
class="h-4 w-4 rounded border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
/>
|
/>
|
||||||
{t_auth("Remember me")}
|
{t_auth("Remember me")}
|
||||||
</label>
|
</label>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/reset-password"}
|
navigate={~p"/users/reset-password"}
|
||||||
class="text-sm font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm font-semibold text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t_auth("Forgot password?")}
|
{t_auth("Forgot password?")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -125,10 +127,10 @@
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="absolute inset-0 flex items-center">
|
<div class="absolute inset-0 flex items-center">
|
||||||
<div class="w-full border-t border-gray-200 dark:border-white/10"></div>
|
<div class="w-full border-t border-cool-steel-200 dark:border-white/10"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex justify-center text-sm">
|
<div class="relative flex justify-center text-sm">
|
||||||
<span class="bg-white px-3 text-gray-500 dark:bg-gray-900 dark:text-gray-500">
|
<span class="bg-white px-3 text-cool-steel-500 dark:bg-cool-steel-900 dark:text-cool-steel-500">
|
||||||
{t("or")}
|
{t("or")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -151,10 +153,10 @@
|
||||||
</.button>
|
</.button>
|
||||||
</.form>
|
</.form>
|
||||||
|
|
||||||
<p class="text-center text-sm text-gray-500 dark:text-gray-500">
|
<p class="text-center text-sm text-cool-steel-500 dark:text-cool-steel-500">
|
||||||
<.link
|
<.link
|
||||||
href={~p"/users/confirm"}
|
href={~p"/users/confirm"}
|
||||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 hover:underline"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
{t_auth("Didn't receive confirmation instructions?")}
|
{t_auth("Didn't receive confirmation instructions?")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
phx-mounted={JS.focus()}
|
phx-mounted={JS.focus()}
|
||||||
/>
|
/>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t_auth(
|
{t_auth(
|
||||||
"Enter the 6-digit code from your authenticator app, or use a recovery code (XXXX-XXXX format)."
|
"Enter the 6-digit code from your authenticator app, or use a recovery code (XXXX-XXXX format)."
|
||||||
)}
|
)}
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
<.link
|
<.link
|
||||||
href={~p"/users/log-in"}
|
href={~p"/users/log-in"}
|
||||||
class="text-sm font-semibold text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
class="text-sm font-semibold text-cool-steel-600 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t_auth("← Back to login")}
|
{t_auth("← Back to login")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -47,38 +47,38 @@
|
||||||
<div class="my-4 border-t border-cool-steel-200 dark:border-cool-steel-700" />
|
<div class="my-4 border-t border-cool-steel-200 dark:border-cool-steel-700" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold leading-6 text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold leading-6 text-cool-steel-900 dark:text-white">
|
||||||
{t_auth("Alert Notification Devices")}
|
{t_auth("Alert Notification Devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t_auth("Manage mobile devices that receive push notifications for alerts")}
|
{t_auth("Manage mobile devices that receive push notifications for alerts")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 space-y-4">
|
<div class="mt-6 space-y-4">
|
||||||
<%= if Enum.empty?(@mobile_sessions) do %>
|
<%= if Enum.empty?(@mobile_sessions) do %>
|
||||||
<div class="rounded-lg border border-dashed border-gray-300 p-8 text-center dark:border-white/10">
|
<div class="rounded-lg border border-dashed border-cool-steel-300 p-8 text-center dark:border-white/10">
|
||||||
<.icon name="hero-device-phone-mobile" class="mx-auto h-12 w-12 text-gray-400" />
|
<.icon name="hero-device-phone-mobile" class="mx-auto h-12 w-12 text-cool-steel-400" />
|
||||||
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t_auth("No mobile devices registered")}
|
{t_auth("No mobile devices registered")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t_auth("Add a mobile device to receive push notifications for alerts")}
|
{t_auth("Add a mobile device to receive push notifications for alerts")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<%= for session <- @mobile_sessions do %>
|
<%= for session <- @mobile_sessions do %>
|
||||||
<div class="flex items-center justify-between rounded-lg border border-gray-200 p-4 dark:border-white/10">
|
<div class="flex items-center justify-between rounded-lg border border-cool-steel-200 p-4 dark:border-white/10">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<.icon name="hero-device-phone-mobile" class="h-5 w-5 text-gray-400" />
|
<.icon name="hero-device-phone-mobile" class="h-5 w-5 text-cool-steel-400" />
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-gray-900 dark:text-white">
|
<p class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{session.device_name || t_auth("Unknown Device")}
|
{session.device_name || t_auth("Unknown Device")}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{session.device_os} • {session.app_version}
|
{session.device_os} • {session.app_version}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t_auth("Last used %{date}",
|
{t_auth("Last used %{date}",
|
||||||
date: ToweropsWeb.TimeHelpers.format_date(session.last_used_at, "UTC")
|
date: ToweropsWeb.TimeHelpers.format_date(session.last_used_at, "UTC")
|
||||||
)}
|
)}
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
do:
|
do:
|
||||||
"bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/20 dark:text-green-400 dark:hover:bg-green-900/30",
|
"bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/20 dark:text-green-400 dark:hover:bg-green-900/30",
|
||||||
else:
|
else:
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800/50 dark:text-gray-400 dark:hover:bg-gray-800"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800/50 dark:text-cool-steel-400 dark:hover:bg-cool-steel-800"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -131,7 +131,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
data-confirm={t_auth("Are you sure you want to remove this device?")}
|
data-confirm={t_auth("Are you sure you want to remove this device?")}
|
||||||
class="text-sm font-semibold text-red-600 hover:text-red-500 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sm font-semibold text-sweet-salmon-600 hover:text-sweet-salmon-500 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
{t_auth("Remove")}
|
{t_auth("Remove")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -144,7 +144,7 @@
|
||||||
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/mobile/qr-login"}
|
navigate={~p"/mobile/qr-login"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-gray-900 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-700 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-300"
|
class="inline-flex items-center gap-2 rounded-lg bg-cool-steel-900 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-cool-steel-700 dark:bg-cool-steel-50 dark:text-cool-steel-900 dark:hover:bg-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-qr-code" class="h-4 w-4" /> {t_auth("Add Mobile Device")}
|
<.icon name="hero-qr-code" class="h-4 w-4" /> {t_auth("Add Mobile Device")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
phx-mounted={JS.focus()}
|
phx-mounted={JS.focus()}
|
||||||
/>
|
/>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t_auth(
|
{t_auth(
|
||||||
"Enter the 6-digit code from your authenticator app. Recovery codes are not allowed for sensitive operations."
|
"Enter the 6-digit code from your authenticator app. Recovery codes are not allowed for sensitive operations."
|
||||||
)}
|
)}
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
<.link
|
<.link
|
||||||
href={~p"/users/log-out"}
|
href={~p"/users/log-out"}
|
||||||
method="delete"
|
method="delete"
|
||||||
class="text-sm font-semibold text-gray-600 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
class="text-sm font-semibold text-cool-steel-600 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t_auth("← Log out instead")}
|
{t_auth("← Log out instead")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -29,31 +29,31 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
active_page="settings"
|
active_page="settings"
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
Activity Log
|
Activity Log
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
View all actions performed by you and on your account data for transparency and security.
|
View all actions performed by you and on your account data for transparency and security.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 space-y-4">
|
<div class="mt-8 space-y-4">
|
||||||
<%= if Enum.empty?(@audit_logs) do %>
|
<%= if Enum.empty?(@audit_logs) do %>
|
||||||
<div class="text-center py-12 bg-white dark:bg-gray-900 rounded-lg shadow">
|
<div class="text-center py-12 bg-white dark:bg-cool-steel-900 rounded-lg shadow">
|
||||||
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-gray-400" />
|
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-cool-steel-400" />
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
No activity logs
|
No activity logs
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Your activity will appear here as you use the platform.
|
Your activity will appear here as you use the platform.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<div class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
<ul role="list" class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<%= for log <- @audit_logs do %>
|
<%= for log <- @audit_logs do %>
|
||||||
<li class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-800">
|
<li class="px-6 py-4 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
|
@ -63,12 +63,12 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
]}>
|
]}>
|
||||||
{format_action(log.action)}
|
{format_action(log.action)}
|
||||||
</span>
|
</span>
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{action_description(log)}
|
{action_description(log)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 flex flex-col gap-1 text-sm text-gray-500 dark:text-gray-400">
|
<div class="mt-2 flex flex-col gap-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if log.superuser do %>
|
<%= if log.superuser do %>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<.icon name="hero-user" class="h-4 w-4" />
|
<.icon name="hero-user" class="h-4 w-4" />
|
||||||
|
|
@ -81,7 +81,7 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
<%= if log.request_path do %>
|
<%= if log.request_path do %>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<.icon name="hero-link" class="h-4 w-4" />
|
<.icon name="hero-link" class="h-4 w-4" />
|
||||||
<code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">
|
<code class="text-xs bg-cool-steel-100 dark:bg-cool-steel-800 px-1 rounded">
|
||||||
{log.request_path}
|
{log.request_path}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,7 +99,7 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
<.icon name="hero-eye" class="h-4 w-4 mt-0.5" />
|
<.icon name="hero-eye" class="h-4 w-4 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<span class="font-medium">Data accessed:</span>
|
<span class="font-medium">Data accessed:</span>
|
||||||
<code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded ml-1">
|
<code class="text-xs bg-cool-steel-100 dark:bg-cool-steel-800 px-1 rounded ml-1">
|
||||||
{inspect(log.data_accessed, pretty: true, limit: 3)}
|
{inspect(log.data_accessed, pretty: true, limit: 3)}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -108,10 +108,10 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
|
|
||||||
<%= if log.metadata && map_size(log.metadata) > 0 do %>
|
<%= if log.metadata && map_size(log.metadata) > 0 do %>
|
||||||
<details class="mt-1">
|
<details class="mt-1">
|
||||||
<summary class="cursor-pointer text-xs text-blue-600 dark:text-blue-400 hover:underline">
|
<summary class="cursor-pointer text-xs text-cerulean-600 dark:text-cerulean-400 hover:underline">
|
||||||
View metadata
|
View metadata
|
||||||
</summary>
|
</summary>
|
||||||
<pre class="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
|
<pre class="mt-1 text-xs bg-cool-steel-100 dark:bg-cool-steel-800 p-2 rounded overflow-x-auto">
|
||||||
{Jason.encode!(log.metadata, pretty: true)}
|
{Jason.encode!(log.metadata, pretty: true)}
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|
@ -120,7 +120,7 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-4 flex-shrink-0 text-right">
|
<div class="ml-4 flex-shrink-0 text-right">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{format_timestamp(log.inserted_at, @current_scope)}
|
{format_timestamp(log.inserted_at, @current_scope)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -130,13 +130,13 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
<div class="bg-cerulean-50 dark:bg-cerulean-900/20 border border-cerulean-200 dark:border-cerulean-800 rounded-lg p-4">
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-information-circle"
|
name="hero-information-circle"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<div class="text-sm text-blue-800 dark:text-blue-300">
|
<div class="text-sm text-cerulean-800 dark:text-cerulean-300">
|
||||||
<p class="font-medium">GDPR Transparency</p>
|
<p class="font-medium">GDPR Transparency</p>
|
||||||
<p class="mt-1">
|
<p class="mt-1">
|
||||||
This activity log shows actions performed by you and on your account data.
|
This activity log shows actions performed by you and on your account data.
|
||||||
|
|
@ -177,10 +177,10 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20"
|
"bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20"
|
||||||
|
|
||||||
:warning ->
|
:warning ->
|
||||||
"bg-yellow-50 text-yellow-700 ring-yellow-600/20 dark:bg-yellow-500/10 dark:text-yellow-400 dark:ring-yellow-500/20"
|
"bg-wheat-50 text-wheat-700 ring-wheat-600/20 dark:bg-wheat-500/10 dark:text-wheat-400 dark:ring-wheat-500/20"
|
||||||
|
|
||||||
:danger ->
|
:danger ->
|
||||||
"bg-red-50 text-red-700 ring-red-600/20 dark:bg-red-500/10 dark:text-red-400 dark:ring-red-500/20"
|
"bg-sweet-salmon-50 text-sweet-salmon-700 ring-sweet-salmon-600/20 dark:bg-sweet-salmon-500/10 dark:text-sweet-salmon-400 dark:ring-sweet-salmon-500/20"
|
||||||
|
|
||||||
:impersonation ->
|
:impersonation ->
|
||||||
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-500/10 dark:text-orange-400 dark:ring-orange-500/20"
|
"bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-500/10 dark:text-orange-400 dark:ring-orange-500/20"
|
||||||
|
|
@ -189,10 +189,10 @@ defmodule ToweropsWeb.AccountLive.Activity do
|
||||||
"bg-purple-50 text-purple-700 ring-purple-600/20 dark:bg-purple-500/10 dark:text-purple-400 dark:ring-purple-500/20"
|
"bg-purple-50 text-purple-700 ring-purple-600/20 dark:bg-purple-500/10 dark:text-purple-400 dark:ring-purple-500/20"
|
||||||
|
|
||||||
:info ->
|
:info ->
|
||||||
"bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-500/10 dark:text-blue-400 dark:ring-blue-500/20"
|
"bg-cerulean-50 text-cerulean-700 ring-cerulean-600/20 dark:bg-cerulean-500/10 dark:text-cerulean-400 dark:ring-cerulean-500/20"
|
||||||
|
|
||||||
:default ->
|
:default ->
|
||||||
"bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-gray-500/10 dark:text-gray-400 dark:ring-gray-500/20"
|
"bg-cool-steel-50 text-cool-steel-700 ring-cool-steel-600/20 dark:bg-cool-steel-500/10 dark:text-cool-steel-400 dark:ring-cool-steel-500/20"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,25 +68,25 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
active_page="settings"
|
active_page="settings"
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
Account Settings
|
Account Settings
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Manage your account email address, password, and security settings
|
Manage your account email address, password, and security settings
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<header class="border-b border-gray-200 dark:border-white/5">
|
<header class="border-b border-cool-steel-200 dark:border-white/5">
|
||||||
<nav class="flex overflow-x-auto py-4">
|
<nav class="flex overflow-x-auto py-4">
|
||||||
<ul
|
<ul
|
||||||
role="list"
|
role="list"
|
||||||
class="flex min-w-full flex-none gap-x-6 px-4 text-sm/6 font-semibold text-gray-500 sm:px-6 lg:px-8 dark:text-gray-400"
|
class="flex min-w-full flex-none gap-x-6 px-4 text-sm/6 font-semibold text-cool-steel-500 sm:px-6 lg:px-8 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=personal"}
|
navigate={~p"/users/settings?tab=personal"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Personal
|
Personal
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -94,7 +94,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=account"}
|
navigate={~p"/users/settings?tab=account"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Account
|
Account
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -102,7 +102,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=sessions"}
|
navigate={~p"/users/settings?tab=sessions"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Sessions
|
Sessions
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -110,7 +110,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=api"}
|
navigate={~p"/users/settings?tab=api"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
API
|
API
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -118,7 +118,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=notifications"}
|
navigate={~p"/users/settings?tab=notifications"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Notifications
|
Notifications
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -126,7 +126,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings?tab=security"}
|
navigate={~p"/users/settings?tab=security"}
|
||||||
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
|
class="text-cool-steel-500 hover:text-cerulean-600 dark:text-cool-steel-400 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Security
|
Security
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -134,7 +134,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/my-data"}
|
navigate={~p"/users/my-data"}
|
||||||
class="text-indigo-600 dark:text-indigo-400"
|
class="text-cerulean-600 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
My Data
|
My Data
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -145,43 +145,57 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
|
|
||||||
<div class="space-y-8 py-8">
|
<div class="space-y-8 py-8">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
This page shows all personal data we have stored about you in compliance with GDPR Article 15 (Right to Access).
|
This page shows all personal data we have stored about you in compliance with GDPR Article 15 (Right to Access).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Profile Information -->
|
<!-- Profile Information -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">Profile Information</h2>
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
|
Profile Information
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Email Address</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{@user_data.profile.email}</dd>
|
Email Address
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{@user_data.profile.email}
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">First Name</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
First Name
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{@user_data.profile.first_name || "Not set"}
|
{@user_data.profile.first_name || "Not set"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Last Name</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Last Name
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{@user_data.profile.last_name || "Not set"}
|
{@user_data.profile.last_name || "Not set"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Timezone</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Timezone
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{@user_data.profile.timezone}
|
{@user_data.profile.timezone}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Account Created</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Account Created
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_datetime(
|
{ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
@user_data.profile.inserted_at,
|
@user_data.profile.inserted_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -190,11 +204,13 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Email Confirmed</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Email Confirmed
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{if @user_data.profile.confirmed_at, do: "Yes", else: "No"}
|
{if @user_data.profile.confirmed_at, do: "Yes", else: "No"}
|
||||||
<%= if @user_data.profile.confirmed_at do %>
|
<%= if @user_data.profile.confirmed_at do %>
|
||||||
<span class="text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
({ToweropsWeb.TimeHelpers.format_date(
|
({ToweropsWeb.TimeHelpers.format_date(
|
||||||
@user_data.profile.confirmed_at,
|
@user_data.profile.confirmed_at,
|
||||||
@current_scope.timezone
|
@current_scope.timezone
|
||||||
|
|
@ -208,40 +224,46 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Consent Records -->
|
<!-- Consent Records -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
Consent Records
|
Consent Records
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Your consent to our Privacy Policy and Terms of Service
|
Your consent to our Privacy Policy and Terms of Service
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<%= if Enum.empty?(@user_data.consents) do %>
|
<%= if Enum.empty?(@user_data.consents) do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No consent records found.
|
No consent records found.
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= for consent <- @user_data.consents do %>
|
<%= for consent <- @user_data.consents do %>
|
||||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
<div class="border border-cool-steel-200 dark:border-cool-steel-700 rounded-lg p-4">
|
||||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Type</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Type
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{String.replace(consent.consent_type, "_", " ") |> String.capitalize()}
|
{String.replace(consent.consent_type, "_", " ") |> String.capitalize()}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Version</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{consent.version}</dd>
|
Version
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{consent.version}
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Granted
|
Granted
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_datetime(
|
{ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
consent.granted_at,
|
consent.granted_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -250,12 +272,12 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Status
|
Status
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm">
|
<dd class="mt-1 text-sm">
|
||||||
<%= if consent.revoked_at do %>
|
<%= if consent.revoked_at do %>
|
||||||
<span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 inset-ring-1 inset-ring-red-600/20 dark:bg-red-500/10 dark:text-red-400 dark:inset-ring-red-500/20">
|
<span class="inline-flex items-center rounded-md bg-sweet-salmon-50 px-2 py-1 text-xs font-medium text-sweet-salmon-700 inset-ring-1 inset-ring-sweet-salmon-600/20 dark:bg-sweet-salmon-500/10 dark:text-sweet-salmon-400 dark:inset-ring-sweet-salmon-500/20">
|
||||||
Revoked {ToweropsWeb.TimeHelpers.format_date(
|
Revoked {ToweropsWeb.TimeHelpers.format_date(
|
||||||
consent.revoked_at,
|
consent.revoked_at,
|
||||||
@current_scope.timezone
|
@current_scope.timezone
|
||||||
|
|
@ -276,7 +298,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
phx-click="revoke_consent"
|
phx-click="revoke_consent"
|
||||||
phx-value-consent-id={consent.id}
|
phx-value-consent-id={consent.id}
|
||||||
data-confirm="Are you sure you want to revoke this consent? This may limit your ability to use certain features."
|
data-confirm="Are you sure you want to revoke this consent? This may limit your ability to use certain features."
|
||||||
class="text-sm font-medium text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sm font-medium text-sweet-salmon-600 hover:text-sweet-salmon-700 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
Revoke Consent
|
Revoke Consent
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -291,42 +313,50 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
|
|
||||||
<%= if @is_owner do %>
|
<%= if @is_owner do %>
|
||||||
<!-- Organizations -->
|
<!-- Organizations -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
Organizations
|
Organizations
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Organizations you own or are a member of
|
Organizations you own or are a member of
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<%= if Enum.empty?(@user_data.organizations) do %>
|
<%= if Enum.empty?(@user_data.organizations) do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">No organization memberships.</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
No organization memberships.
|
||||||
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= for org <- @user_data.organizations do %>
|
<%= for org <- @user_data.organizations do %>
|
||||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
<div class="border border-cool-steel-200 dark:border-cool-steel-700 rounded-lg p-4">
|
||||||
<dl class="grid grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-3 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Name</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{org.name}</dd>
|
Name
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">{org.name}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Role</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Role
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{if org.is_owner, do: "Owner", else: "Member"}
|
{if org.is_owner, do: "Owner", else: "Member"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Slug</dt>
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">{org.slug}</dd>
|
Slug
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">{org.slug}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Created
|
Created
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_date(
|
{ToweropsWeb.TimeHelpers.format_date(
|
||||||
org.inserted_at,
|
org.inserted_at,
|
||||||
@current_scope.timezone
|
@current_scope.timezone
|
||||||
|
|
@ -342,50 +372,52 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Devices -->
|
<!-- Devices -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
Devices
|
Devices
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Network devices monitored by your organizations
|
Network devices monitored by your organizations
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<%= if Enum.empty?(@user_data.devices) do %>
|
<%= if Enum.empty?(@user_data.devices) do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">No devices configured.</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
No devices configured.
|
||||||
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
IP Address
|
IP Address
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Organization
|
Organization
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Created
|
Created
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<%= for device <- @user_data.devices do %>
|
<%= for device <- @user_data.devices do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
<td class="px-3 py-4 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{device.name}
|
{device.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
<td class="px-3 py-4 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{device.ip_address}
|
{device.ip_address}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-3 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{device.site.organization.name}
|
{device.site.organization.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-3 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{ToweropsWeb.TimeHelpers.format_date(
|
{ToweropsWeb.TimeHelpers.format_date(
|
||||||
device.inserted_at,
|
device.inserted_at,
|
||||||
@current_scope.timezone
|
@current_scope.timezone
|
||||||
|
|
@ -396,7 +428,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Total devices: {length(@user_data.devices)}
|
Total devices: {length(@user_data.devices)}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -404,44 +436,46 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Alerts -->
|
<!-- Alerts -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
Recent Alerts
|
Recent Alerts
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Alerts generated for your devices (last 90 days)
|
Alerts generated for your devices (last 90 days)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<%= if Enum.empty?(@user_data.alerts) do %>
|
<%= if Enum.empty?(@user_data.alerts) do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">No alerts in the last 90 days.</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
No alerts in the last 90 days.
|
||||||
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Device
|
Device
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Type
|
Type
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-3 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Created
|
Created
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<%= for alert <- Enum.take(@user_data.alerts, 50) do %>
|
<%= for alert <- Enum.take(@user_data.alerts, 50) do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
<td class="px-3 py-4 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{alert.device.name}
|
{alert.device.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
<td class="px-3 py-4 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{alert.alert_type}
|
{alert.alert_type}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm">
|
<td class="px-3 py-4 text-sm">
|
||||||
|
|
@ -452,7 +486,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
{alert_status_text(alert)}
|
{alert_status_text(alert)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-3 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{ToweropsWeb.TimeHelpers.format_datetime(
|
{ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
alert.inserted_at,
|
alert.inserted_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -464,7 +498,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Showing {min(50, length(@user_data.alerts))} of {length(@user_data.alerts)} alerts
|
Showing {min(50, length(@user_data.alerts))} of {length(@user_data.alerts)} alerts
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -473,31 +507,33 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Audit Logs -->
|
<!-- Audit Logs -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
Audit Log
|
Audit Log
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Administrative actions related to your account
|
Administrative actions related to your account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<%= if Enum.empty?(@user_data.audit_logs) do %>
|
<%= if Enum.empty?(@user_data.audit_logs) do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">No audit log entries.</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
No audit log entries.
|
||||||
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<%= for log <- Enum.take(@user_data.audit_logs, 20) do %>
|
<%= for log <- Enum.take(@user_data.audit_logs, 20) do %>
|
||||||
<div class="border-l-4 border-blue-400 bg-gray-50 dark:bg-gray-800 p-3">
|
<div class="border-l-4 border-cerulean-400 bg-cool-steel-50 dark:bg-cool-steel-800 p-3">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{log.action}
|
{log.action}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
By: {if log.superuser, do: log.superuser.email, else: "System"}
|
By: {if log.superuser, do: log.superuser.email, else: "System"}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-500">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-500">
|
||||||
{ToweropsWeb.TimeHelpers.format_datetime(
|
{ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
log.inserted_at,
|
log.inserted_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -509,7 +545,7 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Showing {min(20, length(@user_data.audit_logs))} of {length(@user_data.audit_logs)} entries
|
Showing {min(20, length(@user_data.audit_logs))} of {length(@user_data.audit_logs)} entries
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -517,22 +553,22 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Export Data -->
|
<!-- Export Data -->
|
||||||
<section class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-6">
|
<section class="bg-cerulean-50 dark:bg-cerulean-900/20 border border-cerulean-200 dark:border-cerulean-800 rounded-lg p-6">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-down-tray"
|
name="hero-arrow-down-tray"
|
||||||
class="h-6 w-6 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
class="h-6 w-6 text-cerulean-600 dark:text-cerulean-400 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
<h3 class="text-sm font-semibold text-cerulean-900 dark:text-cerulean-300 mb-1">
|
||||||
Export Your Data
|
Export Your Data
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-blue-800 dark:text-blue-300 mb-4">
|
<p class="text-sm text-cerulean-800 dark:text-cerulean-300 mb-4">
|
||||||
You can download all your data in JSON format. This includes your profile, organizations, devices, alerts, and audit logs.
|
You can download all your data in JSON format. This includes your profile, organizations, devices, alerts, and audit logs.
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
href={~p"/api/v1/account/data"}
|
href={~p"/api/v1/account/data"}
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
class="inline-flex items-center gap-2 px-4 py-2 bg-cerulean-600 text-white text-sm font-medium rounded-lg hover:bg-cerulean-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download My Data (JSON)
|
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download My Data (JSON)
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -541,15 +577,15 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Data Processing Information -->
|
<!-- Data Processing Information -->
|
||||||
<section class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<section class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="px-4 py-5 sm:px-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="px-4 py-5 sm:px-6 border-b border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h2 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
How We Use Your Data
|
How We Use Your Data
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<div class="prose prose-sm dark:prose-invert max-w-none">
|
<div class="prose prose-sm dark:prose-invert max-w-none">
|
||||||
<ul class="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
<ul class="space-y-2 text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li>
|
<li>
|
||||||
<strong>Profile Information:</strong>
|
<strong>Profile Information:</strong>
|
||||||
Used to identify your account and personalize your experience
|
Used to identify your account and personalize your experience
|
||||||
|
|
@ -574,10 +610,10 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
Security records of administrative actions for compliance and security purposes
|
Security records of administrative actions for compliance and security purposes
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-4 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
For more information, please read our <.link
|
For more information, please read our <.link
|
||||||
navigate={~p"/privacy"}
|
navigate={~p"/privacy"}
|
||||||
class="text-blue-600 hover:text-blue-500 dark:text-blue-400"
|
class="text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</.link>.
|
</.link>.
|
||||||
|
|
@ -663,8 +699,8 @@ defmodule ToweropsWeb.AccountLive.MyData do
|
||||||
defp alert_status_class(alert) do
|
defp alert_status_class(alert) do
|
||||||
cond do
|
cond do
|
||||||
alert.resolved_at -> "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
alert.resolved_at -> "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
||||||
alert.acknowledged_at -> "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
alert.acknowledged_at -> "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
true -> "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
true -> "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="mx-auto max-w-2xl px-4 py-12">
|
<div class="mx-auto max-w-2xl px-4 py-12">
|
||||||
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-8">
|
<div class="bg-white dark:bg-cool-steel-900 rounded-lg shadow-lg p-8">
|
||||||
<%= if @show_recovery_codes do %>
|
<%= if @show_recovery_codes do %>
|
||||||
<!-- Recovery Codes Display -->
|
<!-- Recovery Codes Display -->
|
||||||
<div class="text-center mb-8">
|
<div class="text-center mb-8">
|
||||||
|
|
@ -149,27 +149,27 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
name="hero-shield-check"
|
name="hero-shield-check"
|
||||||
class="w-16 h-16 mx-auto mb-4 text-green-600 dark:text-green-400"
|
class="w-16 h-16 mx-auto mb-4 text-green-600 dark:text-green-400"
|
||||||
/>
|
/>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
<h1 class="text-3xl font-bold text-cool-steel-900 dark:text-white mb-2">
|
||||||
Save Your Recovery Codes
|
Save Your Recovery Codes
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Two-factor authentication has been enabled successfully!
|
Two-factor authentication has been enabled successfully!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Warning -->
|
<!-- Warning -->
|
||||||
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4 border border-yellow-200 dark:border-yellow-800">
|
<div class="rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-4 border border-wheat-200 dark:border-wheat-800">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-exclamation-triangle"
|
name="hero-exclamation-triangle"
|
||||||
class="h-5 w-5 text-yellow-400 mt-0.5"
|
class="h-5 w-5 text-wheat-400 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
<h3 class="text-sm font-medium text-wheat-800 dark:text-wheat-200">
|
||||||
Important: Save these recovery codes now
|
Important: Save these recovery codes now
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
<div class="mt-2 text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<ul class="list-disc pl-5 space-y-1">
|
<ul class="list-disc pl-5 space-y-1">
|
||||||
<li>These codes will only be shown once and cannot be retrieved later</li>
|
<li>These codes will only be shown once and cannot be retrieved later</li>
|
||||||
<li>Store them in a secure location like a password manager</li>
|
<li>Store them in a secure location like a password manager</li>
|
||||||
|
|
@ -181,14 +181,14 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Recovery Codes -->
|
<!-- Recovery Codes -->
|
||||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-6">
|
<div class="border border-cool-steel-200 dark:border-cool-steel-700 rounded-lg p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4 text-center">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4 text-center">
|
||||||
Your Recovery Codes
|
Your Recovery Codes
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-2 gap-3 max-w-md mx-auto">
|
<div class="grid grid-cols-2 gap-3 max-w-md mx-auto">
|
||||||
<%= for code <- @recovery_codes do %>
|
<%= for code <- @recovery_codes do %>
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-md p-3 text-center">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 rounded-md p-3 text-center">
|
||||||
<code class="text-lg font-mono text-gray-900 dark:text-white font-semibold">
|
<code class="text-lg font-mono text-cool-steel-900 dark:text-white font-semibold">
|
||||||
{code}
|
{code}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -199,7 +199,7 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click={JS.dispatch("phx:copy", to: "#recovery-codes-text")}
|
phx-click={JS.dispatch("phx:copy", to: "#recovery-codes-text")}
|
||||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-sm text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
class="inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 shadow-sm text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-clipboard-document" class="w-5 h-5 mr-2" /> Copy All
|
<.icon name="hero-clipboard-document" class="w-5 h-5 mr-2" /> Copy All
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -207,7 +207,7 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
<a
|
<a
|
||||||
href={"data:text/plain;charset=utf-8," <> URI.encode(Enum.join(@recovery_codes, "\n"))}
|
href={"data:text/plain;charset=utf-8," <> URI.encode(Enum.join(@recovery_codes, "\n"))}
|
||||||
download="towerops-recovery-codes.txt"
|
download="towerops-recovery-codes.txt"
|
||||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 shadow-sm text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
class="inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 shadow-sm text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-down-tray" class="w-5 h-5 mr-2" /> Download
|
<.icon name="hero-arrow-down-tray" class="w-5 h-5 mr-2" /> Download
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -226,7 +226,7 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="confirm_recovery_codes_saved"
|
phx-click="confirm_recovery_codes_saved"
|
||||||
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-cerulean-600 hover:bg-cerulean-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
I've Saved My Recovery Codes <.icon name="hero-arrow-right" class="w-5 h-5 ml-2" />
|
I've Saved My Recovery Codes <.icon name="hero-arrow-right" class="w-5 h-5 ml-2" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -237,29 +237,29 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
<div class="text-center mb-8">
|
<div class="text-center mb-8">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-shield-check"
|
name="hero-shield-check"
|
||||||
class="w-16 h-16 mx-auto mb-4 text-blue-600 dark:text-blue-400"
|
class="w-16 h-16 mx-auto mb-4 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
<h1 class="text-3xl font-bold text-cool-steel-900 dark:text-white mb-2">
|
||||||
Set Up Two-Factor Authentication
|
Set Up Two-Factor Authentication
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
To keep your account secure, two-factor authentication is required for all users.
|
To keep your account secure, two-factor authentication is required for all users.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Step 1: Scan QR Code -->
|
<!-- Step 1: Scan QR Code -->
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
<div class="border-t border-cool-steel-200 dark:border-cool-steel-700 pt-6">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white mb-4 flex items-center">
|
||||||
<span class="flex items-center justify-center w-8 h-8 bg-blue-600 text-white rounded-full mr-3 text-sm">
|
<span class="flex items-center justify-center w-8 h-8 bg-cerulean-600 text-white rounded-full mr-3 text-sm">
|
||||||
1
|
1
|
||||||
</span>
|
</span>
|
||||||
Install an Authenticator App
|
Install an Authenticator App
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-600 dark:text-gray-400 ml-11 mb-4">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400 ml-11 mb-4">
|
||||||
Download an authenticator app on your phone if you haven't already:
|
Download an authenticator app on your phone if you haven't already:
|
||||||
</p>
|
</p>
|
||||||
<ul class="ml-11 space-y-2 text-gray-700 dark:text-gray-300">
|
<ul class="ml-11 space-y-2 text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<li class="flex items-center">
|
<li class="flex items-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check-circle"
|
name="hero-check-circle"
|
||||||
|
|
@ -282,35 +282,35 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: Scan QR Code -->
|
<!-- Step 2: Scan QR Code -->
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
<div class="border-t border-cool-steel-200 dark:border-cool-steel-700 pt-6">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white mb-4 flex items-center">
|
||||||
<span class="flex items-center justify-center w-8 h-8 bg-blue-600 text-white rounded-full mr-3 text-sm">
|
<span class="flex items-center justify-center w-8 h-8 bg-cerulean-600 text-white rounded-full mr-3 text-sm">
|
||||||
2
|
2
|
||||||
</span>
|
</span>
|
||||||
Scan the QR Code
|
Scan the QR Code
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-600 dark:text-gray-400 ml-11 mb-4">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400 ml-11 mb-4">
|
||||||
Open your authenticator app and scan this QR code:
|
Open your authenticator app and scan this QR code:
|
||||||
</p>
|
</p>
|
||||||
<%= if @qr_code do %>
|
<%= if @qr_code do %>
|
||||||
<div class="ml-11 flex justify-center">
|
<div class="ml-11 flex justify-center">
|
||||||
<div class="bg-white dark:bg-gray-900 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-700">
|
<div class="bg-white dark:bg-cool-steel-900 p-4 rounded-lg border-2 border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<img src={@qr_code} alt="TOTP QR Code" class="w-64 h-64" />
|
<img src={@qr_code} alt="TOTP QR Code" class="w-64 h-64" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="ml-11 flex justify-center">
|
<div class="ml-11 flex justify-center">
|
||||||
<div class="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-700 w-72 h-72 flex items-center justify-center">
|
<div class="bg-cool-steel-100 dark:bg-cool-steel-800 p-4 rounded-lg border-2 border-cool-steel-200 dark:border-cool-steel-700 w-72 h-72 flex items-center justify-center">
|
||||||
<p class="text-gray-500 dark:text-gray-400">Loading QR code...</p>
|
<p class="text-cool-steel-500 dark:text-cool-steel-400">Loading QR code...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: Enter Code -->
|
<!-- Step 3: Enter Code -->
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
<div class="border-t border-cool-steel-200 dark:border-cool-steel-700 pt-6">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white mb-4 flex items-center">
|
||||||
<span class="flex items-center justify-center w-8 h-8 bg-blue-600 text-white rounded-full mr-3 text-sm">
|
<span class="flex items-center justify-center w-8 h-8 bg-cerulean-600 text-white rounded-full mr-3 text-sm">
|
||||||
3
|
3
|
||||||
</span>
|
</span>
|
||||||
Enter the Code from Your App
|
Enter the Code from Your App
|
||||||
|
|
@ -327,7 +327,7 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="code"
|
for="code"
|
||||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 text-center"
|
class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2 text-center"
|
||||||
>
|
>
|
||||||
6-digit code
|
6-digit code
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -341,17 +341,17 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
placeholder="000000"
|
placeholder="000000"
|
||||||
required
|
required
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white shadow-sm focus:border-blue-500 focus:ring-blue-500 text-2xl text-center tracking-widest font-mono"
|
class="block w-full rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 text-2xl text-center tracking-widest font-mono"
|
||||||
phx-mounted={JS.focus()}
|
phx-mounted={JS.focus()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @error do %>
|
<%= if @error do %>
|
||||||
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
<div class="rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/30 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<.icon name="hero-exclamation-circle" class="h-5 w-5 text-red-400" />
|
<.icon name="hero-exclamation-circle" class="h-5 w-5 text-sweet-salmon-400" />
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm text-red-800 dark:text-red-200">
|
<p class="text-sm text-sweet-salmon-800 dark:text-sweet-salmon-200">
|
||||||
{@error}
|
{@error}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -369,14 +369,14 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
<div class="mt-8 p-4 bg-cerulean-50 dark:bg-cerulean-900/20 rounded-lg border border-cerulean-200 dark:border-cerulean-800">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-information-circle"
|
name="hero-information-circle"
|
||||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm text-blue-900 dark:text-blue-100">
|
<p class="text-sm text-cerulean-900 dark:text-cerulean-100">
|
||||||
<strong>Keep your device safe:</strong>
|
<strong>Keep your device safe:</strong>
|
||||||
You'll need to enter a code from your authenticator app each time you log in.
|
You'll need to enter a code from your authenticator app each time you log in.
|
||||||
Make sure to keep your phone secure and backed up.
|
Make sure to keep your phone secure and backed up.
|
||||||
|
|
|
||||||
|
|
@ -179,44 +179,50 @@ defmodule ToweropsWeb.ActivityFeedLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_active_class(:config_change), do: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
defp filter_active_class(:config_change), do: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
defp filter_active_class(:alert_fired), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
|
||||||
|
defp filter_active_class(:alert_fired),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
defp filter_active_class(:alert_resolved), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
defp filter_active_class(:alert_resolved), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
||||||
defp filter_active_class(:device_event), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
defp filter_active_class(:device_event), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
defp filter_active_class(:sync), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
defp filter_active_class(:sync), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
defp filter_active_class(:device_added), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
defp filter_active_class(:device_added), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
||||||
defp filter_active_class(_), do: "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
|
defp filter_active_class(_), do: "bg-cool-steel-200 text-cool-steel-700 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def severity_dot_color(:critical, _), do: "bg-red-500"
|
def severity_dot_color(:critical, _), do: "bg-sweet-salmon-500"
|
||||||
def severity_dot_color(:warning, _), do: "bg-yellow-500"
|
def severity_dot_color(:warning, _), do: "bg-wheat-500"
|
||||||
def severity_dot_color(:info, :alert_resolved), do: "bg-green-500"
|
def severity_dot_color(:info, :alert_resolved), do: "bg-green-500"
|
||||||
def severity_dot_color(:info, :sync), do: "bg-blue-500"
|
def severity_dot_color(:info, :sync), do: "bg-cerulean-500"
|
||||||
def severity_dot_color(:info, :device_added), do: "bg-cyan-500"
|
def severity_dot_color(:info, :device_added), do: "bg-cyan-500"
|
||||||
def severity_dot_color(:info, _), do: "bg-gray-400"
|
def severity_dot_color(:info, _), do: "bg-cool-steel-400"
|
||||||
def severity_dot_color(_, _), do: "bg-gray-400"
|
def severity_dot_color(_, _), do: "bg-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def severity_icon_color(:critical, _), do: "text-red-500 dark:text-red-400"
|
def severity_icon_color(:critical, _), do: "text-sweet-salmon-500 dark:text-sweet-salmon-400"
|
||||||
def severity_icon_color(:warning, _), do: "text-yellow-500 dark:text-yellow-400"
|
def severity_icon_color(:warning, _), do: "text-wheat-500 dark:text-wheat-400"
|
||||||
def severity_icon_color(:info, :alert_resolved), do: "text-green-500 dark:text-green-400"
|
def severity_icon_color(:info, :alert_resolved), do: "text-green-500 dark:text-green-400"
|
||||||
def severity_icon_color(:info, :sync), do: "text-blue-500 dark:text-blue-400"
|
def severity_icon_color(:info, :sync), do: "text-cerulean-500 dark:text-cerulean-400"
|
||||||
def severity_icon_color(:info, :device_added), do: "text-cyan-500 dark:text-cyan-400"
|
def severity_icon_color(:info, :device_added), do: "text-cyan-500 dark:text-cyan-400"
|
||||||
def severity_icon_color(_, _), do: "text-gray-400 dark:text-gray-500"
|
def severity_icon_color(_, _), do: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def severity_text_color(:critical, _), do: "text-red-700 dark:text-red-400"
|
def severity_text_color(:critical, _), do: "text-sweet-salmon-700 dark:text-sweet-salmon-400"
|
||||||
def severity_text_color(:warning, _), do: "text-yellow-700 dark:text-yellow-400"
|
def severity_text_color(:warning, _), do: "text-wheat-700 dark:text-wheat-400"
|
||||||
def severity_text_color(:info, :alert_resolved), do: "text-green-700 dark:text-green-400"
|
def severity_text_color(:info, :alert_resolved), do: "text-green-700 dark:text-green-400"
|
||||||
def severity_text_color(_, _), do: "text-gray-900 dark:text-white"
|
def severity_text_color(_, _), do: "text-cool-steel-900 dark:text-white"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def type_badge_class(:config_change), do: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
def type_badge_class(:config_change), do: "bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
def type_badge_class(:alert_fired), do: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
|
||||||
|
def type_badge_class(:alert_fired),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
def type_badge_class(:alert_resolved), do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
def type_badge_class(:alert_resolved), do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
||||||
def type_badge_class(:device_event), do: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
|
def type_badge_class(:device_event), do: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
def type_badge_class(:sync), do: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
def type_badge_class(:sync), do: "bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
def type_badge_class(:device_added), do: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400"
|
def type_badge_class(:device_added), do: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400"
|
||||||
def type_badge_class(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
def type_badge_class(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def type_label(:config_change), do: "Config"
|
def type_label(:config_change), do: "Config"
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-magnifying-glass"
|
name="hero-magnifying-glass"
|
||||||
class="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400 dark:text-gray-500"
|
class="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
:if={@search != ""}
|
:if={@search != ""}
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="clear_search"
|
phx-click="clear_search"
|
||||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-5 w-5" />
|
<.icon name="hero-x-mark" class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -48,7 +48,9 @@
|
||||||
|
|
||||||
<%!-- Filter Chips with Counts --%>
|
<%!-- Filter Chips with Counts --%>
|
||||||
<div class="mt-4 flex flex-wrap items-center gap-2">
|
<div class="mt-4 flex flex-wrap items-center gap-2">
|
||||||
<span class="text-sm font-medium text-gray-500 dark:text-gray-400 mr-1">Filter:</span>
|
<span class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 mr-1">
|
||||||
|
Filter:
|
||||||
|
</span>
|
||||||
<%= for {type, label, icon} <- filter_options() do %>
|
<%= for {type, label, icon} <- filter_options() do %>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -59,7 +61,7 @@
|
||||||
if(type in @active_types,
|
if(type in @active_types,
|
||||||
do: filter_active_class(type),
|
do: filter_active_class(type),
|
||||||
else:
|
else:
|
||||||
"bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-500 dark:bg-cool-steel-800 dark:text-cool-steel-500 hover:bg-cool-steel-200 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -71,7 +73,7 @@
|
||||||
"inline-flex items-center justify-center min-w-[18px] h-[18px] rounded-full px-1 text-[10px] font-bold",
|
"inline-flex items-center justify-center min-w-[18px] h-[18px] rounded-full px-1 text-[10px] font-bold",
|
||||||
if(type in @active_types,
|
if(type in @active_types,
|
||||||
do: "bg-white/30 dark:bg-black/20",
|
do: "bg-white/30 dark:bg-black/20",
|
||||||
else: "bg-gray-200 dark:bg-gray-700"
|
else: "bg-cool-steel-200 dark:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -84,18 +86,21 @@
|
||||||
<%!-- Timeline --%>
|
<%!-- Timeline --%>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<%= if @items == [] do %>
|
<%= if @items == [] do %>
|
||||||
<div class="rounded-xl border-2 border-dashed border-gray-200 bg-white p-16 text-center dark:border-gray-700 dark:bg-gray-800/50">
|
<div class="rounded-xl border-2 border-dashed border-cool-steel-200 bg-white p-16 text-center dark:border-cool-steel-700 dark:bg-cool-steel-800/50">
|
||||||
<div class="mx-auto w-16 h-16 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center mb-4">
|
<div class="mx-auto w-16 h-16 rounded-full bg-cool-steel-100 dark:bg-cool-steel-700 flex items-center justify-center mb-4">
|
||||||
<.icon name="hero-signal-slash" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
|
name="hero-signal-slash"
|
||||||
|
class="h-8 w-8 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<%= if @search != "" do %>
|
<%= if @search != "" do %>
|
||||||
No results for "{@search}"
|
No results for "{@search}"
|
||||||
<% else %>
|
<% else %>
|
||||||
{t("No activity yet")}
|
{t("No activity yet")}
|
||||||
<% end %>
|
<% end %>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400 max-w-md mx-auto">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400 max-w-md mx-auto">
|
||||||
<%= if @search != "" do %>
|
<%= if @search != "" do %>
|
||||||
{t("Try adjusting your search terms or clearing the filters.")}
|
{t("Try adjusting your search terms or clearing the filters.")}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -117,13 +122,13 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<%!-- Item count summary --%>
|
<%!-- Item count summary --%>
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Showing {length(@items)} events
|
Showing {length(@items)} events
|
||||||
<span :if={@search != ""}>
|
<span :if={@search != ""}>
|
||||||
matching "<strong class="text-gray-700 dark:text-gray-300">{@search}</strong>"
|
matching "<strong class="text-cool-steel-700 dark:text-cool-steel-300">{@search}</strong>"
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500 flex items-center gap-1">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500 flex items-center gap-1">
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -133,35 +138,35 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-cool-steel-700 overflow-hidden">
|
||||||
<table class="w-full text-sm border-collapse">
|
<table class="w-full text-sm border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/80">
|
<tr class="border-b border-cool-steel-200 dark:border-cool-steel-700 bg-cool-steel-50 dark:bg-cool-steel-800/80">
|
||||||
<th class="w-1 p-0"></th>
|
<th class="w-1 p-0"></th>
|
||||||
<th class="py-2 pl-3 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 whitespace-nowrap w-28">
|
<th class="py-2 pl-3 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 whitespace-nowrap w-28">
|
||||||
Time
|
Time
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 w-28">
|
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 w-28">
|
||||||
Type
|
Type
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Event
|
Event
|
||||||
</th>
|
</th>
|
||||||
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 w-48">
|
<th class="py-2 px-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 w-48">
|
||||||
Device / Site
|
Device / Site
|
||||||
</th>
|
</th>
|
||||||
<th class="w-8 p-0"></th>
|
<th class="w-8 p-0"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-100 dark:divide-gray-800">
|
<tbody class="bg-white dark:bg-cool-steel-900 divide-y divide-cool-steel-100 dark:divide-cool-steel-800">
|
||||||
<%= for {period, period_items} <- @grouped_items do %>
|
<%= for {period, period_items} <- @grouped_items do %>
|
||||||
<%!-- Period group header row --%>
|
<%!-- Period group header row --%>
|
||||||
<tr class="bg-gray-50/80 dark:bg-gray-800/40">
|
<tr class="bg-cool-steel-50/80 dark:bg-cool-steel-800/40">
|
||||||
<td colspan="6" class="px-4 py-1.5">
|
<td colspan="6" class="px-4 py-1.5">
|
||||||
<span class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<span class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{period}
|
{period}
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-1.5 text-xs text-gray-400 dark:text-gray-500">
|
<span class="ml-1.5 text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
({length(period_items)})
|
({length(period_items)})
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -171,8 +176,9 @@
|
||||||
<tr
|
<tr
|
||||||
id={item.id}
|
id={item.id}
|
||||||
class={[
|
class={[
|
||||||
"group transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50",
|
"group transition-colors hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/50",
|
||||||
item.severity == :critical && "bg-red-50/50 dark:bg-red-900/10"
|
item.severity == :critical &&
|
||||||
|
"bg-sweet-salmon-50/50 dark:bg-sweet-salmon-900/10"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<%!-- Severity bar --%>
|
<%!-- Severity bar --%>
|
||||||
|
|
@ -197,7 +203,7 @@
|
||||||
severity_icon_color(item.severity, item.type)
|
severity_icon_color(item.severity, item.type)
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 tabular-nums">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400 tabular-nums">
|
||||||
{relative_time(item.timestamp, @now)}
|
{relative_time(item.timestamp, @now)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -227,7 +233,7 @@
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
:if={item.detail && item.detail != ""}
|
:if={item.detail && item.detail != ""}
|
||||||
class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 leading-snug"
|
class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400 leading-snug"
|
||||||
>
|
>
|
||||||
{item.detail}
|
{item.detail}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -238,21 +244,21 @@
|
||||||
<.link
|
<.link
|
||||||
:if={item.device_name && item.link}
|
:if={item.device_name && item.link}
|
||||||
navigate={item.link}
|
navigate={item.link}
|
||||||
class="inline-flex items-center gap-1 text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 leading-snug"
|
class="inline-flex items-center gap-1 text-xs font-medium text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300 leading-snug"
|
||||||
>
|
>
|
||||||
<.icon name="hero-server-stack" class="h-3 w-3 flex-shrink-0" />
|
<.icon name="hero-server-stack" class="h-3 w-3 flex-shrink-0" />
|
||||||
{item.device_name}
|
{item.device_name}
|
||||||
</.link>
|
</.link>
|
||||||
<span
|
<span
|
||||||
:if={item.device_name && !item.link}
|
:if={item.device_name && !item.link}
|
||||||
class="inline-flex items-center gap-1 text-xs text-gray-600 dark:text-gray-300 leading-snug"
|
class="inline-flex items-center gap-1 text-xs text-cool-steel-600 dark:text-cool-steel-300 leading-snug"
|
||||||
>
|
>
|
||||||
<.icon name="hero-server-stack" class="h-3 w-3 flex-shrink-0" />
|
<.icon name="hero-server-stack" class="h-3 w-3 flex-shrink-0" />
|
||||||
{item.device_name}
|
{item.device_name}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
:if={item.site_name}
|
:if={item.site_name}
|
||||||
class="mt-0.5 inline-flex items-center gap-1 text-xs text-gray-400 dark:text-gray-500"
|
class="mt-0.5 inline-flex items-center gap-1 text-xs text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-map-pin" class="h-3 w-3 flex-shrink-0" />
|
<.icon name="hero-map-pin" class="h-3 w-3 flex-shrink-0" />
|
||||||
{item.site_name}
|
{item.site_name}
|
||||||
|
|
@ -267,7 +273,7 @@
|
||||||
>
|
>
|
||||||
<.link
|
<.link
|
||||||
navigate={item.link}
|
navigate={item.link}
|
||||||
class="text-gray-400 hover:text-blue-500 dark:text-gray-600 dark:hover:text-blue-400"
|
class="text-cool-steel-400 hover:text-cerulean-500 dark:text-cool-steel-600 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-right" class="h-4 w-4" />
|
<.icon name="hero-arrow-right" class="h-4 w-4" />
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("All Agents")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{t("All Agents")}</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Agents across all organizations and cloud pollers")}
|
{t("Agents across all organizations and cloud pollers")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<%!-- Cloud Pollers --%>
|
<%!-- Cloud Pollers --%>
|
||||||
<%= if @has_cloud_pollers do %>
|
<%= if @has_cloud_pollers do %>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-3">
|
||||||
<.icon name="hero-cloud" class="w-5 h-5 inline mr-1" /> Cloud Pollers
|
<.icon name="hero-cloud" class="w-5 h-5 inline mr-1" /> Cloud Pollers
|
||||||
</h2>
|
</h2>
|
||||||
<.table
|
<.table
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium">{agent.name}</span>
|
<span class="font-medium">{agent.name}</span>
|
||||||
<%= if agent.allow_remote_debug do %>
|
<%= if agent.allow_remote_debug do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20 dark:ring-amber-400/30">
|
<span class="inline-flex items-center gap-1 rounded-full bg-wheat-50 dark:bg-wheat-900/30 px-2 py-0.5 text-xs font-medium text-wheat-700 dark:text-wheat-300 ring-1 ring-inset ring-wheat-600/20 dark:ring-wheat-400/30">
|
||||||
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -39,10 +39,10 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Devices")}>
|
<:col :let={{_id, agent}} label={t("Devices")}>
|
||||||
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{counts.total} total
|
{counts.total} total
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{counts.direct} direct
|
{counts.direct} direct
|
||||||
<%= if counts.total > counts.direct do %>
|
<%= if counts.total > counts.direct do %>
|
||||||
· {counts.total - counts.direct} inherited
|
· {counts.total - counts.direct} inherited
|
||||||
|
|
@ -51,11 +51,11 @@
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
||||||
</div>
|
</div>
|
||||||
<%= if agent.last_seen_at do %>
|
<%= if agent.last_seen_at do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={agent.last_seen_at}
|
datetime={agent.last_seen_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Version")}>
|
<:col :let={{_id, agent}} label={t("Version")}>
|
||||||
<%= if agent.metadata["version"] do %>
|
<%= if agent.metadata["version"] do %>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400 font-mono">
|
||||||
{format_agent_version(agent.metadata["version"])}
|
{format_agent_version(agent.metadata["version"])}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
<%!-- Organization Agents --%>
|
<%!-- Organization Agents --%>
|
||||||
<%= if @has_org_agents do %>
|
<%= if @has_org_agents do %>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-3">
|
||||||
<.icon name="hero-server" class="w-5 h-5 inline mr-1" /> Organization Agents
|
<.icon name="hero-server" class="w-5 h-5 inline mr-1" /> Organization Agents
|
||||||
</h2>
|
</h2>
|
||||||
<.table
|
<.table
|
||||||
|
|
@ -92,12 +92,12 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium">{agent.name}</span>
|
<span class="font-medium">{agent.name}</span>
|
||||||
<%= if agent.allow_remote_debug do %>
|
<%= if agent.allow_remote_debug do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20 dark:ring-amber-400/30">
|
<span class="inline-flex items-center gap-1 rounded-full bg-wheat-50 dark:bg-wheat-900/30 px-2 py-0.5 text-xs font-medium text-wheat-700 dark:text-wheat-300 ring-1 ring-inset ring-wheat-600/20 dark:ring-wheat-400/30">
|
||||||
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{agent.organization.name}
|
{agent.organization.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -113,10 +113,10 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Devices")}>
|
<:col :let={{_id, agent}} label={t("Devices")}>
|
||||||
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{counts.total} total
|
{counts.total} total
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{counts.direct} direct
|
{counts.direct} direct
|
||||||
<%= if counts.total > counts.direct do %>
|
<%= if counts.total > counts.direct do %>
|
||||||
· {counts.total - counts.direct} inherited
|
· {counts.total - counts.direct} inherited
|
||||||
|
|
@ -125,11 +125,11 @@
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
||||||
</div>
|
</div>
|
||||||
<%= if agent.last_seen_at do %>
|
<%= if agent.last_seen_at do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={agent.last_seen_at}
|
datetime={agent.last_seen_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
|
|
@ -141,7 +141,7 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Version")}>
|
<:col :let={{_id, agent}} label={t("Version")}>
|
||||||
<%= if agent.metadata["version"] do %>
|
<%= if agent.metadata["version"] do %>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400 font-mono">
|
||||||
{format_agent_version(agent.metadata["version"])}
|
{format_agent_version(agent.metadata["version"])}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -81,21 +81,24 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<Layouts.admin flash={@flash} timezone={@current_scope.user.timezone}>
|
<Layouts.admin flash={@flash} timezone={@current_scope.user.timezone}>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
Audit Logs
|
Audit Logs
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
View and search all system audit logs. Records are retained for 3 years.
|
View and search all system audit logs. Records are retained for 3 years.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Filters -->
|
<!-- Search Filters -->
|
||||||
<div class="mt-8 bg-white dark:bg-gray-900 shadow rounded-lg p-6">
|
<div class="mt-8 bg-white dark:bg-cool-steel-900 shadow rounded-lg p-6">
|
||||||
<form id="audit-search-form" phx-change="search" phx-submit="search" class="space-y-4">
|
<form id="audit-search-form" phx-change="search" phx-submit="search" class="space-y-4">
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="email"
|
||||||
|
class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
User Email
|
User Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -104,18 +107,21 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
id="email"
|
id="email"
|
||||||
value={@search_email}
|
value={@search_email}
|
||||||
placeholder="user@example.com"
|
placeholder="user@example.com"
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-800 dark:border-gray-600 dark:text-white"
|
class="mt-1 block w-full rounded-md border-cool-steel-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 sm:text-sm dark:bg-cool-steel-800 dark:border-cool-steel-600 dark:text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="action" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="action"
|
||||||
|
class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
Action
|
Action
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="action"
|
name="action"
|
||||||
id="action"
|
id="action"
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-800 dark:border-gray-600 dark:text-white"
|
class="mt-1 block w-full rounded-md border-cool-steel-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 sm:text-sm dark:bg-cool-steel-800 dark:border-cool-steel-600 dark:text-white"
|
||||||
>
|
>
|
||||||
<option value="">All Actions</option>
|
<option value="">All Actions</option>
|
||||||
<%= for action <- available_actions() do %>
|
<%= for action <- available_actions() do %>
|
||||||
|
|
@ -129,7 +135,7 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="date_from"
|
for="date_from"
|
||||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
From Date
|
From Date
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -138,12 +144,15 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
name="date_from"
|
name="date_from"
|
||||||
id="date_from"
|
id="date_from"
|
||||||
value={@date_from}
|
value={@date_from}
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-800 dark:border-gray-600 dark:text-white"
|
class="mt-1 block w-full rounded-md border-cool-steel-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 sm:text-sm dark:bg-cool-steel-800 dark:border-cool-steel-600 dark:text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="date_to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label
|
||||||
|
for="date_to"
|
||||||
|
class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
To Date
|
To Date
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -151,7 +160,7 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
name="date_to"
|
name="date_to"
|
||||||
id="date_to"
|
id="date_to"
|
||||||
value={@date_to}
|
value={@date_to}
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-800 dark:border-gray-600 dark:text-white"
|
class="mt-1 block w-full rounded-md border-cool-steel-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 sm:text-sm dark:bg-cool-steel-800 dark:border-cool-steel-600 dark:text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -159,7 +168,7 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-cerulean-600 hover:bg-cerulean-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-magnifying-glass" class="h-4 w-4 mr-2" /> Search
|
<.icon name="hero-magnifying-glass" class="h-4 w-4 mr-2" /> Search
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -167,14 +176,14 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
id="audit-clear-filters"
|
id="audit-clear-filters"
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="clear_filters"
|
phx-click="clear_filters"
|
||||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4 mr-2" /> Clear
|
<.icon name="hero-x-mark" class="h-4 w-4 mr-2" /> Clear
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="export"
|
phx-click="export"
|
||||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
class="inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-down-tray" class="h-4 w-4 mr-2" /> Export CSV
|
<.icon name="hero-arrow-down-tray" class="h-4 w-4 mr-2" /> Export CSV
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -185,45 +194,45 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<%= if Enum.empty?(@audit_logs) do %>
|
<%= if Enum.empty?(@audit_logs) do %>
|
||||||
<div class="text-center py-12 bg-white dark:bg-gray-900 rounded-lg shadow">
|
<div class="text-center py-12 bg-white dark:bg-cool-steel-900 rounded-lg shadow">
|
||||||
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-gray-400" />
|
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-cool-steel-400" />
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
No audit logs found
|
No audit logs found
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Try adjusting your search filters.
|
Try adjusting your search filters.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-900 shadow rounded-lg overflow-hidden">
|
<div class="bg-white dark:bg-cool-steel-900 shadow rounded-lg overflow-hidden">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Timestamp
|
Timestamp
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Action
|
Action
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Actor
|
Actor
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Target User
|
Target User
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
IP Address
|
IP Address
|
||||||
</th>
|
</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<th class="px-6 py-3 text-left text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
Details
|
Details
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<%= for log <- @audit_logs do %>
|
<%= for log <- @audit_logs do %>
|
||||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
<tr class="hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_datetime(
|
{ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
log.inserted_at,
|
log.inserted_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -238,37 +247,37 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
{format_action(log.action)}
|
{format_action(log.action)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if log.superuser do %>
|
<%= if log.superuser do %>
|
||||||
{log.superuser.email}
|
{log.superuser.email}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400">System</span>
|
<span class="text-cool-steel-400">System</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if log.target_user do %>
|
<%= if log.target_user do %>
|
||||||
{log.target_user.email}
|
{log.target_user.email}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400">-</span>
|
<span class="text-cool-steel-400">-</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{log.ip_address || "-"}
|
{log.ip_address || "-"}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
<td class="px-6 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if log.request_path do %>
|
<%= if log.request_path do %>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
<code class="bg-gray-100 dark:bg-gray-800 px-1 rounded">
|
<code class="bg-cool-steel-100 dark:bg-cool-steel-800 px-1 rounded">
|
||||||
{log.request_path}
|
{log.request_path}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if log.metadata && map_size(log.metadata) > 0 do %>
|
<%= if log.metadata && map_size(log.metadata) > 0 do %>
|
||||||
<details class="mt-1">
|
<details class="mt-1">
|
||||||
<summary class="cursor-pointer text-xs text-blue-600 dark:text-blue-400 hover:underline">
|
<summary class="cursor-pointer text-xs text-cerulean-600 dark:text-cerulean-400 hover:underline">
|
||||||
Metadata
|
Metadata
|
||||||
</summary>
|
</summary>
|
||||||
<pre class="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto max-w-md">
|
<pre class="mt-1 text-xs bg-cool-steel-100 dark:bg-cool-steel-800 p-2 rounded overflow-x-auto max-w-md">
|
||||||
{Jason.encode!(log.metadata, pretty: true)}
|
{Jason.encode!(log.metadata, pretty: true)}
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|
@ -281,8 +290,8 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 px-6 py-4 flex items-center justify-between border-t border-gray-200 dark:border-gray-700">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 px-6 py-4 flex items-center justify-between border-t border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
<div class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Page {@page} • Showing {length(@audit_logs)} records
|
Page {@page} • Showing {length(@audit_logs)} records
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|
@ -291,7 +300,7 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="prev_page"
|
phx-click="prev_page"
|
||||||
disabled={@page == 1}
|
disabled={@page == 1}
|
||||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
class="relative inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -300,7 +309,7 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="next_page"
|
phx-click="next_page"
|
||||||
disabled={length(@audit_logs) < @per_page}
|
disabled={length(@audit_logs) < @per_page}
|
||||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
class="relative inline-flex items-center px-4 py-2 border border-cool-steel-300 dark:border-cool-steel-600 text-sm font-medium rounded-md text-cool-steel-700 dark:text-cool-steel-300 bg-white dark:bg-cool-steel-800 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -405,12 +414,23 @@ defmodule ToweropsWeb.Admin.AuditLive.Index do
|
||||||
|
|
||||||
defp audit_badge_class_for_category(category) do
|
defp audit_badge_class_for_category(category) do
|
||||||
case category do
|
case category do
|
||||||
:success -> "bg-green-50 text-green-700 ring-green-600/20"
|
:success ->
|
||||||
:warning -> "bg-yellow-50 text-yellow-700 ring-yellow-600/20"
|
"bg-green-50 text-green-700 ring-green-600/20"
|
||||||
:danger -> "bg-red-50 text-red-700 ring-red-600/20"
|
|
||||||
:impersonation -> "bg-orange-50 text-orange-700 ring-orange-600/20"
|
:warning ->
|
||||||
:info -> "bg-blue-50 text-blue-700 ring-blue-600/20"
|
"bg-wheat-50 text-wheat-700 ring-wheat-600/20"
|
||||||
:default -> "bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 ring-gray-600/20"
|
|
||||||
|
:danger ->
|
||||||
|
"bg-sweet-salmon-50 text-sweet-salmon-700 ring-sweet-salmon-600/20"
|
||||||
|
|
||||||
|
:impersonation ->
|
||||||
|
"bg-orange-50 text-orange-700 ring-orange-600/20"
|
||||||
|
|
||||||
|
:info ->
|
||||||
|
"bg-cerulean-50 text-cerulean-700 ring-cerulean-600/20"
|
||||||
|
|
||||||
|
:default ->
|
||||||
|
"bg-cool-steel-50 dark:bg-cool-steel-800 text-cool-steel-700 dark:text-cool-steel-300 ring-cool-steel-600/20"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,119 +1,123 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("Dashboard")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{t("Dashboard")}</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">System overview and recent activity</p>
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
System overview and recent activity
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">{t("Users")}</h2>
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white">{@user_count}</p>
|
{t("Users")}
|
||||||
|
</h2>
|
||||||
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white">{@user_count}</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/users"}
|
navigate={~p"/admin/users"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
View all users →
|
View all users →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Organizations")}
|
{t("Organizations")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white">{@org_count}</p>
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white">{@org_count}</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/organizations"}
|
navigate={~p"/admin/organizations"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
View all organizations →
|
View all organizations →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<.icon name="hero-queue-list" class="w-5 h-5 inline mr-1" /> Oban Dashboard
|
<.icon name="hero-queue-list" class="w-5 h-5 inline mr-1" /> Oban Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Monitor background jobs and queues")}
|
{t("Monitor background jobs and queues")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/oban"}
|
navigate={~p"/admin/oban"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
Open Oban →
|
Open Oban →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<.icon name="hero-chart-bar" class="w-5 h-5 inline mr-1" /> LiveDashboard
|
<.icon name="hero-chart-bar" class="w-5 h-5 inline mr-1" /> LiveDashboard
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("System metrics and performance")}
|
{t("System metrics and performance")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/dashboard"}
|
navigate={~p"/admin/dashboard"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
Open Dashboard →
|
Open Dashboard →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<.icon name="hero-bug-ant" class="w-5 h-5 inline mr-1" /> Exceptions
|
<.icon name="hero-bug-ant" class="w-5 h-5 inline mr-1" /> Exceptions
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Tracked errors and exceptions")}
|
{t("Tracked errors and exceptions")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/errors"}
|
navigate={~p"/admin/errors"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
Open Error Tracker →
|
Open Error Tracker →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<.icon name="hero-cpu-chip" class="w-5 h-5 inline mr-1" /> Job Monitoring
|
<.icon name="hero-cpu-chip" class="w-5 h-5 inline mr-1" /> Job Monitoring
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Real-time polling and discovery job monitoring")}
|
{t("Real-time polling and discovery job monitoring")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/monitoring"}
|
navigate={~p"/admin/monitoring"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
Open Monitoring →
|
Open Monitoring →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
<.icon name="hero-server" class="w-5 h-5 inline mr-1" /> All Agents
|
<.icon name="hero-server" class="w-5 h-5 inline mr-1" /> All Agents
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Agents across all organizations and cloud pollers")}
|
{t("Agents across all organizations and cloud pollers")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/agents"}
|
navigate={~p"/admin/agents"}
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
class="text-cerulean-600 dark:text-cerulean-400 hover:underline text-sm"
|
||||||
>
|
>
|
||||||
View all agents →
|
View all agents →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 flex items-center justify-between">
|
<div class="px-4 py-3 border-b border-cool-steel-200 dark:border-white/10 flex items-center justify-between">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Recent Audit Logs")}
|
{t("Recent Audit Logs")}
|
||||||
</h2>
|
</h2>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/admin/audit"}
|
navigate={~p"/admin/audit"}
|
||||||
class="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-sm text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
View all →
|
View all →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,9 @@ defmodule ToweropsWeb.Admin.MonitoringLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp event_border_color("completed"), do: "border-green-500"
|
defp event_border_color("completed"), do: "border-green-500"
|
||||||
defp event_border_color("cancelled"), do: "border-red-500"
|
defp event_border_color("cancelled"), do: "border-sweet-salmon-500"
|
||||||
defp event_border_color("discarded"), do: "border-gray-400"
|
defp event_border_color("discarded"), do: "border-cool-steel-400"
|
||||||
defp event_border_color(_), do: "border-gray-300 dark:border-gray-600"
|
defp event_border_color(_), do: "border-cool-steel-300 dark:border-cool-steel-600"
|
||||||
|
|
||||||
defp event_outcome(job) do
|
defp event_outcome(job) do
|
||||||
case job.state do
|
case job.state do
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,93 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{t("Job Monitoring Dashboard")}
|
{t("Job Monitoring Dashboard")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Real-time monitoring of polling and discovery jobs")}
|
{t("Real-time monitoring of polling and discovery jobs")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Health Metrics Cards -->
|
<!-- Health Metrics Cards -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Completed (1h)</h3>
|
<h3 class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
Completed (1h)
|
||||||
|
</h3>
|
||||||
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white mt-2">
|
||||||
{Map.get(@health_metrics, :completed_last_hour, 0)}
|
{Map.get(@health_metrics, :completed_last_hour, 0)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">jobs completed</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-500 mt-1">jobs completed</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">Failed (1h)</h3>
|
<h3 class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
Failed (1h)
|
||||||
|
</h3>
|
||||||
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white mt-2">
|
||||||
{Map.get(@health_metrics, :failed_last_hour, 0)}
|
{Map.get(@health_metrics, :failed_last_hour, 0)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">failures</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-500 mt-1">failures</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">{t("Avg Duration")}</h3>
|
<h3 class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
{t("Avg Duration")}
|
||||||
|
</h3>
|
||||||
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white mt-2">
|
||||||
<%= if Map.get(@health_metrics, :avg_execution_time_seconds) do %>
|
<%= if Map.get(@health_metrics, :avg_execution_time_seconds) do %>
|
||||||
{Map.get(@health_metrics, :avg_execution_time_seconds) |> Float.round(1)}s
|
{Map.get(@health_metrics, :avg_execution_time_seconds) |> Float.round(1)}s
|
||||||
<% else %>
|
<% else %>
|
||||||
{t("N/A")}
|
{t("N/A")}
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">last hour</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-500 mt-1">last hour</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h3 class="text-sm font-medium text-gray-600 dark:text-gray-400">{t("Active Jobs")}</h3>
|
<h3 class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">
|
{t("Active Jobs")}
|
||||||
|
</h3>
|
||||||
|
<p class="text-3xl font-bold text-cool-steel-900 dark:text-white mt-2">
|
||||||
{length(@active_jobs)}
|
{length(@active_jobs)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">executing now</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-500 mt-1">executing now</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active Operations -->
|
<!-- Active Operations -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
Active Operations ({length(@executing_jobs)})
|
Active Operations ({length(@executing_jobs)})
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<%= if length(@executing_jobs) == 0 do %>
|
<%= if length(@executing_jobs) == 0 do %>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">No jobs currently executing</p>
|
<p class="text-cool-steel-600 dark:text-cool-steel-400 text-sm">
|
||||||
|
No jobs currently executing
|
||||||
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<%= for job <- @executing_jobs do %>
|
<%= for job <- @executing_jobs do %>
|
||||||
<div class="border border-gray-200 dark:border-gray-700 rounded p-3">
|
<div class="border border-cool-steel-200 dark:border-cool-steel-700 rounded p-3">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-medium text-gray-900 dark:text-white">
|
<p class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if job.device do %>
|
<%= if job.device do %>
|
||||||
{job.device.name}
|
{job.device.name}
|
||||||
<% else %>
|
<% else %>
|
||||||
Device #{get_in(job.args, ["device_id"])}
|
Device #{get_in(job.args, ["device_id"])}
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{worker_name(job.worker)}
|
{worker_name(job.worker)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-500 mt-1">
|
||||||
Started {ToweropsWeb.TimeHelpers.format_time_ago(job.attempted_at)}
|
Started {ToweropsWeb.TimeHelpers.format_time_ago(job.attempted_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<.icon name="hero-arrow-path" class="w-4 h-4 animate-spin text-blue-600" />
|
<.icon name="hero-arrow-path" class="w-4 h-4 animate-spin text-cerulean-600" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -88,32 +98,32 @@
|
||||||
|
|
||||||
<!-- Problems -->
|
<!-- Problems -->
|
||||||
<%= if length(@stuck_jobs) > 0 or length(@failed_jobs) > 0 do %>
|
<%= if length(@stuck_jobs) > 0 or length(@failed_jobs) > 0 do %>
|
||||||
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
<div class="bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 border border-sweet-salmon-200 dark:border-sweet-salmon-800 rounded-lg p-4">
|
||||||
<h2 class="text-lg font-semibold text-red-900 dark:text-red-100 mb-4">
|
<h2 class="text-lg font-semibold text-sweet-salmon-900 dark:text-sweet-salmon-100 mb-4">
|
||||||
⚠️ Problems Detected
|
⚠️ Problems Detected
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<%= if length(@stuck_jobs) > 0 do %>
|
<%= if length(@stuck_jobs) > 0 do %>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h3 class="font-medium text-red-800 dark:text-red-200 mb-2">
|
<h3 class="font-medium text-sweet-salmon-800 dark:text-sweet-salmon-200 mb-2">
|
||||||
Stuck Jobs ({length(@stuck_jobs)})
|
Stuck Jobs ({length(@stuck_jobs)})
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= for job <- @stuck_jobs do %>
|
<%= for job <- @stuck_jobs do %>
|
||||||
<div class="bg-white dark:bg-gray-800 rounded p-3 border border-red-300 dark:border-red-700">
|
<div class="bg-white dark:bg-cool-steel-800 rounded p-3 border border-sweet-salmon-300 dark:border-sweet-salmon-700">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-gray-900 dark:text-white">
|
<p class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if job.device do %>
|
<%= if job.device do %>
|
||||||
{job.device.name}
|
{job.device.name}
|
||||||
<% else %>
|
<% else %>
|
||||||
Device #{get_in(job.args, ["device_id"])}
|
Device #{get_in(job.args, ["device_id"])}
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{worker_name(job.worker)}
|
{worker_name(job.worker)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-red-600 dark:text-red-400 mt-1">
|
<p class="text-xs text-sweet-salmon-600 dark:text-sweet-salmon-400 mt-1">
|
||||||
Running for {duration_in_words(job.attempted_at)}
|
Running for {duration_in_words(job.attempted_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -126,26 +136,26 @@
|
||||||
|
|
||||||
<%= if length(@failed_jobs) > 0 do %>
|
<%= if length(@failed_jobs) > 0 do %>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-medium text-red-800 dark:text-red-200 mb-2">
|
<h3 class="font-medium text-sweet-salmon-800 dark:text-sweet-salmon-200 mb-2">
|
||||||
Failed Jobs ({length(@failed_jobs)})
|
Failed Jobs ({length(@failed_jobs)})
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= for job <- Enum.take(@failed_jobs, 5) do %>
|
<%= for job <- Enum.take(@failed_jobs, 5) do %>
|
||||||
<div class="bg-white dark:bg-gray-800 rounded p-3 border border-red-300 dark:border-red-700">
|
<div class="bg-white dark:bg-cool-steel-800 rounded p-3 border border-sweet-salmon-300 dark:border-sweet-salmon-700">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-gray-900 dark:text-white">
|
<p class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if job.device do %>
|
<%= if job.device do %>
|
||||||
{job.device.name}
|
{job.device.name}
|
||||||
<% else %>
|
<% else %>
|
||||||
Device #{get_in(job.args, ["device_id"])}
|
Device #{get_in(job.args, ["device_id"])}
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{worker_name(job.worker)} - Attempt {job.attempt}/{job.max_attempts}
|
{worker_name(job.worker)} - Attempt {job.attempt}/{job.max_attempts}
|
||||||
</p>
|
</p>
|
||||||
<%= if job.errors && length(job.errors) > 0 do %>
|
<%= if job.errors && length(job.errors) > 0 do %>
|
||||||
<p class="text-xs text-red-600 dark:text-red-400 mt-1">
|
<p class="text-xs text-sweet-salmon-600 dark:text-sweet-salmon-400 mt-1">
|
||||||
{hd(job.errors)["error"] || "Unknown error"}
|
{hd(job.errors)["error"] || "Unknown error"}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -160,38 +170,38 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Health Metrics -->
|
<!-- Health Metrics -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Health Metrics")}
|
{t("Health Metrics")}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Current Activity -->
|
<!-- Current Activity -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
<h3 class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-3">
|
||||||
{t("Current Activity")}
|
{t("Current Activity")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 rounded p-3">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 rounded p-3">
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400">Executing</p>
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400">Executing</p>
|
||||||
<p class="text-2xl font-bold text-gray-900 dark:text-white">
|
<p class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@metrics.executing_count}
|
{@metrics.executing_count}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 rounded p-3">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 rounded p-3">
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400">Queued</p>
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400">Queued</p>
|
||||||
<p class="text-2xl font-bold text-gray-900 dark:text-white">
|
<p class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@metrics.queued_count}
|
{@metrics.queued_count}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 rounded p-3">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 rounded p-3">
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400">Completed (1h)</p>
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400">Completed (1h)</p>
|
||||||
<p class="text-2xl font-bold text-green-600 dark:text-green-400">
|
<p class="text-2xl font-bold text-green-600 dark:text-green-400">
|
||||||
{@metrics.completed_last_hour}
|
{@metrics.completed_last_hour}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 rounded p-3">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 rounded p-3">
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400">Failed (1h)</p>
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400">Failed (1h)</p>
|
||||||
<p class="text-2xl font-bold text-red-600 dark:text-red-400">
|
<p class="text-2xl font-bold text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{@metrics.failed_last_hour}
|
{@metrics.failed_last_hour}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -200,18 +210,18 @@
|
||||||
|
|
||||||
<!-- Success Rates -->
|
<!-- Success Rates -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
<h3 class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-3">
|
||||||
{t("Success Rates")}
|
{t("Success Rates")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between text-xs mb-1">
|
<div class="flex justify-between text-xs mb-1">
|
||||||
<span class="text-gray-600 dark:text-gray-400">Polling (1h)</span>
|
<span class="text-cool-steel-600 dark:text-cool-steel-400">Polling (1h)</span>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{Float.round(@metrics.polling_success_rate_1h * 100, 1)}%
|
{Float.round(@metrics.polling_success_rate_1h * 100, 1)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
<div class="w-full bg-cool-steel-200 dark:bg-cool-steel-700 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
class="bg-green-600 h-2 rounded-full transition-all"
|
class="bg-green-600 h-2 rounded-full transition-all"
|
||||||
style={"width: #{@metrics.polling_success_rate_1h * 100}%"}
|
style={"width: #{@metrics.polling_success_rate_1h * 100}%"}
|
||||||
|
|
@ -222,14 +232,14 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between text-xs mb-1">
|
<div class="flex justify-between text-xs mb-1">
|
||||||
<span class="text-gray-600 dark:text-gray-400">Discovery (1h)</span>
|
<span class="text-cool-steel-600 dark:text-cool-steel-400">Discovery (1h)</span>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{Float.round(@metrics.discovery_success_rate_1h * 100, 1)}%
|
{Float.round(@metrics.discovery_success_rate_1h * 100, 1)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
<div class="w-full bg-cool-steel-200 dark:bg-cool-steel-700 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
class="bg-blue-600 h-2 rounded-full transition-all"
|
class="bg-cerulean-600 h-2 rounded-full transition-all"
|
||||||
style={"width: #{@metrics.discovery_success_rate_1h * 100}%"}
|
style={"width: #{@metrics.discovery_success_rate_1h * 100}%"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -240,13 +250,13 @@
|
||||||
|
|
||||||
<!-- Performance -->
|
<!-- Performance -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
<h3 class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-3">
|
||||||
{t("Performance")}
|
{t("Performance")}
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="space-y-2 text-sm">
|
<dl class="space-y-2 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Avg Execution Time</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">Avg Execution Time</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if @metrics.avg_execution_time_seconds do %>
|
<%= if @metrics.avg_execution_time_seconds do %>
|
||||||
{Float.round(@metrics.avg_execution_time_seconds, 1)}s
|
{Float.round(@metrics.avg_execution_time_seconds, 1)}s
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -255,8 +265,8 @@
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Jobs/Hour</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">Jobs/Hour</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@metrics.completed_last_hour}
|
{@metrics.completed_last_hour}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -265,58 +275,58 @@
|
||||||
|
|
||||||
<!-- Queue Depths -->
|
<!-- Queue Depths -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
<h3 class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-3">
|
||||||
{t("Queue Depths")}
|
{t("Queue Depths")}
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="space-y-2 text-sm">
|
<dl class="space-y-2 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Pollers</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">Pollers</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@metrics.queue_depths.pollers}
|
{@metrics.queue_depths.pollers}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">Discovery</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">Discovery</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@metrics.queue_depths.discovery}
|
{@metrics.queue_depths.discovery}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div class="mt-4 pt-4 border-t border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-500">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-500">
|
||||||
{t("Updates every 10 seconds")}
|
{t("Updates every 10 seconds")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent Activity -->
|
<!-- Recent Activity -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Recent Activity")}
|
{t("Recent Activity")}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<%= if length(@recent_events) == 0 do %>
|
<%= if length(@recent_events) == 0 do %>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">No recent activity</p>
|
<p class="text-cool-steel-600 dark:text-cool-steel-400 text-sm">No recent activity</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-2 max-h-96 overflow-y-auto">
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||||
<%= for job <- Enum.take(@recent_events, 50) do %>
|
<%= for job <- Enum.take(@recent_events, 50) do %>
|
||||||
<div class={"text-sm border-l-2 pl-3 py-2 #{event_border_color(job.state)}"}>
|
<div class={"text-sm border-l-2 pl-3 py-2 #{event_border_color(job.state)}"}>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-medium text-gray-900 dark:text-white">
|
<p class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if job.device do %>
|
<%= if job.device do %>
|
||||||
{job.device.name}
|
{job.device.name}
|
||||||
<% else %>
|
<% else %>
|
||||||
Device #{get_in(job.args, ["device_id"])}
|
Device #{get_in(job.args, ["device_id"])}
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{worker_name(job.worker)} · {event_outcome(job)}
|
{worker_name(job.worker)} · {event_outcome(job)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-500 whitespace-nowrap ml-2">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-500 whitespace-nowrap ml-2">
|
||||||
{ToweropsWeb.TimeHelpers.format_time_ago(
|
{ToweropsWeb.TimeHelpers.format_time_ago(
|
||||||
job.completed_at || job.cancelled_at || job.discarded_at || job.inserted_at
|
job.completed_at || job.cancelled_at || job.discarded_at || job.inserted_at
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("All Organizations")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-gray-600 dark:text-gray-400">{t_admin("Manage organizations")}</p>
|
{t("All Organizations")}
|
||||||
|
</h1>
|
||||||
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t_admin("Manage organizations")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Global Billing Defaults Card -->
|
<!-- Global Billing Defaults Card -->
|
||||||
|
|
@ -23,7 +27,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="edit_global_defaults"
|
phx-click="edit_global_defaults"
|
||||||
data-test="edit-global-defaults"
|
data-test="edit-global-defaults"
|
||||||
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white hover:bg-cerulean-500"
|
||||||
>
|
>
|
||||||
Edit Defaults
|
Edit Defaults
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -53,7 +57,7 @@
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<.table id="organizations" rows={@organizations}>
|
<.table id="organizations" rows={@organizations}>
|
||||||
<:col :let={org} label={t("Name")}>{org.name}</:col>
|
<:col :let={org} label={t("Name")}>{org.name}</:col>
|
||||||
<:col :let={org} label={t("Slug")}>{org.slug}</:col>
|
<:col :let={org} label={t("Slug")}>{org.slug}</:col>
|
||||||
|
|
@ -62,7 +66,8 @@
|
||||||
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
||||||
if(org.subscription_plan == "paid",
|
if(org.subscription_plan == "paid",
|
||||||
do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
|
do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
|
||||||
else: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
|
else:
|
||||||
|
"bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{org.subscription_plan}
|
{org.subscription_plan}
|
||||||
|
|
@ -75,7 +80,7 @@
|
||||||
<%= if org.custom_free_device_limit || org.custom_price_per_device do %>
|
<%= if org.custom_free_device_limit || org.custom_price_per_device do %>
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
<%= if org.custom_free_device_limit do %>
|
<%= if org.custom_free_device_limit do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">
|
<span class="inline-flex items-center rounded-full bg-cerulean-100 px-2 py-0.5 text-xs font-medium text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400">
|
||||||
{org.custom_free_device_limit} {t("free")}
|
{org.custom_free_device_limit} {t("free")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -86,7 +91,9 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-500 text-xs">{t("Default")}</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 text-xs">
|
||||||
|
{t("Default")}
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={org} label={t("Members")}>{length(org.memberships)}</:col>
|
<:col :let={org} label={t("Members")}>{length(org.memberships)}</:col>
|
||||||
|
|
@ -122,15 +129,15 @@
|
||||||
|
|
||||||
<%!-- Billing Override Edit Panel --%>
|
<%!-- Billing Override Edit Panel --%>
|
||||||
<%= if @editing_org do %>
|
<%= if @editing_org do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t_admin("Billing Overrides")} — {@editing_org.name}
|
{t_admin("Billing Overrides")} — {@editing_org.name}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
id="cancel-overrides-btn"
|
id="cancel-overrides-btn"
|
||||||
phx-click="cancel_overrides"
|
phx-click="cancel_overrides"
|
||||||
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -170,7 +177,7 @@
|
||||||
id="clear-overrides-btn"
|
id="clear-overrides-btn"
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="clear_overrides"
|
phx-click="clear_overrides"
|
||||||
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t_admin("Reset to Defaults")}
|
{t_admin("Reset to Defaults")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -212,12 +219,12 @@
|
||||||
type="number"
|
type="number"
|
||||||
name="global_pricing[default_free_devices]"
|
name="global_pricing[default_free_devices]"
|
||||||
value={@global_form.data["default_free_devices"]}
|
value={@global_form.data["default_free_devices"]}
|
||||||
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
|
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 dark:border-slate-600 dark:bg-slate-700"
|
||||||
/>
|
/>
|
||||||
<%= if error = @global_form.errors[:default_free_devices] do %>
|
<%= if error = @global_form.errors[:default_free_devices] do %>
|
||||||
<p
|
<p
|
||||||
id="global_pricing_default_free_devices-error"
|
id="global_pricing_default_free_devices-error"
|
||||||
class="mt-1 text-sm text-red-600"
|
class="mt-1 text-sm text-sweet-salmon-600"
|
||||||
>
|
>
|
||||||
{elem(error, 0)}
|
{elem(error, 0)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -233,12 +240,12 @@
|
||||||
step="0.01"
|
step="0.01"
|
||||||
name="global_pricing[default_price_per_device]"
|
name="global_pricing[default_price_per_device]"
|
||||||
value={@global_form.data["default_price_per_device"]}
|
value={@global_form.data["default_price_per_device"]}
|
||||||
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
|
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 dark:border-slate-600 dark:bg-slate-700"
|
||||||
/>
|
/>
|
||||||
<%= if error = @global_form.errors[:default_price_per_device] do %>
|
<%= if error = @global_form.errors[:default_price_per_device] do %>
|
||||||
<p
|
<p
|
||||||
id="global_pricing_default_price_per_device-error"
|
id="global_pricing_default_price_per_device-error"
|
||||||
class="mt-1 text-sm text-red-600"
|
class="mt-1 text-sm text-sweet-salmon-600"
|
||||||
>
|
>
|
||||||
{elem(error, 0)}
|
{elem(error, 0)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -256,7 +263,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white hover:bg-cerulean-500"
|
||||||
>
|
>
|
||||||
Save Changes
|
Save Changes
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -289,7 +296,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="confirm_pricing_update"
|
phx-click="confirm_pricing_update"
|
||||||
data-test="confirm-pricing-update"
|
data-test="confirm-pricing-update"
|
||||||
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white hover:bg-cerulean-500"
|
||||||
>
|
>
|
||||||
Confirm Update
|
Confirm Update
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("IP Access Control")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
{t("IP Access Control")}
|
||||||
|
</h1>
|
||||||
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Manage IP allowlist and view denied IPs")}
|
{t("Manage IP allowlist and view denied IPs")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="border-b border-gray-200 dark:border-white/10">
|
<div class="border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="-mb-px flex space-x-8">
|
<nav class="-mb-px flex space-x-8">
|
||||||
<button
|
<button
|
||||||
id="security-tab-whitelist"
|
id="security-tab-whitelist"
|
||||||
|
|
@ -17,9 +19,9 @@
|
||||||
class={[
|
class={[
|
||||||
"border-b-2 py-4 px-1 text-sm font-medium",
|
"border-b-2 py-4 px-1 text-sm font-medium",
|
||||||
if(@current_tab == "whitelist",
|
if(@current_tab == "whitelist",
|
||||||
do: "border-blue-500 text-blue-600 dark:text-blue-400",
|
do: "border-cerulean-500 text-cerulean-600 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -33,9 +35,9 @@
|
||||||
class={[
|
class={[
|
||||||
"border-b-2 py-4 px-1 text-sm font-medium",
|
"border-b-2 py-4 px-1 text-sm font-medium",
|
||||||
if(@current_tab == "blocked",
|
if(@current_tab == "blocked",
|
||||||
do: "border-blue-500 text-blue-600 dark:text-blue-400",
|
do: "border-cerulean-500 text-cerulean-600 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -48,7 +50,7 @@
|
||||||
<%= if @current_tab == "whitelist" do %>
|
<%= if @current_tab == "whitelist" do %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Allowed IPs and CIDR Ranges")}
|
{t("Allowed IPs and CIDR Ranges")}
|
||||||
</h2>
|
</h2>
|
||||||
<.button id="show-whitelist-form" phx-click="show_whitelist_form">
|
<.button id="show-whitelist-form" phx-click="show_whitelist_form">
|
||||||
|
|
@ -57,7 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @show_whitelist_form do %>
|
<%= if @show_whitelist_form do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<.form
|
<.form
|
||||||
for={@whitelist_form}
|
for={@whitelist_form}
|
||||||
phx-submit="add_whitelist"
|
phx-submit="add_whitelist"
|
||||||
|
|
@ -90,9 +92,9 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<%= if Enum.empty?(@whitelist) do %>
|
<%= if Enum.empty?(@whitelist) do %>
|
||||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
<div class="p-8 text-center text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No allowed IPs or CIDR ranges")}
|
{t("No allowed IPs or CIDR ranges")}
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -103,7 +105,7 @@
|
||||||
<%= if entry.added_by do %>
|
<%= if entry.added_by do %>
|
||||||
{entry.added_by.email}
|
{entry.added_by.email}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400">Unknown</span>
|
<span class="text-cool-steel-400">Unknown</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={entry} label={t("Added")}>
|
<:col :let={entry} label={t("Added")}>
|
||||||
|
|
@ -128,16 +130,16 @@
|
||||||
<%= if @current_tab == "blocked" do %>
|
<%= if @current_tab == "blocked" do %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Denied IP Addresses")}
|
{t("Denied IP Addresses")}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label class="text-sm text-gray-600 dark:text-gray-400">Filter:</label>
|
<label class="text-sm text-cool-steel-600 dark:text-cool-steel-400">Filter:</label>
|
||||||
<select
|
<select
|
||||||
phx-change="change_filter"
|
phx-change="change_filter"
|
||||||
name="filter"
|
name="filter"
|
||||||
class="rounded-md border-gray-300 dark:border-white/10 dark:bg-gray-900 text-sm"
|
class="rounded-md border-cool-steel-300 dark:border-white/10 dark:bg-cool-steel-900 text-sm"
|
||||||
>
|
>
|
||||||
<option value="all" selected={@filter == "all"}>All Bans</option>
|
<option value="all" selected={@filter == "all"}>All Bans</option>
|
||||||
<option value="permanent" selected={@filter == "permanent"}>Permanent Only</option>
|
<option value="permanent" selected={@filter == "permanent"}>Permanent Only</option>
|
||||||
|
|
@ -146,9 +148,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<%= if Enum.empty?(@blocked_ips) do %>
|
<%= if Enum.empty?(@blocked_ips) do %>
|
||||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
<div class="p-8 text-center text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No denied IPs")}
|
{t("No denied IPs")}
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -159,13 +161,13 @@
|
||||||
"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium",
|
"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium",
|
||||||
case block.offense_count do
|
case block.offense_count do
|
||||||
1 ->
|
1 ->
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
2 ->
|
2 ->
|
||||||
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
"bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{block.offense_count}
|
{block.offense_count}
|
||||||
|
|
@ -173,11 +175,11 @@
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={block} label={t("Status")}>
|
<:col :let={block} label={t("Status")}>
|
||||||
<%= if Towerops.Security.IpBlock.permanent?(block) do %>
|
<%= if Towerops.Security.IpBlock.permanent?(block) do %>
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400">
|
||||||
{t("Permanent")}
|
{t("Permanent")}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400">
|
||||||
{Towerops.Security.IpBlock.ban_duration(block)}
|
{Towerops.Security.IpBlock.ban_duration(block)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -186,7 +188,7 @@
|
||||||
{ToweropsWeb.TimeHelpers.format_time_ago(block.last_violation_at)}
|
{ToweropsWeb.TimeHelpers.format_time_ago(block.last_violation_at)}
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={block} label={t("Reason")}>
|
<:col :let={block} label={t("Reason")}>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400 max-w-xs truncate">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400 max-w-xs truncate">
|
||||||
{block.reason}
|
{block.reason}
|
||||||
</div>
|
</div>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
@ -197,7 +199,7 @@
|
||||||
data-confirm={
|
data-confirm={
|
||||||
t("Unblock this IP address? This will remove all offense history.")
|
t("Unblock this IP address? This will remove all offense history.")
|
||||||
}
|
}
|
||||||
class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
|
class="text-sm text-cerulean-600 dark:text-cerulean-400 hover:text-cerulean-800 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Unblock")}
|
{t("Unblock")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<Layouts.admin flash={@flash} timezone={@timezone}>
|
<Layouts.admin flash={@flash} timezone={@timezone}>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("All Users")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{t("All Users")}</h1>
|
||||||
<p class="text-gray-600 dark:text-gray-400">Manage system users</p>
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">Manage system users</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<.table id="users" rows={@users}>
|
<.table id="users" rows={@users}>
|
||||||
<:col :let={user} label={t("Email")}>{user.email}</:col>
|
<:col :let={user} label={t("Email")}>{user.email}</:col>
|
||||||
<:col :let={user} label={t("Superuser")}>
|
<:col :let={user} label={t("Superuser")}>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={user.id == @current_scope.user.id}
|
disabled={user.id == @current_scope.user.id}
|
||||||
class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 disabled:text-gray-400 disabled:cursor-not-allowed"
|
class="text-sm text-cerulean-600 dark:text-cerulean-400 hover:text-cerulean-800 dark:hover:text-cerulean-300 disabled:text-cool-steel-400 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{t("Impersonate")}
|
{t("Impersonate")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,18 @@
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/agents/#{@agent_token.id}"}
|
navigate={~p"/agents/#{@agent_token.id}"}
|
||||||
class="text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
<span class="hero-arrow-left h-4 w-4 inline"></span> Back to agent
|
<span class="hero-arrow-left h-4 w-4 inline"></span> Back to agent
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800/50 shadow rounded-lg">
|
<div class="bg-white dark:bg-cool-steel-800/50 shadow rounded-lg">
|
||||||
<div class="px-6 py-5 border-b border-gray-200 dark:border-white/10">
|
<div class="px-6 py-5 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h3 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Edit Agent")}
|
{t("Edit Agent")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Update the agent name. The authentication token cannot be changed.")}
|
{t("Update the agent name. The authentication token cannot be changed.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
placeholder="e.g., Datacenter Agent"
|
placeholder="e.g., Datacenter Agent"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("A descriptive name to identify this agent in your organization.")}
|
{t("A descriptive name to identify this agent in your organization.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -49,9 +49,9 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:if={@form[:allow_remote_debug].value}
|
:if={@form[:allow_remote_debug].value}
|
||||||
class="rounded-md bg-amber-50 dark:bg-amber-900/20 p-3 mt-2"
|
class="rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-3 mt-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-2 text-amber-800 dark:text-amber-200">
|
<div class="flex items-start gap-2 text-wheat-800 dark:text-wheat-200">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0 mt-0.5" />
|
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0 mt-0.5" />
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p class="font-medium">Performance Impact</p>
|
<p class="font-medium">Performance Impact</p>
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Enable verbose logging for troubleshooting SNMP discovery and polling issues.")}
|
{t("Enable verbose logging for troubleshooting SNMP discovery and polling issues.")}
|
||||||
{t("Only organization members can toggle this setting.")}
|
{t("Only organization members can toggle this setting.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
<div class="mt-6 flex items-center justify-end gap-x-4">
|
<div class="mt-6 flex items-center justify-end gap-x-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/agents/#{@agent_token.id}"}
|
navigate={~p"/agents/#{@agent_token.id}"}
|
||||||
class="text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ defmodule ToweropsWeb.AgentLive.Helpers do
|
||||||
def status_badge_class(status) do
|
def status_badge_class(status) do
|
||||||
case status do
|
case status do
|
||||||
:online -> "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300"
|
:online -> "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300"
|
||||||
:warning -> "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300"
|
:warning -> "bg-wheat-100 text-wheat-800 dark:bg-wheat-900 dark:text-wheat-300"
|
||||||
:offline -> "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300"
|
:offline -> "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900 dark:text-sweet-salmon-300"
|
||||||
:never -> "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300"
|
:never -> "bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -47,9 +47,9 @@ defmodule ToweropsWeb.AgentLive.Helpers do
|
||||||
def status_dot_class(status) do
|
def status_dot_class(status) do
|
||||||
case status do
|
case status do
|
||||||
:online -> "bg-green-600 dark:bg-green-400 animate-pulse"
|
:online -> "bg-green-600 dark:bg-green-400 animate-pulse"
|
||||||
:warning -> "bg-yellow-600 dark:bg-yellow-400"
|
:warning -> "bg-wheat-600 dark:bg-wheat-400"
|
||||||
:offline -> "bg-red-600 dark:bg-red-400"
|
:offline -> "bg-sweet-salmon-600 dark:bg-sweet-salmon-400"
|
||||||
:never -> "bg-gray-400 dark:bg-gray-600"
|
:never -> "bg-cool-steel-400 dark:bg-cool-steel-600"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
<!-- New Agent Form -->
|
<!-- New Agent Form -->
|
||||||
<div id="new-agent-form" class="hidden mt-6">
|
<div id="new-agent-form" class="hidden mt-6">
|
||||||
<div class="bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Create New Agent")}
|
{t("Create New Agent")}
|
||||||
</h3>
|
</h3>
|
||||||
<.form id="create-agent-form" for={@agent_form} phx-submit="create_agent">
|
<.form id="create-agent-form" for={@agent_form} phx-submit="create_agent">
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click={JS.hide(to: "#new-agent-form")}
|
phx-click={JS.hide(to: "#new-agent-form")}
|
||||||
class="px-3 py-2 text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
class="px-3 py-2 text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -55,9 +55,14 @@
|
||||||
|
|
||||||
<%= if !@has_agents do %>
|
<%= if !@has_agents do %>
|
||||||
<div class="text-center py-16">
|
<div class="text-center py-16">
|
||||||
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">{t("No agents")}</h3>
|
name="hero-server"
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("No agents")}
|
||||||
|
</h3>
|
||||||
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Get started by creating your first remote agent.")}
|
{t("Get started by creating your first remote agent.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
|
@ -82,7 +87,7 @@
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if agent.allow_remote_debug do %>
|
<%= if agent.allow_remote_debug do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20 dark:ring-amber-400/30">
|
<span class="inline-flex items-center gap-1 rounded-full bg-wheat-50 dark:bg-wheat-900/30 px-2 py-0.5 text-xs font-medium text-wheat-700 dark:text-wheat-300 ring-1 ring-inset ring-wheat-600/20 dark:ring-wheat-400/30">
|
||||||
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -99,10 +104,10 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Device")}>
|
<:col :let={{_id, agent}} label={t("Device")}>
|
||||||
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
<% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{counts.total} total
|
{counts.total} total
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{counts.direct} direct
|
{counts.direct} direct
|
||||||
<%= if counts.total > counts.direct do %>
|
<%= if counts.total > counts.direct do %>
|
||||||
· {counts.total - counts.direct} inherited
|
· {counts.total - counts.direct} inherited
|
||||||
|
|
@ -111,16 +116,16 @@
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
||||||
</div>
|
</div>
|
||||||
<%= if agent.last_seen_at do %>
|
<%= if agent.last_seen_at do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} format="absolute" />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} format="absolute" />
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if agent.last_ip do %>
|
<%= if agent.last_ip do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-mono">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5 font-mono">
|
||||||
{agent.last_ip}
|
{agent.last_ip}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -128,14 +133,14 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Version")}>
|
<:col :let={{_id, agent}} label={t("Version")}>
|
||||||
<%= if agent.metadata["version"] do %>
|
<%= if agent.metadata["version"] do %>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400 font-mono">
|
||||||
{format_agent_version(agent.metadata["version"])}
|
{format_agent_version(agent.metadata["version"])}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Created")}>
|
<:col :let={{_id, agent}} label={t("Created")}>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
|
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
|
||||||
</div>
|
</div>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
@ -168,7 +173,7 @@
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Disabled</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">Disabled</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:action>
|
</:action>
|
||||||
</.table>
|
</.table>
|
||||||
|
|
@ -178,10 +183,10 @@
|
||||||
<%!-- Cloud Pollers Section (Superadmin Only) --%>
|
<%!-- Cloud Pollers Section (Superadmin Only) --%>
|
||||||
<%= if @is_superuser && @has_cloud_pollers do %>
|
<%= if @is_superuser && @has_cloud_pollers do %>
|
||||||
<div class="mt-12">
|
<div class="mt-12">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Cloud Pollers")}
|
{t("Cloud Pollers")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Application-wide agents that can poll devices for any organization. Only visible and manageable by superadmins."
|
"Application-wide agents that can poll devices for any organization. Only visible and manageable by superadmins."
|
||||||
)}
|
)}
|
||||||
|
|
@ -201,7 +206,7 @@
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if agent.allow_remote_debug do %>
|
<%= if agent.allow_remote_debug do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-600/20 dark:ring-amber-400/30">
|
<span class="inline-flex items-center gap-1 rounded-full bg-wheat-50 dark:bg-wheat-900/30 px-2 py-0.5 text-xs font-medium text-wheat-700 dark:text-wheat-300 ring-1 ring-inset ring-wheat-600/20 dark:ring-wheat-400/30">
|
||||||
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
<.icon name="hero-bug-ant" class="h-3 w-3" /> Debug
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -218,10 +223,10 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Device")}>
|
<:col :let={{_id, agent}} label={t("Device")}>
|
||||||
<% counts = Map.get(@cloud_poller_counts, agent.id, %{direct: 0, total: 0}) %>
|
<% counts = Map.get(@cloud_poller_counts, agent.id, %{direct: 0, total: 0}) %>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{counts.total} total
|
{counts.total} total
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{counts.direct} direct
|
{counts.direct} direct
|
||||||
<%= if counts.total > counts.direct do %>
|
<%= if counts.total > counts.direct do %>
|
||||||
· {counts.total - counts.direct} inherited
|
· {counts.total - counts.direct} inherited
|
||||||
|
|
@ -230,16 +235,16 @@
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
<:col :let={{_id, agent}} label={t("Last Seen")}>
|
||||||
<div class="text-sm text-gray-900 dark:text-white">
|
<div class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} />
|
||||||
</div>
|
</div>
|
||||||
<%= if agent.last_seen_at do %>
|
<%= if agent.last_seen_at do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} format="absolute" />
|
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} format="absolute" />
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if agent.last_ip do %>
|
<%= if agent.last_ip do %>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-mono">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5 font-mono">
|
||||||
{agent.last_ip}
|
{agent.last_ip}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -247,14 +252,14 @@
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Version")}>
|
<:col :let={{_id, agent}} label={t("Version")}>
|
||||||
<%= if agent.metadata["version"] do %>
|
<%= if agent.metadata["version"] do %>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400 font-mono">
|
||||||
{format_agent_version(agent.metadata["version"])}
|
{format_agent_version(agent.metadata["version"])}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
<:col :let={{_id, agent}} label={t("Created")}>
|
<:col :let={{_id, agent}} label={t("Created")}>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
|
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
|
||||||
</div>
|
</div>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
@ -287,7 +292,7 @@
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Disabled</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">Disabled</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:action>
|
</:action>
|
||||||
</.table>
|
</.table>
|
||||||
|
|
@ -298,11 +303,11 @@
|
||||||
<%!-- Global Default Cloud Poller (Superadmin Only) --%>
|
<%!-- Global Default Cloud Poller (Superadmin Only) --%>
|
||||||
<%= if @is_superuser do %>
|
<%= if @is_superuser do %>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<div class="bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-800/30 rounded-lg p-6">
|
<div class="bg-wheat-50 dark:bg-wheat-900/10 border border-wheat-200 dark:border-wheat-800/30 rounded-lg p-6">
|
||||||
<h3 class="text-lg font-semibold text-amber-900 dark:text-amber-200 mb-2">
|
<h3 class="text-lg font-semibold text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Global Default Cloud Poller")}
|
{t("Global Default Cloud Poller")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-amber-700 dark:text-amber-300 mb-4">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Fallback agent for organizations without a default agent configured. Devices with no assignment at any level will use this agent."
|
"Fallback agent for organizations without a default agent configured. Devices with no assignment at any level will use this agent."
|
||||||
)}
|
)}
|
||||||
|
|
@ -316,13 +321,13 @@
|
||||||
>
|
>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
|
<label class="block text-sm font-medium text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Select Global Default")}
|
{t("Select Global Default")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="agent_token_id"
|
name="agent_token_id"
|
||||||
value={@selected_global_default}
|
value={@selected_global_default}
|
||||||
class="block w-full rounded-md border-amber-300 dark:border-amber-700 bg-white dark:bg-gray-900 text-gray-900 dark:text-white shadow-sm focus:border-amber-500 focus:ring-amber-500 sm:text-sm"
|
class="block w-full rounded-md border-wheat-300 dark:border-wheat-700 bg-white dark:bg-cool-steel-900 text-cool-steel-900 dark:text-white shadow-sm focus:border-wheat-500 focus:ring-wheat-500 sm:text-sm"
|
||||||
>
|
>
|
||||||
<option value="">No global default (direct Phoenix polling)</option>
|
<option value="">No global default (direct Phoenix polling)</option>
|
||||||
<%= for cloud_poller <- @cloud_pollers_list do %>
|
<%= for cloud_poller <- @cloud_pollers_list do %>
|
||||||
|
|
@ -340,18 +345,18 @@
|
||||||
<% selected_poller =
|
<% selected_poller =
|
||||||
Enum.find(@cloud_pollers_list, &(&1.id == @global_default_cloud_poller_id)) %>
|
Enum.find(@cloud_pollers_list, &(&1.id == @global_default_cloud_poller_id)) %>
|
||||||
<%= if selected_poller do %>
|
<%= if selected_poller do %>
|
||||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
<p class="text-xs text-wheat-600 dark:text-wheat-400">
|
||||||
<.icon name="hero-check-circle" class="h-4 w-4 inline" /> Currently using:
|
<.icon name="hero-check-circle" class="h-4 w-4 inline" /> Currently using:
|
||||||
<span class="font-medium">{selected_poller.name}</span>
|
<span class="font-medium">{selected_poller.name}</span>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-xs text-red-600 dark:text-red-400">
|
<p class="text-xs text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline" />
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline" />
|
||||||
{t("Warning: Selected agent no longer exists. Please choose a new agent.")}
|
{t("Warning: Selected agent no longer exists. Please choose a new agent.")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
<p class="text-xs text-wheat-600 dark:text-wheat-400">
|
||||||
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
||||||
{t(
|
{t(
|
||||||
"No global default configured. Devices without assignments will use direct Phoenix cluster polling."
|
"No global default configured. Devices without assignments will use direct Phoenix cluster polling."
|
||||||
|
|
@ -377,7 +382,7 @@
|
||||||
value: %{agent_token_id: @global_default_cloud_poller_id || ""}
|
value: %{agent_token_id: @global_default_cloud_poller_id || ""}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="px-3 py-2 text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
class="px-3 py-2 text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -398,26 +403,26 @@
|
||||||
>
|
>
|
||||||
<div class="flex min-h-screen items-center justify-center p-4">
|
<div class="flex min-h-screen items-center justify-center p-4">
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 bg-gray-500/75 dark:bg-gray-950/75 transition-opacity"
|
class="fixed inset-0 bg-cool-steel-500/75 dark:bg-cool-steel-950/75 transition-opacity"
|
||||||
phx-click="close_token_modal"
|
phx-click="close_token_modal"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative bg-white dark:bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full">
|
<div class="relative bg-white dark:bg-cool-steel-900 rounded-lg shadow-xl max-w-2xl w-full">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Agent Setup Instructions")}
|
{t("Agent Setup Instructions")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Use the token and Docker Compose configuration below to deploy this agent.")}
|
{t("Use the token and Docker Compose configuration below to deploy this agent.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close_token_modal"
|
phx-click="close_token_modal"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-6 w-6" />
|
<.icon name="hero-x-mark" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -425,16 +430,16 @@
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2">
|
||||||
{t("Agent Name")}
|
{t("Agent Name")}
|
||||||
</label>
|
</label>
|
||||||
<div class="text-base font-mono text-gray-900 dark:text-white">
|
<div class="text-base font-mono text-cool-steel-900 dark:text-white">
|
||||||
{@new_token.agent_token.name}
|
{@new_token.agent_token.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2">
|
||||||
{t("Authentication Token")}
|
{t("Authentication Token")}
|
||||||
</label>
|
</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
|
|
@ -442,7 +447,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
value={String.trim(@new_token.token)}
|
value={String.trim(@new_token.token)}
|
||||||
class="w-full font-mono text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded px-3 py-2 pr-28 text-gray-900 dark:text-white select-all"
|
class="w-full font-mono text-sm bg-cool-steel-50 dark:bg-cool-steel-800 border border-cool-steel-300 dark:border-cool-steel-700 rounded px-3 py-2 pr-28 text-cool-steel-900 dark:text-white select-all"
|
||||||
id="agent-token-input"
|
id="agent-token-input"
|
||||||
onclick="this.select()"
|
onclick="this.select()"
|
||||||
/>
|
/>
|
||||||
|
|
@ -452,27 +457,27 @@
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
data-target="#agent-token-input"
|
data-target="#agent-token-input"
|
||||||
id={"copy-token-#{@new_token.agent_token.id}"}
|
id={"copy-token-#{@new_token.agent_token.id}"}
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 px-3 py-1.5 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded shadow-sm transition-colors flex items-center gap-1.5"
|
class="absolute right-2 top-1/2 -translate-y-1/2 px-3 py-1.5 text-xs font-medium text-white bg-cerulean-600 hover:bg-cerulean-700 dark:bg-cerulean-500 dark:hover:bg-cerulean-600 rounded shadow-sm transition-colors flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<.icon name="hero-clipboard" class="h-3.5 w-3.5" />
|
<.icon name="hero-clipboard" class="h-3.5 w-3.5" />
|
||||||
<span>{t("Copy")}</span>
|
<span>{t("Copy")}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Click the token to select all, or use the Copy button")}
|
{t("Click the token to select all, or use the Copy button")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-800 border border-cool-steel-200 dark:border-cool-steel-700 rounded-lg p-4">
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<h4 class="text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Docker Compose Setup with Auto-Updates")}
|
{t("Docker Compose Setup with Auto-Updates")}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-3">
|
<p class="text-xs text-cool-steel-600 dark:text-cool-steel-400 mb-3">
|
||||||
{t(
|
{t(
|
||||||
"This configuration includes Watchtower for automatic agent updates. The agent will automatically update to the latest version every 12 hours."
|
"This configuration includes Watchtower for automatic agent updates. The agent will automatically update to the latest version every 12 hours."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<pre class="text-xs font-mono text-gray-600 dark:text-gray-400 overflow-x-auto"><code>services:
|
<pre class="text-xs font-mono text-cool-steel-600 dark:text-cool-steel-400 overflow-x-auto"><code>services:
|
||||||
towerops-agent:
|
towerops-agent:
|
||||||
image: {@agent_image}
|
image: {@agent_image}
|
||||||
container_name: towerops-agent
|
container_name: towerops-agent
|
||||||
|
|
@ -505,7 +510,7 @@
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
data-target="#docker-compose-example"
|
data-target="#docker-compose-example"
|
||||||
id={"copy-docker-compose-#{@new_token.agent_token.id}"}
|
id={"copy-docker-compose-#{@new_token.agent_token.id}"}
|
||||||
class="inline-flex items-center px-2.5 py-1.5 text-xs font-medium text-white bg-gray-700 hover:bg-gray-600 dark:bg-gray-600 dark:hover:bg-gray-500 rounded transition-colors"
|
class="inline-flex items-center px-2.5 py-1.5 text-xs font-medium text-white bg-cool-steel-700 hover:bg-cool-steel-600 dark:bg-cool-steel-600 dark:hover:bg-cool-steel-500 rounded transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-clipboard" class="h-3 w-3" /> Copy to clipboard
|
<.icon name="hero-clipboard" class="h-3 w-3" /> Copy to clipboard
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,10 @@ defmodule ToweropsWeb.AgentLive.Show do
|
||||||
|
|
||||||
defp source_badge_class(source) do
|
defp source_badge_class(source) do
|
||||||
case source do
|
case source do
|
||||||
:direct -> "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300"
|
:direct -> "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900 dark:text-cerulean-300"
|
||||||
:site -> "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300"
|
:site -> "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300"
|
||||||
:organization -> "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-300"
|
:organization -> "bg-wheat-100 text-wheat-800 dark:bg-wheat-900 dark:text-wheat-300"
|
||||||
:unknown -> "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300"
|
:unknown -> "bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,21 @@
|
||||||
"Are you sure you want to restart this agent? It will disconnect and restart its container."
|
"Are you sure you want to restart this agent? It will disconnect and restart its container."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-2 rounded-lg px-3.5 py-2.5 text-sm font-semibold shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 bg-amber-600 text-white hover:bg-amber-700 focus:ring-amber-500 dark:bg-amber-500 dark:hover:bg-amber-600"
|
class="inline-flex items-center gap-2 rounded-lg px-3.5 py-2.5 text-sm font-semibold shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 bg-wheat-600 text-white hover:bg-wheat-700 focus:ring-wheat-500 dark:bg-wheat-500 dark:hover:bg-wheat-600"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-5 w-5" />
|
<.icon name="hero-arrow-path" class="h-5 w-5" />
|
||||||
<span class="hidden sm:inline">Restart</span>
|
<span class="hidden sm:inline">Restart</span>
|
||||||
</button>
|
</button>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/agents/#{@agent_token.id}/edit"}
|
navigate={~p"/agents/#{@agent_token.id}/edit"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg px-3.5 py-2.5 text-sm font-semibold shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 dark:bg-blue-500 dark:hover:bg-blue-600"
|
class="inline-flex items-center gap-2 rounded-lg px-3.5 py-2.5 text-sm font-semibold shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed bg-cerulean-600 text-white hover:bg-cerulean-700 focus:ring-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-600"
|
||||||
>
|
>
|
||||||
<.icon name="hero-pencil" class="h-5 w-5" />
|
<.icon name="hero-pencil" class="h-5 w-5" />
|
||||||
<span class="hidden sm:inline">Edit Agent</span>
|
<span class="hidden sm:inline">Edit Agent</span>
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/agents"}
|
navigate={~p"/agents"}
|
||||||
class="inline-flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
class="inline-flex items-center gap-2 text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
<span class="hidden sm:inline">Back to Agents</span>
|
<span class="hidden sm:inline">Back to Agents</span>
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<!-- Tab Navigation (Superuser Only) -->
|
<!-- Tab Navigation (Superuser Only) -->
|
||||||
<div
|
<div
|
||||||
:if={@current_scope.user.is_superuser}
|
:if={@current_scope.user.is_superuser}
|
||||||
class="mt-6 border-b border-gray-200 dark:border-white/10"
|
class="mt-6 border-b border-cool-steel-200 dark:border-white/10"
|
||||||
>
|
>
|
||||||
<nav class="-mb-px flex space-x-8">
|
<nav class="-mb-px flex space-x-8">
|
||||||
<button
|
<button
|
||||||
|
|
@ -64,9 +64,9 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
||||||
if(@active_tab == "overview",
|
if(@active_tab == "overview",
|
||||||
do: "border-blue-500 text-blue-600 dark:text-blue-400",
|
do: "border-cerulean-500 text-cerulean-600 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:text-cool-steel-700 hover:border-cool-steel-300 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -79,9 +79,9 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
||||||
if(@active_tab == "debug",
|
if(@active_tab == "debug",
|
||||||
do: "border-blue-500 text-blue-600 dark:text-blue-400",
|
do: "border-cerulean-500 text-cerulean-600 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:text-cool-steel-700 hover:border-cool-steel-300 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -96,15 +96,15 @@
|
||||||
"rounded-md p-4 mb-4",
|
"rounded-md p-4 mb-4",
|
||||||
if(@agent_token.allow_remote_debug,
|
if(@agent_token.allow_remote_debug,
|
||||||
do: "bg-green-50 dark:bg-green-900/20",
|
do: "bg-green-50 dark:bg-green-900/20",
|
||||||
else: "bg-gray-50 dark:bg-gray-900/20"
|
else: "bg-cool-steel-50 dark:bg-cool-steel-900/20"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
Debug Status: {if @agent_token.allow_remote_debug,
|
Debug Status: {if @agent_token.allow_remote_debug,
|
||||||
do: "Enabled ✓",
|
do: "Enabled ✓",
|
||||||
else: "Disabled"}
|
else: "Disabled"}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if @agent_token.allow_remote_debug do %>
|
<%= if @agent_token.allow_remote_debug do %>
|
||||||
{t("Verbose logging is enabled. All SNMP data and agent messages are being logged.")}
|
{t("Verbose logging is enabled. All SNMP data and agent messages are being logged.")}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -116,33 +116,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("View Debug Logs")}
|
{t("View Debug Logs")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="rounded-md bg-gray-50 dark:bg-gray-900 p-4">
|
<div class="rounded-md bg-cool-steel-50 dark:bg-cool-steel-900 p-4">
|
||||||
<p class="text-sm mb-2 font-medium text-gray-900 dark:text-white">
|
<p class="text-sm mb-2 font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Production (kubectl):")}
|
{t("Production (kubectl):")}
|
||||||
</p>
|
</p>
|
||||||
<code class="text-xs text-gray-800 dark:text-gray-200 block">
|
<code class="text-xs text-cool-steel-800 dark:text-cool-steel-200 block">
|
||||||
kubectl logs -n towerops deployment/towerops --tail=100 | grep "agent_token_id: {@agent_token.id}"
|
kubectl logs -n towerops deployment/towerops --tail=100 | grep "agent_token_id: {@agent_token.id}"
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-md bg-gray-50 dark:bg-gray-900 p-4">
|
<div class="rounded-md bg-cool-steel-50 dark:bg-cool-steel-900 p-4">
|
||||||
<p class="text-sm mb-2 font-medium text-gray-900 dark:text-white">
|
<p class="text-sm mb-2 font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Development (tail):")}
|
{t("Development (tail):")}
|
||||||
</p>
|
</p>
|
||||||
<code class="text-xs text-gray-800 dark:text-gray-200 block">
|
<code class="text-xs text-cool-steel-800 dark:text-cool-steel-200 block">
|
||||||
tail -f _build/dev/lib/towerops/priv/log/dev.log | grep "{@agent_token.id}"
|
tail -f _build/dev/lib/towerops/priv/log/dev.log | grep "{@agent_token.id}"
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<p class="text-sm font-medium mb-2 text-gray-900 dark:text-white">
|
<p class="text-sm font-medium mb-2 text-cool-steel-900 dark:text-white">
|
||||||
{t("What Gets Logged:")}
|
{t("What Gets Logged:")}
|
||||||
</p>
|
</p>
|
||||||
<ul class="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
|
<ul class="text-sm text-cool-steel-600 dark:text-cool-steel-400 list-disc list-inside space-y-1">
|
||||||
<li>Agent connection/disconnection events</li>
|
<li>Agent connection/disconnection events</li>
|
||||||
<li>Job submissions (device IDs and job types)</li>
|
<li>Job submissions (device IDs and job types)</li>
|
||||||
<li>SNMP discovery results (full OID maps)</li>
|
<li>SNMP discovery results (full OID maps)</li>
|
||||||
|
|
@ -160,10 +160,10 @@
|
||||||
<!-- Agent Status Overview -->
|
<!-- Agent Status Overview -->
|
||||||
<div class="mt-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="mt-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<!-- Status Card -->
|
<!-- Status Card -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Status</p>
|
<p class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">Status</p>
|
||||||
<% {status, label} = agent_status(@agent_token) %>
|
<% {status, label} = agent_status(@agent_token) %>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<span class={"inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium #{status_badge_class(status)}"}>
|
<span class={"inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium #{status_badge_class(status)}"}>
|
||||||
|
|
@ -172,36 +172,38 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<.icon name="hero-signal" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
<.icon name="hero-signal" class="h-8 w-8 text-cool-steel-400 dark:text-cool-steel-500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Device Count Card -->
|
<!-- Device Count Card -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Device</p>
|
<p class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">Device</p>
|
||||||
<p class="mt-2 text-3xl font-semibold text-gray-900 dark:text-white">
|
<p class="mt-2 text-3xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{length(@polling_targets)}
|
{length(@polling_targets)}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{@direct_assignments} direct
|
{@direct_assignments} direct
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<.icon name="hero-server" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
<.icon name="hero-server" class="h-8 w-8 text-cool-steel-400 dark:text-cool-steel-500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Last Seen Card -->
|
<!-- Last Seen Card -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Last Seen</p>
|
<p class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p class="mt-2 text-lg font-semibold text-gray-900 dark:text-white">
|
Last Seen
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
|
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
|
||||||
</p>
|
</p>
|
||||||
<%= if @agent_token.last_seen_at do %>
|
<%= if @agent_token.last_seen_at do %>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={@agent_token.last_seen_at}
|
datetime={@agent_token.last_seen_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
|
|
@ -210,54 +212,61 @@
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @agent_token.last_ip do %>
|
<%= if @agent_token.last_ip do %>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 font-mono">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400 font-mono">
|
||||||
{@agent_token.last_ip}
|
{@agent_token.last_ip}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<.icon name="hero-clock" class="h-8 w-8 text-gray-400 dark:text-gray-500 flex-shrink-0" />
|
<.icon
|
||||||
|
name="hero-clock"
|
||||||
|
class="h-8 w-8 text-cool-steel-400 dark:text-cool-steel-500 flex-shrink-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uptime Card -->
|
<!-- Uptime Card -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Uptime</p>
|
<p class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">Uptime</p>
|
||||||
<%= if @agent_token.metadata["uptime_seconds"] do %>
|
<%= if @agent_token.metadata["uptime_seconds"] do %>
|
||||||
<p class="mt-2 text-lg font-semibold text-gray-900 dark:text-white">
|
<p class="mt-2 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{format_uptime(@agent_token.metadata["uptime_seconds"])}
|
{format_uptime(@agent_token.metadata["uptime_seconds"])}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="mt-2 text-lg font-semibold text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-lg font-semibold text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Unknown")}
|
{t("Unknown")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<.icon name="hero-clock" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
<.icon name="hero-clock" class="h-8 w-8 text-cool-steel-400 dark:text-cool-steel-500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Agent Details -->
|
<!-- Agent Details -->
|
||||||
<%= if @agent_token.metadata && map_size(@agent_token.metadata) > 0 do %>
|
<%= if @agent_token.metadata && map_size(@agent_token.metadata) > 0 do %>
|
||||||
<div class="mt-6 bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="mt-6 bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Agent Details")}
|
{t("Agent Details")}
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<%= if @agent_token.metadata["version"] do %>
|
<%= if @agent_token.metadata["version"] do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Version</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white font-mono">
|
Version
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white font-mono">
|
||||||
{format_agent_version(@agent_token.metadata["version"])}
|
{format_agent_version(@agent_token.metadata["version"])}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @agent_token.metadata["uptime_seconds"] do %>
|
<%= if @agent_token.metadata["uptime_seconds"] do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Uptime</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Uptime
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{format_uptime(@agent_token.metadata["uptime_seconds"])}
|
{format_uptime(@agent_token.metadata["uptime_seconds"])}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -267,38 +276,44 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Agent Timestamps -->
|
<!-- Agent Timestamps -->
|
||||||
<div class="mt-6 bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg p-6">
|
<div class="mt-6 bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
{t("Timestamps")}
|
{t("Timestamps")}
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Created</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Created
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={@agent_token.inserted_at} timezone={@timezone} />
|
<.timestamp datetime={@agent_token.inserted_at} timezone={@timezone} />
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Last Updated</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
Last Updated
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={@agent_token.updated_at} timezone={@timezone} />
|
<.timestamp datetime={@agent_token.updated_at} timezone={@timezone} />
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Last Seen (Heartbeat)")}
|
{t("Last Seen (Heartbeat)")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
|
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @agent_token.last_ip do %>
|
<%= if @agent_token.last_ip do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Last IP Address</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white font-mono">
|
Last IP Address
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-cool-steel-900 dark:text-white font-mono">
|
||||||
{@agent_token.last_ip}
|
{@agent_token.last_ip}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -307,38 +322,41 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Device List -->
|
<!-- Device List -->
|
||||||
<div class="mt-6 bg-white dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
|
<div class="mt-6 bg-white dark:bg-cool-steel-800/50 border border-cool-steel-200 dark:border-white/10 rounded-lg">
|
||||||
<div class="p-6 border-b border-gray-200 dark:border-white/10">
|
<div class="p-6 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Polling Targets")}
|
{t("Polling Targets")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Device that this agent is responsible for polling")}
|
{t("Device that this agent is responsible for polling")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @polling_targets == [] do %>
|
<%= if @polling_targets == [] do %>
|
||||||
<div class="p-12 text-center">
|
<div class="p-12 text-center">
|
||||||
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
<h3 class="mt-4 text-sm font-semibold text-gray-900 dark:text-white">
|
name="hero-server"
|
||||||
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-4 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No devices assigned")}
|
{t("No devices assigned")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("This agent is not currently polling any device.")}
|
{t("This agent is not currently polling any device.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for device <- @polling_targets do %>
|
<%= for device <- @polling_targets do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{device.id}"}
|
navigate={~p"/devices/#{device.id}"}
|
||||||
class="block hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
class="block hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800 transition-colors"
|
||||||
>
|
>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
{device.name}
|
{device.name}
|
||||||
</p>
|
</p>
|
||||||
<% {source, source_label} = assignment_source(device, @agent_token.id) %>
|
<% {source, source_label} = assignment_source(device, @agent_token.id) %>
|
||||||
|
|
@ -346,7 +364,7 @@
|
||||||
{source_label}
|
{source_label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
<div class="mt-1 flex items-center gap-4 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<.icon name="hero-globe-alt" class="h-4 w-4" />
|
<.icon name="hero-globe-alt" class="h-4 w-4" />
|
||||||
{device.ip_address}
|
{device.ip_address}
|
||||||
|
|
@ -361,7 +379,7 @@
|
||||||
</div>
|
</div>
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-chevron-right"
|
name="hero-chevron-right"
|
||||||
class="h-5 w-5 text-gray-400 dark:text-gray-500"
|
class="h-5 w-5 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -334,12 +334,12 @@ defmodule ToweropsWeb.AlertLive.Index do
|
||||||
color = severity_color(alert)
|
color = severity_color(alert)
|
||||||
|
|
||||||
case color do
|
case color do
|
||||||
"red" -> "bg-red-500"
|
"red" -> "bg-sweet-salmon-500"
|
||||||
"orange" -> "bg-orange-500"
|
"orange" -> "bg-orange-500"
|
||||||
"yellow" -> "bg-yellow-500"
|
"yellow" -> "bg-wheat-500"
|
||||||
"green" -> "bg-green-500"
|
"green" -> "bg-green-500"
|
||||||
"gray" -> "bg-gray-300 dark:bg-gray-600"
|
"gray" -> "bg-cool-steel-300 dark:bg-cool-steel-600"
|
||||||
_ -> "bg-gray-300"
|
_ -> "bg-cool-steel-300"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@
|
||||||
<%!-- Compact header --%>
|
<%!-- Compact header --%>
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Alerts")}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">{t("Alerts")}</h1>
|
||||||
<span class="rounded px-2 py-0.5 text-xs font-medium bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400">
|
<span class="rounded px-2 py-0.5 text-xs font-medium bg-cerulean-50 text-cerulean-700 dark:bg-cerulean-900/20 dark:text-cerulean-400">
|
||||||
{t("Experimental")}
|
{t("Experimental")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Monitor and triage network alerts, grouped by site with subscriber impact.")}
|
{t("Monitor and triage network alerts, grouped by site with subscriber impact.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -23,16 +23,17 @@
|
||||||
patch={~p"/alerts?filter=unresolved&sort=#{@sort_by}"}
|
patch={~p"/alerts?filter=unresolved&sort=#{@sort_by}"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "unresolved" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "unresolved" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "unresolved" &&
|
@filter != "unresolved" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Unresolved")}
|
{t("Unresolved")}
|
||||||
<span class={[
|
<span class={[
|
||||||
"rounded-full px-1.5 py-0.5 text-xs",
|
"rounded-full px-1.5 py-0.5 text-xs",
|
||||||
@filter == "unresolved" && "bg-white/20 dark:bg-gray-900/30",
|
@filter == "unresolved" && "bg-white/20 dark:bg-cool-steel-900/30",
|
||||||
@filter != "unresolved" && "bg-gray-200 dark:bg-gray-700"
|
@filter != "unresolved" && "bg-cool-steel-200 dark:bg-cool-steel-700"
|
||||||
]}>
|
]}>
|
||||||
{@counts.unresolved}
|
{@counts.unresolved}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -41,16 +42,16 @@
|
||||||
patch={~p"/alerts?filter=critical&sort=#{@sort_by}"}
|
patch={~p"/alerts?filter=critical&sort=#{@sort_by}"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "critical" && "bg-red-600 text-white",
|
@filter == "critical" && "bg-sweet-salmon-600 text-white",
|
||||||
@filter != "critical" &&
|
@filter != "critical" &&
|
||||||
"bg-red-50 text-red-700 hover:bg-red-100 dark:bg-red-900/20 dark:text-red-400 dark:hover:bg-red-900/40"
|
"bg-sweet-salmon-50 text-sweet-salmon-700 hover:bg-sweet-salmon-100 dark:bg-sweet-salmon-900/20 dark:text-sweet-salmon-400 dark:hover:bg-sweet-salmon-900/40"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Critical")}
|
{t("Critical")}
|
||||||
<span class={[
|
<span class={[
|
||||||
"rounded-full px-1.5 py-0.5 text-xs",
|
"rounded-full px-1.5 py-0.5 text-xs",
|
||||||
@filter == "critical" && "bg-white/20",
|
@filter == "critical" && "bg-white/20",
|
||||||
@filter != "critical" && "bg-red-100 dark:bg-red-900/40"
|
@filter != "critical" && "bg-sweet-salmon-100 dark:bg-sweet-salmon-900/40"
|
||||||
]}>
|
]}>
|
||||||
{@counts.critical}
|
{@counts.critical}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -59,16 +60,16 @@
|
||||||
patch={~p"/alerts?filter=all&sort=#{@sort_by}"}
|
patch={~p"/alerts?filter=all&sort=#{@sort_by}"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "all" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "all" && "bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "all" &&
|
@filter != "all" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("All")}
|
{t("All")}
|
||||||
<span class={[
|
<span class={[
|
||||||
"rounded-full px-1.5 py-0.5 text-xs",
|
"rounded-full px-1.5 py-0.5 text-xs",
|
||||||
@filter == "all" && "bg-white/20 dark:bg-gray-900/30",
|
@filter == "all" && "bg-white/20 dark:bg-cool-steel-900/30",
|
||||||
@filter != "all" && "bg-gray-200 dark:bg-gray-700"
|
@filter != "all" && "bg-cool-steel-200 dark:bg-cool-steel-700"
|
||||||
]}>
|
]}>
|
||||||
{@counts.all}
|
{@counts.all}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -77,33 +78,34 @@
|
||||||
patch={~p"/alerts?filter=resolved&sort=#{@sort_by}"}
|
patch={~p"/alerts?filter=resolved&sort=#{@sort_by}"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "resolved" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "resolved" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "resolved" &&
|
@filter != "resolved" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Resolved")}
|
{t("Resolved")}
|
||||||
<span class={[
|
<span class={[
|
||||||
"rounded-full px-1.5 py-0.5 text-xs",
|
"rounded-full px-1.5 py-0.5 text-xs",
|
||||||
@filter == "resolved" && "bg-white/20 dark:bg-gray-900/30",
|
@filter == "resolved" && "bg-white/20 dark:bg-cool-steel-900/30",
|
||||||
@filter != "resolved" && "bg-gray-200 dark:bg-gray-700"
|
@filter != "resolved" && "bg-cool-steel-200 dark:bg-cool-steel-700"
|
||||||
]}>
|
]}>
|
||||||
{@counts.resolved}
|
{@counts.resolved}
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
|
|
||||||
<span class="text-gray-300 dark:text-gray-600 mx-1">|</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600 mx-1">|</span>
|
||||||
|
|
||||||
<%!-- Sort controls --%>
|
<%!-- Sort controls --%>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">{t("Sort")}:</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">{t("Sort")}:</span>
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/alerts?filter=#{@filter}&sort=severity"}
|
patch={~p"/alerts?filter=#{@filter}&sort=severity"}
|
||||||
class={[
|
class={[
|
||||||
"text-xs px-2 py-1 rounded transition-colors",
|
"text-xs px-2 py-1 rounded transition-colors",
|
||||||
@sort_by == "severity" &&
|
@sort_by == "severity" &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 font-medium",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-300 font-medium",
|
||||||
@sort_by != "severity" &&
|
@sort_by != "severity" &&
|
||||||
"text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
|
"text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Severity")}
|
{t("Severity")}
|
||||||
|
|
@ -113,9 +115,9 @@
|
||||||
class={[
|
class={[
|
||||||
"text-xs px-2 py-1 rounded transition-colors",
|
"text-xs px-2 py-1 rounded transition-colors",
|
||||||
@sort_by == "age" &&
|
@sort_by == "age" &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 font-medium",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-300 font-medium",
|
||||||
@sort_by != "age" &&
|
@sort_by != "age" &&
|
||||||
"text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
|
"text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Oldest")}
|
{t("Oldest")}
|
||||||
|
|
@ -125,9 +127,9 @@
|
||||||
class={[
|
class={[
|
||||||
"text-xs px-2 py-1 rounded transition-colors",
|
"text-xs px-2 py-1 rounded transition-colors",
|
||||||
@sort_by == "impact" &&
|
@sort_by == "impact" &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 font-medium",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-300 font-medium",
|
||||||
@sort_by != "impact" &&
|
@sort_by != "impact" &&
|
||||||
"text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
|
"text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Impact")}
|
{t("Impact")}
|
||||||
|
|
@ -137,9 +139,9 @@
|
||||||
<%!-- Bulk actions bar --%>
|
<%!-- Bulk actions bar --%>
|
||||||
<div
|
<div
|
||||||
:if={MapSet.size(@selected_alerts) > 0}
|
:if={MapSet.size(@selected_alerts) > 0}
|
||||||
class="flex items-center gap-3 mb-3 px-3 py-2 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800"
|
class="flex items-center gap-3 mb-3 px-3 py-2 rounded-lg bg-cerulean-50 dark:bg-cerulean-900/20 border border-cerulean-200 dark:border-cerulean-800"
|
||||||
>
|
>
|
||||||
<span class="text-xs font-medium text-blue-800 dark:text-blue-300">
|
<span class="text-xs font-medium text-cerulean-800 dark:text-cerulean-300">
|
||||||
{MapSet.size(@selected_alerts)} {t("selected")}
|
{MapSet.size(@selected_alerts)} {t("selected")}
|
||||||
</span>
|
</span>
|
||||||
<.button type="button" phx-click="bulk_acknowledge" variant="primary">
|
<.button type="button" phx-click="bulk_acknowledge" variant="primary">
|
||||||
|
|
@ -151,7 +153,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="select_none"
|
phx-click="select_none"
|
||||||
class="text-xs text-blue-600 dark:text-blue-400 hover:underline ml-auto"
|
class="text-xs text-cerulean-600 dark:text-cerulean-400 hover:underline ml-auto"
|
||||||
>
|
>
|
||||||
{t("Clear selection")}
|
{t("Clear selection")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -169,7 +171,7 @@
|
||||||
<h3 class="text-lg font-semibold text-green-700 dark:text-green-400">
|
<h3 class="text-lg font-semibold text-green-700 dark:text-green-400">
|
||||||
{t("All Clear!")}
|
{t("All Clear!")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-sm">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 max-w-sm">
|
||||||
<%= case @filter do %>
|
<%= case @filter do %>
|
||||||
<% "unresolved" -> %>
|
<% "unresolved" -> %>
|
||||||
{t("No active alerts. Your network is looking good!")}
|
{t("No active alerts. Your network is looking good!")}
|
||||||
|
|
@ -189,13 +191,13 @@
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="py-2 pl-4 pr-2 w-8">
|
<th scope="col" class="py-2 pl-4 pr-2 w-8">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="select_all"
|
phx-click="select_all"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
title={t("Select all")}
|
title={t("Select all")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-check-circle" class="h-4 w-4" />
|
<.icon name="hero-check-circle" class="h-4 w-4" />
|
||||||
|
|
@ -204,51 +206,51 @@
|
||||||
<th scope="col" class="w-1 px-0 py-2"></th>
|
<th scope="col" class="w-1 px-0 py-2"></th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Device")}
|
{t("Device")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Site")}
|
{t("Site")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Message")}
|
{t("Message")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Duration")}
|
{t("Duration")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Impact")}
|
{t("Impact")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-right text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-right text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<%= for alert <- @alerts do %>
|
<%= for alert <- @alerts do %>
|
||||||
<tr
|
<tr
|
||||||
class={[
|
class={[
|
||||||
"hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors cursor-pointer",
|
"hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/30 transition-colors cursor-pointer",
|
||||||
MapSet.member?(@selected_alerts, alert.id) &&
|
MapSet.member?(@selected_alerts, alert.id) &&
|
||||||
"bg-blue-50/50 dark:bg-blue-900/10",
|
"bg-cerulean-50/50 dark:bg-cerulean-900/10",
|
||||||
rem(Enum.find_index(@alerts, &(&1.id == alert.id)) || 0, 2) == 1 &&
|
rem(Enum.find_index(@alerts, &(&1.id == alert.id)) || 0, 2) == 1 &&
|
||||||
"bg-gray-50/40 dark:bg-gray-800/20"
|
"bg-cool-steel-50/40 dark:bg-cool-steel-800/20"
|
||||||
]}
|
]}
|
||||||
phx-click="toggle_alert"
|
phx-click="toggle_alert"
|
||||||
phx-value-id={alert.id}
|
phx-value-id={alert.id}
|
||||||
|
|
@ -257,9 +259,10 @@
|
||||||
<td class="py-2 pl-4 pr-2" phx-click="toggle_select" phx-value-id={alert.id}>
|
<td class="py-2 pl-4 pr-2" phx-click="toggle_select" phx-value-id={alert.id}>
|
||||||
<div class={[
|
<div class={[
|
||||||
"h-4 w-4 rounded border flex items-center justify-center transition-colors",
|
"h-4 w-4 rounded border flex items-center justify-center transition-colors",
|
||||||
MapSet.member?(@selected_alerts, alert.id) && "bg-blue-500 border-blue-500",
|
MapSet.member?(@selected_alerts, alert.id) &&
|
||||||
|
"bg-cerulean-500 border-cerulean-500",
|
||||||
!MapSet.member?(@selected_alerts, alert.id) &&
|
!MapSet.member?(@selected_alerts, alert.id) &&
|
||||||
"border-gray-300 dark:border-gray-600"
|
"border-cool-steel-300 dark:border-cool-steel-600"
|
||||||
]}>
|
]}>
|
||||||
<.icon
|
<.icon
|
||||||
:if={MapSet.member?(@selected_alerts, alert.id)}
|
:if={MapSet.member?(@selected_alerts, alert.id)}
|
||||||
|
|
@ -280,9 +283,9 @@
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded",
|
"inline-flex items-center gap-1 text-xs font-medium px-1.5 py-0.5 rounded",
|
||||||
alert.alert_type == "device_down" && is_nil(alert.resolved_at) &&
|
alert.alert_type == "device_down" && is_nil(alert.resolved_at) &&
|
||||||
"bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300",
|
"bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/40 dark:text-sweet-salmon-300",
|
||||||
alert.alert_type == "device_down" && alert.resolved_at &&
|
alert.alert_type == "device_down" && alert.resolved_at &&
|
||||||
"bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400",
|
||||||
alert.alert_type == "device_up" &&
|
alert.alert_type == "device_up" &&
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
|
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
|
||||||
]}>
|
]}>
|
||||||
|
|
@ -294,33 +297,33 @@
|
||||||
</span>
|
</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{alert.device.id}"}
|
navigate={~p"/devices/#{alert.device.id}"}
|
||||||
class="text-sm font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm font-medium text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{alert.device.name}
|
{alert.device.name}
|
||||||
</.link>
|
</.link>
|
||||||
<span class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
<span class="text-xs font-mono text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{alert.device.ip_address}
|
{alert.device.ip_address}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<%!-- Site --%>
|
<%!-- Site --%>
|
||||||
<td class="px-3 py-2 whitespace-nowrap text-xs text-gray-600 dark:text-gray-400">
|
<td class="px-3 py-2 whitespace-nowrap text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if alert.device.site do %>
|
<%= if alert.device.site do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{alert.device.site.id}"}
|
navigate={~p"/sites/#{alert.device.site.id}"}
|
||||||
class="hover:text-blue-600 dark:hover:text-blue-400"
|
class="hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{alert.device.site.name}
|
{alert.device.site.name}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-600">—</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-600">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<%!-- Message --%>
|
<%!-- Message --%>
|
||||||
<td
|
<td
|
||||||
class="px-3 py-2 text-xs text-gray-600 dark:text-gray-400 max-w-xs truncate"
|
class="px-3 py-2 text-xs text-cool-steel-600 dark:text-cool-steel-400 max-w-xs truncate"
|
||||||
title={alert.message}
|
title={alert.message}
|
||||||
>
|
>
|
||||||
{alert.message || "—"}
|
{alert.message || "—"}
|
||||||
|
|
@ -331,10 +334,10 @@
|
||||||
<span class={[
|
<span class={[
|
||||||
"text-xs font-mono",
|
"text-xs font-mono",
|
||||||
severity_color(alert) == "red" &&
|
severity_color(alert) == "red" &&
|
||||||
"text-red-600 dark:text-red-400 font-medium",
|
"text-sweet-salmon-600 dark:text-sweet-salmon-400 font-medium",
|
||||||
severity_color(alert) == "orange" && "text-orange-600 dark:text-orange-400",
|
severity_color(alert) == "orange" && "text-orange-600 dark:text-orange-400",
|
||||||
severity_color(alert) not in ["red", "orange"] &&
|
severity_color(alert) not in ["red", "orange"] &&
|
||||||
"text-gray-600 dark:text-gray-400"
|
"text-cool-steel-600 dark:text-cool-steel-400"
|
||||||
]}>
|
]}>
|
||||||
{duration_text(alert)}
|
{duration_text(alert)}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -343,12 +346,12 @@
|
||||||
<%!-- Impact --%>
|
<%!-- Impact --%>
|
||||||
<td class="px-3 py-2 whitespace-nowrap">
|
<td class="px-3 py-2 whitespace-nowrap">
|
||||||
<%= if impact = impact_text(alert, @site_subscribers) do %>
|
<%= if impact = impact_text(alert, @site_subscribers) do %>
|
||||||
<span class="inline-flex items-center gap-1 text-xs text-amber-700 dark:text-amber-400">
|
<span class="inline-flex items-center gap-1 text-xs text-wheat-700 dark:text-wheat-400">
|
||||||
<.icon name="hero-users" class="h-3 w-3" />
|
<.icon name="hero-users" class="h-3 w-3" />
|
||||||
{impact}
|
{impact}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-xs text-gray-300 dark:text-gray-600">—</span>
|
<span class="text-xs text-cool-steel-300 dark:text-cool-steel-600">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
|
@ -356,12 +359,12 @@
|
||||||
<td class="px-3 py-2 whitespace-nowrap text-right">
|
<td class="px-3 py-2 whitespace-nowrap text-right">
|
||||||
<div class="flex items-center justify-end gap-1">
|
<div class="flex items-center justify-end gap-1">
|
||||||
<%= if alert.acknowledged_at && is_nil(alert.resolved_at) do %>
|
<%= if alert.acknowledged_at && is_nil(alert.resolved_at) do %>
|
||||||
<span class="text-xs text-blue-600 dark:text-blue-400 px-1.5 py-0.5 rounded bg-blue-50 dark:bg-blue-900/20">
|
<span class="text-xs text-cerulean-600 dark:text-cerulean-400 px-1.5 py-0.5 rounded bg-cerulean-50 dark:bg-cerulean-900/20">
|
||||||
Ack'd
|
Ack'd
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if alert.resolved_at do %>
|
<%= if alert.resolved_at do %>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400 px-1.5 py-0.5 rounded bg-cool-steel-100 dark:bg-cool-steel-800">
|
||||||
{t("Resolved")}
|
{t("Resolved")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -370,7 +373,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="acknowledge"
|
phx-click="acknowledge"
|
||||||
phx-value-id={alert.id}
|
phx-value-id={alert.id}
|
||||||
class="text-xs px-2 py-1 rounded bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:hover:bg-blue-900/50 transition-colors"
|
class="text-xs px-2 py-1 rounded bg-cerulean-100 text-cerulean-700 hover:bg-cerulean-200 dark:bg-cerulean-900/30 dark:text-cerulean-300 dark:hover:bg-cerulean-900/50 transition-colors"
|
||||||
>
|
>
|
||||||
Ack
|
Ack
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -381,7 +384,7 @@
|
||||||
phx-click="resolve"
|
phx-click="resolve"
|
||||||
phx-value-id={alert.id}
|
phx-value-id={alert.id}
|
||||||
data-confirm={t("Resolve this alert?")}
|
data-confirm={t("Resolve this alert?")}
|
||||||
class="text-xs px-2 py-1 rounded bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
|
class="text-xs px-2 py-1 rounded bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
Resolve
|
Resolve
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -392,22 +395,22 @@
|
||||||
|
|
||||||
<%!-- Expanded alert detail --%>
|
<%!-- Expanded alert detail --%>
|
||||||
<%= if @expanded_alert == alert.id do %>
|
<%= if @expanded_alert == alert.id do %>
|
||||||
<tr class="bg-gray-50 dark:bg-gray-800/40">
|
<tr class="bg-cool-steel-50 dark:bg-cool-steel-800/40">
|
||||||
<td colspan="8" class="px-4 py-3">
|
<td colspan="8" class="px-4 py-3">
|
||||||
<div class="flex gap-8 text-xs">
|
<div class="flex gap-8 text-xs">
|
||||||
<%!-- Timeline --%>
|
<%!-- Timeline --%>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h4 class="font-semibold text-gray-700 dark:text-gray-300 mb-2 uppercase tracking-wider">
|
<h4 class="font-semibold text-cool-steel-700 dark:text-cool-steel-300 mb-2 uppercase tracking-wider">
|
||||||
{t("Timeline")}
|
{t("Timeline")}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block h-2 w-2 rounded-full bg-red-500 flex-shrink-0">
|
<span class="inline-block h-2 w-2 rounded-full bg-sweet-salmon-500 flex-shrink-0">
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-500 dark:text-gray-400 w-20 flex-shrink-0">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 w-20 flex-shrink-0">
|
||||||
{t("Triggered")}
|
{t("Triggered")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-gray-900 dark:text-white">
|
<span class="font-mono text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_iso8601(
|
{ToweropsWeb.TimeHelpers.format_iso8601(
|
||||||
alert.triggered_at,
|
alert.triggered_at,
|
||||||
@timezone
|
@timezone
|
||||||
|
|
@ -416,19 +419,19 @@
|
||||||
</div>
|
</div>
|
||||||
<%= if alert.acknowledged_at do %>
|
<%= if alert.acknowledged_at do %>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block h-2 w-2 rounded-full bg-blue-500 flex-shrink-0">
|
<span class="inline-block h-2 w-2 rounded-full bg-cerulean-500 flex-shrink-0">
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-500 dark:text-gray-400 w-20 flex-shrink-0">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 w-20 flex-shrink-0">
|
||||||
{t("Ack'd")}
|
{t("Ack'd")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-gray-900 dark:text-white">
|
<span class="font-mono text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_iso8601(
|
{ToweropsWeb.TimeHelpers.format_iso8601(
|
||||||
alert.acknowledged_at,
|
alert.acknowledged_at,
|
||||||
@timezone
|
@timezone
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<%= if alert.acknowledged_by do %>
|
<%= if alert.acknowledged_by do %>
|
||||||
<span class="text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
by {alert.acknowledged_by.email}
|
by {alert.acknowledged_by.email}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -438,10 +441,10 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block h-2 w-2 rounded-full bg-green-500 flex-shrink-0">
|
<span class="inline-block h-2 w-2 rounded-full bg-green-500 flex-shrink-0">
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-500 dark:text-gray-400 w-20 flex-shrink-0">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 w-20 flex-shrink-0">
|
||||||
{t("Resolved")}
|
{t("Resolved")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-gray-900 dark:text-white">
|
<span class="font-mono text-cool-steel-900 dark:text-white">
|
||||||
{ToweropsWeb.TimeHelpers.format_iso8601(
|
{ToweropsWeb.TimeHelpers.format_iso8601(
|
||||||
alert.resolved_at,
|
alert.resolved_at,
|
||||||
@timezone
|
@timezone
|
||||||
|
|
@ -455,13 +458,13 @@
|
||||||
<%!-- Impact details --%>
|
<%!-- Impact details --%>
|
||||||
<%= if alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] && alert.gaiia_impact["total_subscribers"] > 0 do %>
|
<%= if alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] && alert.gaiia_impact["total_subscribers"] > 0 do %>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-gray-700 dark:text-gray-300 mb-2 uppercase tracking-wider">
|
<h4 class="font-semibold text-cool-steel-700 dark:text-cool-steel-300 mb-2 uppercase tracking-wider">
|
||||||
{t("Impact")}
|
{t("Impact")}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-users" class="h-3.5 w-3.5 text-amber-500" />
|
<.icon name="hero-users" class="h-3.5 w-3.5 text-wheat-500" />
|
||||||
<span class="text-gray-900 dark:text-white font-medium">
|
<span class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{format_number(alert.gaiia_impact["total_subscribers"])} subscribers affected
|
{format_number(alert.gaiia_impact["total_subscribers"])} subscribers affected
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -470,9 +473,9 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-currency-dollar"
|
name="hero-currency-dollar"
|
||||||
class="h-3.5 w-3.5 text-amber-500"
|
class="h-3.5 w-3.5 text-wheat-500"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-900 dark:text-white font-medium">
|
<span class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
${alert.gaiia_impact["total_mrr"]}/mo at risk
|
${alert.gaiia_impact["total_mrr"]}/mo at risk
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -484,10 +487,12 @@
|
||||||
<%!-- Message --%>
|
<%!-- Message --%>
|
||||||
<%= if alert.message do %>
|
<%= if alert.message do %>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h4 class="font-semibold text-gray-700 dark:text-gray-300 mb-2 uppercase tracking-wider">
|
<h4 class="font-semibold text-cool-steel-700 dark:text-cool-steel-300 mb-2 uppercase tracking-wider">
|
||||||
{t("Details")}
|
{t("Details")}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-gray-600 dark:text-gray-400">{alert.message}</p>
|
<p class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{alert.message}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="relative z-50">
|
<div class="relative z-50">
|
||||||
<div class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-80 transition-opacity">
|
<div class="fixed inset-0 bg-cool-steel-500 dark:bg-cool-steel-900 bg-opacity-75 dark:bg-opacity-80 transition-opacity">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||||
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
<div class="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
<div class="relative transform overflow-hidden rounded-lg bg-white dark:bg-cool-steel-800 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
|
||||||
<.form
|
<.form
|
||||||
for={@form}
|
for={@form}
|
||||||
id="check-form"
|
id="check-form"
|
||||||
|
|
@ -22,16 +22,16 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
phx-change="validate"
|
phx-change="validate"
|
||||||
phx-submit="save"
|
phx-submit="save"
|
||||||
>
|
>
|
||||||
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div class="bg-white dark:bg-cool-steel-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{if @action == :edit, do: "Edit Service Check", else: "Add Service Check"}
|
{if @action == :edit, do: "Edit Service Check", else: "Add Service Check"}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close"
|
phx-click="close"
|
||||||
phx-target={@myself}
|
phx-target={@myself}
|
||||||
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-500 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-6 w-6" />
|
<.icon name="hero-x-mark" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -40,7 +40,7 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Check Type Selection -->
|
<!-- Check Type Selection -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
Check Type
|
Check Type
|
||||||
</label>
|
</label>
|
||||||
<.input
|
<.input
|
||||||
|
|
@ -79,7 +79,7 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
<% "ssl" -> %>
|
<% "ssl" -> %>
|
||||||
{render_ssl_fields(assigns)}
|
{render_ssl_fields(assigns)}
|
||||||
<% _ -> %>
|
<% _ -> %>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Select a check type to configure
|
Select a check type to configure
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -100,7 +100,7 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 gap-3">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-900/50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 gap-3">
|
||||||
<.button
|
<.button
|
||||||
type="submit"
|
type="submit"
|
||||||
phx-disable-with={if @action == :edit, do: "Saving...", else: "Creating..."}
|
phx-disable-with={if @action == :edit, do: "Saving...", else: "Creating..."}
|
||||||
|
|
@ -111,7 +111,7 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close"
|
phx-click="close"
|
||||||
phx-target={@myself}
|
phx-target={@myself}
|
||||||
class="text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -155,8 +155,8 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
<.input field={@form[:follow_redirects]} type="checkbox" label="Follow Redirects" />
|
<.input field={@form[:follow_redirects]} type="checkbox" label="Follow Redirects" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
|
<details class="border border-cool-steel-200 dark:border-cool-steel-700 rounded-lg p-3">
|
||||||
<summary class="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
|
<summary class="cursor-pointer text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Advanced Options
|
Advanced Options
|
||||||
</summary>
|
</summary>
|
||||||
<div class="mt-3 space-y-3">
|
<div class="mt-3 space-y-3">
|
||||||
|
|
@ -264,7 +264,7 @@ defmodule ToweropsWeb.CheckLive.FormComponent do
|
||||||
label="Warning Days"
|
label="Warning Days"
|
||||||
placeholder="30"
|
placeholder="30"
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Alert when certificate expires within this many days
|
Alert when certificate expires within this many days
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,12 @@ defmodule ToweropsWeb.Live.Components.GlobalSearchComponent do
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex min-h-full items-start justify-center p-4 pt-[15vh]">
|
<div class="flex min-h-full items-start justify-center p-4 pt-[15vh]">
|
||||||
<div class="relative w-full max-w-lg transform rounded-xl bg-white shadow-2xl ring-1 ring-black/5 dark:bg-gray-800 dark:ring-white/10">
|
<div class="relative w-full max-w-lg transform rounded-xl bg-white shadow-2xl ring-1 ring-black/5 dark:bg-cool-steel-800 dark:ring-white/10">
|
||||||
<div class="flex items-center gap-3 border-b border-gray-200 px-4 dark:border-white/10">
|
<div class="flex items-center gap-3 border-b border-cool-steel-200 px-4 dark:border-white/10">
|
||||||
<.icon name="hero-magnifying-glass" class="h-5 w-5 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
|
name="hero-magnifying-glass"
|
||||||
|
class="h-5 w-5 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="global-search-input"
|
id="global-search-input"
|
||||||
|
|
@ -93,23 +96,23 @@ defmodule ToweropsWeb.Live.Components.GlobalSearchComponent do
|
||||||
phx-debounce="200"
|
phx-debounce="200"
|
||||||
phx-target={@myself}
|
phx-target={@myself}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex-1 border-0 bg-transparent py-3.5 text-sm text-gray-900 placeholder:text-gray-400 focus:ring-0 dark:text-white dark:placeholder:text-gray-500"
|
class="flex-1 border-0 bg-transparent py-3.5 text-sm text-cool-steel-900 placeholder:text-cool-steel-400 focus:ring-0 dark:text-white dark:placeholder:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<kbd class="hidden sm:inline-flex items-center rounded border border-gray-300 px-1.5 py-0.5 text-xs text-gray-400 dark:border-gray-600 dark:text-gray-500">
|
<kbd class="hidden sm:inline-flex items-center rounded border border-cool-steel-300 px-1.5 py-0.5 text-xs text-cool-steel-400 dark:border-cool-steel-600 dark:text-cool-steel-500">
|
||||||
esc
|
esc
|
||||||
</kbd>
|
</kbd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-h-80 overflow-y-auto px-2 py-2">
|
<div class="max-h-80 overflow-y-auto px-2 py-2">
|
||||||
<%= if @query != "" and map_size(@results) == 0 do %>
|
<%= if @query != "" and map_size(@results) == 0 do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No results found.
|
No results found.
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= for {category, items} <- @results do %>
|
<%= for {category, items} <- @results do %>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<p class="px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<p class="px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{category_label(category)}
|
{category_label(category)}
|
||||||
</p>
|
</p>
|
||||||
<%= for {item, idx} <- Enum.with_index(items) do %>
|
<%= for {item, idx} <- Enum.with_index(items) do %>
|
||||||
|
|
@ -119,9 +122,10 @@ defmodule ToweropsWeb.Live.Components.GlobalSearchComponent do
|
||||||
class={[
|
class={[
|
||||||
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm cursor-pointer",
|
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm cursor-pointer",
|
||||||
if(global_idx == @selected_index,
|
if(global_idx == @selected_index,
|
||||||
do: "bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300",
|
do:
|
||||||
|
"bg-cerulean-50 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-300",
|
||||||
else:
|
else:
|
||||||
"text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5"
|
"text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-white/5"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -141,13 +145,13 @@ defmodule ToweropsWeb.Live.Components.GlobalSearchComponent do
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @query == "" do %>
|
<%= if @query == "" do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Start typing to search...
|
Start typing to search...
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between border-t border-gray-200 px-4 py-2 text-xs text-gray-400 dark:border-white/10 dark:text-gray-500">
|
<div class="flex items-center justify-between border-t border-cool-steel-200 px-4 py-2 text-xs text-cool-steel-400 dark:border-white/10 dark:text-cool-steel-500">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<span><kbd class="font-semibold">↑↓</kbd> navigate</span>
|
<span><kbd class="font-semibold">↑↓</kbd> navigate</span>
|
||||||
<span><kbd class="font-semibold">↵</kbd> open</span>
|
<span><kbd class="font-semibold">↵</kbd> open</span>
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ defmodule ToweropsWeb.ConfigTimelineLive do
|
||||||
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
def change_size_class(size) when is_integer(size) and size > 20,
|
def change_size_class(size) when is_integer(size) and size > 20,
|
||||||
do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
def change_size_class(_), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
def change_size_class(_), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
)}
|
)}
|
||||||
</:subtitle>
|
</:subtitle>
|
||||||
<:actions>
|
<:actions>
|
||||||
<span class="inline-flex items-center px-3 py-1.5 rounded-md border border-blue-200 dark:border-blue-900/50 bg-blue-50 dark:bg-blue-900/20 text-sm">
|
<span class="inline-flex items-center px-3 py-1.5 rounded-md border border-cerulean-200 dark:border-cerulean-900/50 bg-cerulean-50 dark:bg-cerulean-900/20 text-sm">
|
||||||
<span class="text-blue-700 dark:text-blue-300 font-medium mr-2">EIRP:</span>
|
<span class="text-cerulean-700 dark:text-cerulean-300 font-medium mr-2">EIRP:</span>
|
||||||
<span class="text-blue-900 dark:text-blue-100 font-mono">
|
<span class="text-cerulean-900 dark:text-cerulean-100 font-mono">
|
||||||
{Float.round(@computed_eirp, 1)} dBm
|
{Float.round(@computed_eirp, 1)} dBm
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<%!-- Identity --%>
|
<%!-- Identity --%>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Identity")}
|
{t("Identity")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
:if={@form[:site_id].value not in [nil, ""] and @devices == []}
|
:if={@form[:site_id].value not in [nil, ""] and @devices == []}
|
||||||
class="text-xs text-gray-500 dark:text-gray-400"
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("This site has no devices yet — coverage will not be attached to a specific device.")}
|
{t("This site has no devices yet — coverage will not be attached to a specific device.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -86,20 +86,20 @@
|
||||||
<%!-- Radio location: draggable marker on a Leaflet map. --%>
|
<%!-- Radio location: draggable marker on a Leaflet map. --%>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Radio location")}
|
{t("Radio location")}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
:if={radio_overridden?(@form)}
|
:if={radio_overridden?(@form)}
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="use_site_location"
|
phx-click="use_site_location"
|
||||||
class="text-xs text-blue-600 hover:underline dark:text-blue-400"
|
class="text-xs text-cerulean-600 hover:underline dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Use site location")}
|
{t("Use site location")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Drag the marker to where the radio is mounted. Defaults to the site's coordinates.")}
|
{t("Drag the marker to where the radio is mounted. Defaults to the site's coordinates.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
id="coverage-location-picker"
|
id="coverage-location-picker"
|
||||||
phx-hook="CoverageLocationPicker"
|
phx-hook="CoverageLocationPicker"
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
class="h-72 w-full rounded border border-gray-200 dark:border-white/10 bg-gray-100 dark:bg-gray-800"
|
class="h-72 w-full rounded border border-cool-steel-200 dark:border-white/10 bg-cool-steel-100 dark:bg-cool-steel-800"
|
||||||
data-marker-lat={radio_marker_lat(@form, @sites)}
|
data-marker-lat={radio_marker_lat(@form, @sites)}
|
||||||
data-marker-lon={radio_marker_lon(@form, @sites)}
|
data-marker-lon={radio_marker_lon(@form, @sites)}
|
||||||
>
|
>
|
||||||
|
|
@ -115,10 +115,13 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:if={radio_marker_lat(@form, @sites)}
|
:if={radio_marker_lat(@form, @sites)}
|
||||||
class="text-xs font-mono text-gray-600 dark:text-gray-400"
|
class="text-xs font-mono text-cool-steel-600 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{radio_marker_lat(@form, @sites)}, {radio_marker_lon(@form, @sites)}
|
{radio_marker_lat(@form, @sites)}, {radio_marker_lon(@form, @sites)}
|
||||||
<span :if={radio_overridden?(@form)} class="ml-2 text-blue-600 dark:text-blue-400">
|
<span
|
||||||
|
:if={radio_overridden?(@form)}
|
||||||
|
class="ml-2 text-cerulean-600 dark:text-cerulean-400"
|
||||||
|
>
|
||||||
({t("override")})
|
({t("override")})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -129,7 +132,7 @@
|
||||||
|
|
||||||
<%!-- Radio --%>
|
<%!-- Radio --%>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Radio")}
|
{t("Radio")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -166,7 +169,7 @@
|
||||||
|
|
||||||
<%!-- Mounting --%>
|
<%!-- Mounting --%>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Mounting")}
|
{t("Mounting")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -202,14 +205,14 @@
|
||||||
|
|
||||||
<%!-- Foliage --%>
|
<%!-- Foliage --%>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Environment")}
|
{t("Environment")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="coverage_foliage_tuning"
|
for="coverage_foliage_tuning"
|
||||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100 mb-1"
|
class="block text-sm font-medium text-cool-steel-900 dark:text-cool-steel-100 mb-1"
|
||||||
>
|
>
|
||||||
{t("Foliage tuning")}: {Phoenix.HTML.Form.input_value(@form, :foliage_tuning) || 0}
|
{t("Foliage tuning")}: {Phoenix.HTML.Form.input_value(@form, :foliage_tuning) || 0}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -223,7 +226,7 @@
|
||||||
step="1"
|
step="1"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-1">
|
||||||
{t("Higher values reduce predicted signal in wooded areas.")}
|
{t("Higher values reduce predicted signal in wooded areas.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,17 @@ defmodule ToweropsWeb.CoverageLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def status_badge_class("draft"), do: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
def status_badge_class("draft"),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def status_badge_class("queued"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300"
|
def status_badge_class("queued"), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-300"
|
||||||
|
|
||||||
def status_badge_class("computing"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300"
|
def status_badge_class("computing"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-300"
|
||||||
|
|
||||||
def status_badge_class("ready"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
def status_badge_class("ready"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
||||||
|
|
||||||
def status_badge_class("failed"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300"
|
def status_badge_class("failed"),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-300"
|
||||||
|
|
||||||
def status_badge_class(_), do: "bg-gray-100 text-gray-700"
|
def status_badge_class(_), do: "bg-cool-steel-100 text-cool-steel-700"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,16 @@
|
||||||
<div class="flex items-center justify-center py-16">
|
<div class="flex items-center justify-center py-16">
|
||||||
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
||||||
<div class="items-center text-center">
|
<div class="items-center text-center">
|
||||||
<div class="rounded-full bg-blue-100 dark:bg-blue-900/40 p-4 mb-2">
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
||||||
<.icon name="hero-signal" class="h-12 w-12 text-blue-500 dark:text-blue-400" />
|
<.icon
|
||||||
|
name="hero-signal"
|
||||||
|
class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Add a site first")}
|
{t("Add a site first")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Coverage predictions are computed from a site's location. Add a site, then return here to plan its coverage."
|
"Coverage predictions are computed from a site's location. Add a site, then return here to plan its coverage."
|
||||||
)}
|
)}
|
||||||
|
|
@ -46,13 +49,16 @@
|
||||||
<div class="flex items-center justify-center py-16">
|
<div class="flex items-center justify-center py-16">
|
||||||
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
||||||
<div class="items-center text-center">
|
<div class="items-center text-center">
|
||||||
<div class="rounded-full bg-blue-100 dark:bg-blue-900/40 p-4 mb-2">
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
||||||
<.icon name="hero-signal" class="h-12 w-12 text-blue-500 dark:text-blue-400" />
|
<.icon
|
||||||
|
name="hero-signal"
|
||||||
|
class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No coverages yet")}
|
{t("No coverages yet")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Create your first coverage to predict where a tower's antenna reaches.")}
|
{t("Create your first coverage to predict where a tower's antenna reaches.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 flex gap-2">
|
<div class="mt-4 flex gap-2">
|
||||||
|
|
@ -64,65 +70,65 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% true -> %>
|
<% true -> %>
|
||||||
<div class="mt-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="mt-6 overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table
|
<table
|
||||||
id="coverages-table"
|
id="coverages-table"
|
||||||
class="min-w-full divide-y divide-gray-200 dark:divide-white/10"
|
class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10"
|
||||||
>
|
>
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/80">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/80">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Name")}
|
{t("Name")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Site")}
|
{t("Site")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Antenna")}
|
{t("Antenna")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Freq (GHz)")}
|
{t("Freq (GHz)")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("TX (dBm)")}
|
{t("TX (dBm)")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Range (mi)")}
|
{t("Range (mi)")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200 dark:bg-gray-900 dark:divide-white/10">
|
<tbody class="bg-white divide-y divide-cool-steel-200 dark:bg-cool-steel-900 dark:divide-white/10">
|
||||||
<%= for coverage <- @coverages do %>
|
<%= for coverage <- @coverages do %>
|
||||||
<tr id={"coverage-#{coverage.id}"}>
|
<tr id={"coverage-#{coverage.id}"}>
|
||||||
<td class="px-4 py-2 text-sm">
|
<td class="px-4 py-2 text-sm">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/coverage/#{coverage.id}"}
|
navigate={~p"/coverage/#{coverage.id}"}
|
||||||
class="font-medium text-blue-600 hover:underline dark:text-blue-400"
|
class="font-medium text-cerulean-600 hover:underline dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{coverage.name}
|
{coverage.name}
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300">
|
<td class="px-4 py-2 text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<.link navigate={~p"/sites/#{coverage.site_id}"} class="hover:underline">
|
<.link navigate={~p"/sites/#{coverage.site_id}"} class="hover:underline">
|
||||||
{coverage.site && coverage.site.name}
|
{coverage.site && coverage.site.name}
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300">
|
<td class="px-4 py-2 text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<span class="font-mono text-xs">{coverage.antenna_slug}</span>
|
<span class="font-mono text-xs">{coverage.antenna_slug}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-right text-gray-700 dark:text-gray-300 font-mono">
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300 font-mono">
|
||||||
{Float.round(coverage.frequency_mhz / 1000.0, 3)}
|
{Float.round(coverage.frequency_mhz / 1000.0, 3)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-right text-gray-700 dark:text-gray-300">
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{coverage.tx_power_dbm}
|
{coverage.tx_power_dbm}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm text-right text-gray-700 dark:text-gray-300 font-mono">
|
<td class="px-4 py-2 text-sm text-right text-cool-steel-700 dark:text-cool-steel-300 font-mono">
|
||||||
{Float.round(coverage.radius_m / 1609.344, 2)}
|
{Float.round(coverage.radius_m / 1609.344, 2)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-sm">
|
<td class="px-4 py-2 text-sm">
|
||||||
|
|
@ -137,7 +143,7 @@
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/coverage/#{coverage.id}/edit"}
|
navigate={~p"/coverage/#{coverage.id}/edit"}
|
||||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
class="text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
<.icon name="hero-pencil-square" class="h-4 w-4" />
|
<.icon name="hero-pencil-square" class="h-4 w-4" />
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -145,7 +151,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="recompute"
|
phx-click="recompute"
|
||||||
phx-value-id={coverage.id}
|
phx-value-id={coverage.id}
|
||||||
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
title={t("Recompute")}
|
title={t("Recompute")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
||||||
|
|
@ -155,7 +161,7 @@
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
phx-value-id={coverage.id}
|
phx-value-id={coverage.id}
|
||||||
data-confirm={t("Delete this coverage?")}
|
data-confirm={t("Delete this coverage?")}
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
title={t("Delete")}
|
title={t("Delete")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@
|
||||||
<div class="mt-6 flex items-center justify-center py-16">
|
<div class="mt-6 flex items-center justify-center py-16">
|
||||||
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
||||||
<div class="items-center text-center">
|
<div class="items-center text-center">
|
||||||
<div class="rounded-full bg-blue-100 dark:bg-blue-900/40 p-4 mb-2">
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
||||||
<.icon name="hero-signal" class="h-12 w-12 text-blue-500 dark:text-blue-400" />
|
<.icon name="hero-signal" class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No ready coverages")}
|
{t("No ready coverages")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Coverage maps appear here once the background compute finishes. Create or wait for a coverage to finish."
|
"Coverage maps appear here once the background compute finishes. Create or wait for a coverage to finish."
|
||||||
)}
|
)}
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
id="coverage-multi-map"
|
id="coverage-multi-map"
|
||||||
phx-hook="MultiCoverageMap"
|
phx-hook="MultiCoverageMap"
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
class="relative mt-6 h-[78vh] w-full rounded-lg border border-gray-200 dark:border-white/10 bg-gray-100 dark:bg-gray-800 overflow-hidden"
|
class="relative mt-6 h-[78vh] w-full rounded-lg border border-cool-steel-200 dark:border-white/10 bg-cool-steel-100 dark:bg-cool-steel-800 overflow-hidden"
|
||||||
data-coverages={Jason.encode!(coverages_payload(@coverages))}
|
data-coverages={Jason.encode!(coverages_payload(@coverages))}
|
||||||
data-enabled={Jason.encode!(@enabled_ids)}
|
data-enabled={Jason.encode!(@enabled_ids)}
|
||||||
>
|
>
|
||||||
|
|
@ -61,15 +61,15 @@
|
||||||
class="pointer-events-none fixed top-32 left-1/2 -translate-x-[640px] z-[1100]"
|
class="pointer-events-none fixed top-32 left-1/2 -translate-x-[640px] z-[1100]"
|
||||||
id="coverage-height-control"
|
id="coverage-height-control"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg p-3 flex flex-col items-center gap-2 w-24">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg p-3 flex flex-col items-center gap-2 w-24">
|
||||||
<%!-- LOS vs NLOS mode toggle. The label and reference frame
|
<%!-- LOS vs NLOS mode toggle. The label and reference frame
|
||||||
of the height slider switch with the mode. --%>
|
of the height slider switch with the mode. --%>
|
||||||
<div class="flex w-full text-[10px] font-medium border border-gray-200 dark:border-white/10 rounded overflow-hidden">
|
<div class="flex w-full text-[10px] font-medium border border-cool-steel-200 dark:border-white/10 rounded overflow-hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="coverage-mode-los"
|
id="coverage-mode-los"
|
||||||
data-mode="los"
|
data-mode="los"
|
||||||
class="flex-1 px-1 py-1 text-gray-900 dark:text-white bg-blue-100 dark:bg-blue-900/40"
|
class="flex-1 px-1 py-1 text-cool-steel-900 dark:text-white bg-cerulean-100 dark:bg-cerulean-900/40"
|
||||||
>
|
>
|
||||||
LOS
|
LOS
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -77,14 +77,14 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="coverage-mode-nlos"
|
id="coverage-mode-nlos"
|
||||||
data-mode="nlos"
|
data-mode="nlos"
|
||||||
class="flex-1 px-1 py-1 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"
|
class="flex-1 px-1 py-1 text-cool-steel-700 dark:text-cool-steel-300 hover:bg-cool-steel-100 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
NLOS
|
NLOS
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
id="coverage-mode-label"
|
id="coverage-mode-label"
|
||||||
class="text-[10px] font-medium text-gray-700 dark:text-gray-300 text-center leading-tight"
|
class="text-[10px] font-medium text-cool-steel-700 dark:text-cool-steel-300 text-center leading-tight"
|
||||||
>
|
>
|
||||||
{t("Height above clutter")}
|
{t("Height above clutter")}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -107,21 +107,21 @@
|
||||||
class="pointer-events-none fixed top-32 right-6 z-[1100] w-72 max-h-[60vh] overflow-y-auto"
|
class="pointer-events-none fixed top-32 right-6 z-[1100] w-72 max-h-[60vh] overflow-y-auto"
|
||||||
id="coverage-tower-list"
|
id="coverage-tower-list"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">{t("Towers")}</h3>
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">{t("Towers")}</h3>
|
||||||
<div class="flex gap-1.5">
|
<div class="flex gap-1.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="show_all"
|
phx-click="show_all"
|
||||||
class="text-xs px-1.5 py-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300"
|
class="text-xs px-1.5 py-0.5 rounded hover:bg-cool-steel-100 dark:hover:bg-cool-steel-800 text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t("Show All")}
|
{t("Show All")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="hide_all"
|
phx-click="hide_all"
|
||||||
class="text-xs px-1.5 py-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300"
|
class="text-xs px-1.5 py-0.5 rounded hover:bg-cool-steel-100 dark:hover:bg-cool-steel-800 text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t("Hide All")}
|
{t("Hide All")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -135,10 +135,10 @@
|
||||||
phx-click="toggle_coverage"
|
phx-click="toggle_coverage"
|
||||||
phx-value-id={c.id}
|
phx-value-id={c.id}
|
||||||
class={[
|
class={[
|
||||||
"w-full text-left flex items-center gap-2 px-3 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-800",
|
"w-full text-left flex items-center gap-2 px-3 py-1.5 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800",
|
||||||
if(c.id in @enabled_ids,
|
if(c.id in @enabled_ids,
|
||||||
do: "text-gray-900 dark:text-white font-medium",
|
do: "text-cool-steel-900 dark:text-white font-medium",
|
||||||
else: "text-gray-400 dark:text-gray-500"
|
else: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -146,12 +146,12 @@
|
||||||
"inline-block h-2.5 w-2.5 rounded-full",
|
"inline-block h-2.5 w-2.5 rounded-full",
|
||||||
if(c.id in @enabled_ids,
|
if(c.id in @enabled_ids,
|
||||||
do: "bg-green-500",
|
do: "bg-green-500",
|
||||||
else: "bg-gray-300 dark:bg-gray-600"
|
else: "bg-cool-steel-300 dark:bg-cool-steel-600"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm truncate flex-1">{c.name}</span>
|
<span class="text-sm truncate flex-1">{c.name}</span>
|
||||||
<span class="text-[10px] text-gray-500 dark:text-gray-400 truncate">
|
<span class="text-[10px] text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{c.site && c.site.name}
|
{c.site && c.site.name}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -163,11 +163,11 @@
|
||||||
|
|
||||||
<%!-- Opacity slider + legend (bottom-center, overlay above map) --%>
|
<%!-- Opacity slider + legend (bottom-center, overlay above map) --%>
|
||||||
<div class="pointer-events-none fixed bottom-6 left-1/2 -translate-x-1/2 z-[1100] w-[640px] max-w-[90vw]">
|
<div class="pointer-events-none fixed bottom-6 left-1/2 -translate-x-1/2 z-[1100] w-[640px] max-w-[90vw]">
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg p-3 space-y-2">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg p-3 space-y-2">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label
|
<label
|
||||||
for="coverage-opacity-slider"
|
for="coverage-opacity-slider"
|
||||||
class="text-xs font-medium text-gray-700 dark:text-gray-300 w-16"
|
class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 w-16"
|
||||||
>
|
>
|
||||||
{t("Opacity")}
|
{t("Opacity")}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -185,7 +185,7 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label
|
<label
|
||||||
for="coverage-rssi-slider"
|
for="coverage-rssi-slider"
|
||||||
class="text-xs font-medium text-gray-700 dark:text-gray-300 w-16"
|
class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 w-16"
|
||||||
>
|
>
|
||||||
{t("Min RSSI")}
|
{t("Min RSSI")}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -200,7 +200,7 @@
|
||||||
/>
|
/>
|
||||||
<span id="coverage-rssi-label" class="text-xs font-mono w-16 text-right">-100 dBm</span>
|
<span id="coverage-rssi-label" class="text-xs font-mono w-16 text-right">-100 dBm</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-[10px] font-mono text-gray-700 dark:text-gray-300">
|
<div class="flex items-center gap-1 text-[10px] font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<span class="w-3 h-3 rounded inline-block" style="background:rgb(0,200,0)"></span>
|
<span class="w-3 h-3 rounded inline-block" style="background:rgb(0,200,0)"></span>
|
||||||
<span>≥-50</span>
|
<span>≥-50</span>
|
||||||
<span class="w-3 h-3 rounded inline-block ml-2" style="background:rgb(200,230,0)">
|
<span class="w-3 h-3 rounded inline-block ml-2" style="background:rgb(200,230,0)">
|
||||||
|
|
@ -224,10 +224,10 @@
|
||||||
class="pointer-events-none fixed top-32 left-6 z-[1100] w-[420px] max-w-[90vw] max-h-[60vh] overflow-y-auto"
|
class="pointer-events-none fixed top-32 left-6 z-[1100] w-[420px] max-w-[90vw] max-h-[60vh] overflow-y-auto"
|
||||||
id="coverage-probe-panel"
|
id="coverage-probe-panel"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="flex items-center gap-2 text-xs font-mono text-gray-700 dark:text-gray-300">
|
<div class="flex items-center gap-2 text-xs font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<.icon name="hero-map-pin" class="h-4 w-4 text-blue-500" />
|
<.icon name="hero-map-pin" class="h-4 w-4 text-cerulean-500" />
|
||||||
<span>
|
<span>
|
||||||
{:erlang.float_to_binary(@probe.lat * 1.0, decimals: 5)}, {:erlang.float_to_binary(
|
{:erlang.float_to_binary(@probe.lat * 1.0, decimals: 5)}, {:erlang.float_to_binary(
|
||||||
@probe.lon * 1.0,
|
@probe.lon * 1.0,
|
||||||
|
|
@ -238,13 +238,13 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close_probe"
|
phx-click="close_probe"
|
||||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400"
|
class="text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4" />
|
<.icon name="hero-x-mark" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-3 py-2 border-b border-gray-200 dark:border-white/10 flex items-center gap-3">
|
<div class="px-3 py-2 border-b border-cool-steel-200 dark:border-white/10 flex items-center gap-3">
|
||||||
<span class="text-xs text-gray-700 dark:text-gray-300">{t("Units:")}</span>
|
<span class="text-xs text-cool-steel-700 dark:text-cool-steel-300">{t("Units:")}</span>
|
||||||
<label class="flex items-center gap-1 text-xs cursor-pointer">
|
<label class="flex items-center gap-1 text-xs cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|
@ -265,45 +265,45 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<table class="min-w-full text-xs">
|
<table class="min-w-full text-xs">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-3 py-1.5 text-left font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-left font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Tower")}
|
{t("Tower")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-right font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Distance")}
|
{t("Distance")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-right font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
RSSI
|
RSSI
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for row <- @probe.rows do %>
|
<%= for row <- @probe.rows do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-3 py-1.5 font-medium text-gray-900 dark:text-gray-100 truncate">
|
<td class="px-3 py-1.5 font-medium text-cool-steel-900 dark:text-cool-steel-100 truncate">
|
||||||
{row.coverage.name}
|
{row.coverage.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right font-mono text-gray-700 dark:text-gray-300">
|
<td class="px-3 py-1.5 text-right font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{fmt_distance(row.distance_m, @probe_units)}
|
{fmt_distance(row.distance_m, @probe_units)}
|
||||||
</td>
|
</td>
|
||||||
<td class={[
|
<td class={[
|
||||||
"px-3 py-1.5 text-right font-mono",
|
"px-3 py-1.5 text-right font-mono",
|
||||||
cond do
|
cond do
|
||||||
row.rssi == :no_coverage ->
|
row.rssi == :no_coverage ->
|
||||||
"text-gray-400"
|
"text-cool-steel-400"
|
||||||
|
|
||||||
is_number(row.rssi) and row.rssi >= -75 ->
|
is_number(row.rssi) and row.rssi >= -75 ->
|
||||||
"text-green-600 dark:text-green-400 font-bold"
|
"text-green-600 dark:text-green-400 font-bold"
|
||||||
|
|
||||||
is_number(row.rssi) and row.rssi >= -85 ->
|
is_number(row.rssi) and row.rssi >= -85 ->
|
||||||
"text-yellow-600 dark:text-yellow-400"
|
"text-wheat-600 dark:text-wheat-400"
|
||||||
|
|
||||||
is_number(row.rssi) ->
|
is_number(row.rssi) ->
|
||||||
"text-red-600 dark:text-red-400"
|
"text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"text-gray-400"
|
"text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{fmt_rssi(row.rssi)}
|
{fmt_rssi(row.rssi)}
|
||||||
|
|
|
||||||
|
|
@ -120,17 +120,19 @@ defmodule ToweropsWeb.CoverageLive.Show do
|
||||||
defdelegate fmt_rssi(v), to: CoverageMapView
|
defdelegate fmt_rssi(v), to: CoverageMapView
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def status_badge_class("draft"), do: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
def status_badge_class("draft"),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def status_badge_class("queued"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300"
|
def status_badge_class("queued"), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-300"
|
||||||
|
|
||||||
def status_badge_class("computing"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300"
|
def status_badge_class("computing"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-300"
|
||||||
|
|
||||||
def status_badge_class("ready"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
def status_badge_class("ready"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
||||||
|
|
||||||
def status_badge_class("failed"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300"
|
def status_badge_class("failed"),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-300"
|
||||||
|
|
||||||
def status_badge_class(_), do: "bg-gray-100 text-gray-700"
|
def status_badge_class(_), do: "bg-cool-steel-100 text-cool-steel-700"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def coverage_lat(coverage) do
|
def coverage_lat(coverage) do
|
||||||
|
|
@ -166,8 +168,8 @@ defmodule ToweropsWeb.CoverageLive.Show do
|
||||||
def param_row(assigns) do
|
def param_row(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex justify-between gap-3">
|
<div class="flex justify-between gap-3">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">{@label}</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">{@label}</dt>
|
||||||
<dd class="text-gray-900 dark:text-gray-100 font-medium text-right">{@value}</dd>
|
<dd class="text-cool-steel-900 dark:text-cool-steel-100 font-medium text-right">{@value}</dd>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@
|
||||||
%{label: t("Coverage"), navigate: ~p"/coverage"},
|
%{label: t("Coverage"), navigate: ~p"/coverage"},
|
||||||
%{label: @coverage.name}
|
%{label: @coverage.name}
|
||||||
]} />
|
]} />
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white truncate">
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white truncate">
|
||||||
{@coverage.name}
|
{@coverage.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-map-pin" class="h-4 w-4 inline" />
|
<.icon name="hero-map-pin" class="h-4 w-4 inline" />
|
||||||
<.link navigate={~p"/sites/#{@coverage.site_id}"} class="hover:underline">
|
<.link navigate={~p"/sites/#{@coverage.site_id}"} class="hover:underline">
|
||||||
{@coverage.site && @coverage.site.name}
|
{@coverage.site && @coverage.site.name}
|
||||||
|
|
@ -53,25 +53,25 @@
|
||||||
|
|
||||||
<%!-- Status banners (queued / computing / failed). --%>
|
<%!-- Status banners (queued / computing / failed). --%>
|
||||||
<%= if @coverage.status == "computing" do %>
|
<%= if @coverage.status == "computing" do %>
|
||||||
<div class="mb-4 rounded-lg border border-yellow-200 bg-yellow-50 dark:border-yellow-900/50 dark:bg-yellow-900/20 p-4">
|
<div class="mb-4 rounded-lg border border-wheat-200 bg-wheat-50 dark:border-wheat-900/50 dark:bg-wheat-900/20 p-4">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-path"
|
name="hero-arrow-path"
|
||||||
class="h-5 w-5 text-yellow-700 dark:text-yellow-400 animate-spin shrink-0"
|
class="h-5 w-5 text-wheat-700 dark:text-wheat-400 animate-spin shrink-0"
|
||||||
/>
|
/>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-sm font-medium text-yellow-900 dark:text-yellow-200">
|
<p class="text-sm font-medium text-wheat-900 dark:text-wheat-200">
|
||||||
{t("Computing coverage…")}
|
{t("Computing coverage…")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-1 flex items-center gap-3">
|
<div class="mt-1 flex items-center gap-3">
|
||||||
<div class="flex-1 h-2 bg-yellow-200 dark:bg-yellow-900 rounded-full overflow-hidden">
|
<div class="flex-1 h-2 bg-wheat-200 dark:bg-wheat-900 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="h-full bg-yellow-600 dark:bg-yellow-500 transition-all"
|
class="h-full bg-wheat-600 dark:bg-wheat-500 transition-all"
|
||||||
style={"width: #{@coverage.progress_pct}%"}
|
style={"width: #{@coverage.progress_pct}%"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-yellow-800 dark:text-yellow-300 font-mono shrink-0">
|
<span class="text-sm text-wheat-800 dark:text-wheat-300 font-mono shrink-0">
|
||||||
{@coverage.progress_pct}%
|
{@coverage.progress_pct}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -81,17 +81,20 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @coverage.status == "failed" do %>
|
<%= if @coverage.status == "failed" do %>
|
||||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 dark:border-red-900/50 dark:bg-red-900/20 p-4">
|
<div class="mb-4 rounded-lg border border-sweet-salmon-200 bg-sweet-salmon-50 dark:border-sweet-salmon-900/50 dark:bg-sweet-salmon-900/20 p-4">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-exclamation-triangle"
|
name="hero-exclamation-triangle"
|
||||||
class="h-5 w-5 text-red-700 dark:text-red-400 flex-shrink-0 mt-0.5"
|
class="h-5 w-5 text-sweet-salmon-700 dark:text-sweet-salmon-400 flex-shrink-0 mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-red-900 dark:text-red-200">
|
<p class="text-sm font-medium text-sweet-salmon-900 dark:text-sweet-salmon-200">
|
||||||
{t("Compute failed")}
|
{t("Compute failed")}
|
||||||
</p>
|
</p>
|
||||||
<p :if={@coverage.error_message} class="mt-1 text-sm text-red-800 dark:text-red-300">
|
<p
|
||||||
|
:if={@coverage.error_message}
|
||||||
|
class="mt-1 text-sm text-sweet-salmon-800 dark:text-sweet-salmon-300"
|
||||||
|
>
|
||||||
{@coverage.error_message}
|
{@coverage.error_message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,8 +103,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @coverage.status == "queued" do %>
|
<%= if @coverage.status == "queued" do %>
|
||||||
<div class="mb-4 rounded-lg border border-blue-200 bg-blue-50 dark:border-blue-900/50 dark:bg-blue-900/20 p-4">
|
<div class="mb-4 rounded-lg border border-cerulean-200 bg-cerulean-50 dark:border-cerulean-900/50 dark:bg-cerulean-900/20 p-4">
|
||||||
<p class="text-sm text-blue-900 dark:text-blue-200">
|
<p class="text-sm text-cerulean-900 dark:text-cerulean-200">
|
||||||
<.icon name="hero-clock" class="h-4 w-4 inline" />
|
<.icon name="hero-clock" class="h-4 w-4 inline" />
|
||||||
{t("Coverage is queued. Compute will start shortly.")}
|
{t("Coverage is queued. Compute will start shortly.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -115,7 +118,7 @@
|
||||||
id="coverage-multi-map"
|
id="coverage-multi-map"
|
||||||
phx-hook="MultiCoverageMap"
|
phx-hook="MultiCoverageMap"
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
class="relative h-[78vh] w-full rounded-lg border border-gray-200 dark:border-white/10 bg-gray-100 dark:bg-gray-800 overflow-hidden"
|
class="relative h-[78vh] w-full rounded-lg border border-cool-steel-200 dark:border-white/10 bg-cool-steel-100 dark:bg-cool-steel-800 overflow-hidden"
|
||||||
data-coverages={Jason.encode!(coverages_payload([@coverage]))}
|
data-coverages={Jason.encode!(coverages_payload([@coverage]))}
|
||||||
data-enabled={Jason.encode!([@coverage.id])}
|
data-enabled={Jason.encode!([@coverage.id])}
|
||||||
>
|
>
|
||||||
|
|
@ -126,13 +129,13 @@
|
||||||
class="pointer-events-none fixed top-32 left-6 z-[1100]"
|
class="pointer-events-none fixed top-32 left-6 z-[1100]"
|
||||||
id="coverage-height-control"
|
id="coverage-height-control"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg p-3 flex flex-col items-center gap-2 w-24">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg p-3 flex flex-col items-center gap-2 w-24">
|
||||||
<div class="flex w-full text-[10px] font-medium border border-gray-200 dark:border-white/10 rounded overflow-hidden">
|
<div class="flex w-full text-[10px] font-medium border border-cool-steel-200 dark:border-white/10 rounded overflow-hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="coverage-mode-los"
|
id="coverage-mode-los"
|
||||||
data-mode="los"
|
data-mode="los"
|
||||||
class="flex-1 px-1 py-1 text-gray-900 dark:text-white bg-blue-100 dark:bg-blue-900/40"
|
class="flex-1 px-1 py-1 text-cool-steel-900 dark:text-white bg-cerulean-100 dark:bg-cerulean-900/40"
|
||||||
>
|
>
|
||||||
LOS
|
LOS
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -140,14 +143,14 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="coverage-mode-nlos"
|
id="coverage-mode-nlos"
|
||||||
data-mode="nlos"
|
data-mode="nlos"
|
||||||
class="flex-1 px-1 py-1 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"
|
class="flex-1 px-1 py-1 text-cool-steel-700 dark:text-cool-steel-300 hover:bg-cool-steel-100 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
NLOS
|
NLOS
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
id="coverage-mode-label"
|
id="coverage-mode-label"
|
||||||
class="text-[10px] font-medium text-gray-700 dark:text-gray-300 text-center leading-tight"
|
class="text-[10px] font-medium text-cool-steel-700 dark:text-cool-steel-300 text-center leading-tight"
|
||||||
>
|
>
|
||||||
{t("Height above clutter")}
|
{t("Height above clutter")}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -171,15 +174,15 @@
|
||||||
class="pointer-events-none fixed top-32 right-6 z-[1100] w-80 max-h-[70vh] overflow-y-auto"
|
class="pointer-events-none fixed top-32 right-6 z-[1100] w-80 max-h-[70vh] overflow-y-auto"
|
||||||
id="coverage-params-panel"
|
id="coverage-params-panel"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Parameters")}
|
{t("Parameters")}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_params"
|
phx-click="toggle_params"
|
||||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400"
|
class="text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4" />
|
<.icon name="hero-x-mark" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -233,7 +236,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
data-confirm={t("Delete this coverage?")}
|
data-confirm={t("Delete this coverage?")}
|
||||||
class="mt-3 w-full text-sm text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
class="mt-3 w-full text-sm text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4 inline" /> {t("Delete coverage")}
|
<.icon name="hero-trash" class="h-4 w-4 inline" /> {t("Delete coverage")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -244,11 +247,11 @@
|
||||||
|
|
||||||
<%!-- Opacity + RSSI filter + legend (bottom-center). --%>
|
<%!-- Opacity + RSSI filter + legend (bottom-center). --%>
|
||||||
<div class="pointer-events-none fixed bottom-6 left-1/2 -translate-x-1/2 z-[1100] w-[640px] max-w-[90vw]">
|
<div class="pointer-events-none fixed bottom-6 left-1/2 -translate-x-1/2 z-[1100] w-[640px] max-w-[90vw]">
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg p-3 space-y-2">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg p-3 space-y-2">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label
|
<label
|
||||||
for="coverage-opacity-slider"
|
for="coverage-opacity-slider"
|
||||||
class="text-xs font-medium text-gray-700 dark:text-gray-300 w-16"
|
class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 w-16"
|
||||||
>
|
>
|
||||||
{t("Opacity")}
|
{t("Opacity")}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -265,7 +268,7 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label
|
<label
|
||||||
for="coverage-rssi-slider"
|
for="coverage-rssi-slider"
|
||||||
class="text-xs font-medium text-gray-700 dark:text-gray-300 w-16"
|
class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 w-16"
|
||||||
>
|
>
|
||||||
{t("Min RSSI")}
|
{t("Min RSSI")}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -282,7 +285,7 @@
|
||||||
-100 dBm
|
-100 dBm
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 text-[10px] font-mono text-gray-700 dark:text-gray-300">
|
<div class="flex items-center gap-1 text-[10px] font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<span class="w-3 h-3 rounded inline-block" style="background:rgb(0,200,0)"></span>
|
<span class="w-3 h-3 rounded inline-block" style="background:rgb(0,200,0)"></span>
|
||||||
<span>≥-50</span>
|
<span>≥-50</span>
|
||||||
<span class="w-3 h-3 rounded inline-block ml-2" style="background:rgb(200,230,0)">
|
<span class="w-3 h-3 rounded inline-block ml-2" style="background:rgb(200,230,0)">
|
||||||
|
|
@ -307,10 +310,10 @@
|
||||||
class="pointer-events-none fixed top-32 left-32 z-[1100] w-[360px] max-w-[90vw] max-h-[60vh] overflow-y-auto"
|
class="pointer-events-none fixed top-32 left-32 z-[1100] w-[360px] max-w-[90vw] max-h-[60vh] overflow-y-auto"
|
||||||
id="coverage-probe-panel"
|
id="coverage-probe-panel"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto rounded-lg border border-gray-200 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur shadow-lg">
|
<div class="pointer-events-auto rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white/95 dark:bg-cool-steel-900/95 backdrop-blur shadow-lg">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="flex items-center gap-2 text-xs font-mono text-gray-700 dark:text-gray-300">
|
<div class="flex items-center gap-2 text-xs font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<.icon name="hero-map-pin" class="h-4 w-4 text-blue-500" />
|
<.icon name="hero-map-pin" class="h-4 w-4 text-cerulean-500" />
|
||||||
<span>
|
<span>
|
||||||
{:erlang.float_to_binary(@probe.lat * 1.0, decimals: 5)}, {:erlang.float_to_binary(
|
{:erlang.float_to_binary(@probe.lat * 1.0, decimals: 5)}, {:erlang.float_to_binary(
|
||||||
@probe.lon * 1.0,
|
@probe.lon * 1.0,
|
||||||
|
|
@ -321,13 +324,15 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close_probe"
|
phx-click="close_probe"
|
||||||
class="text-gray-500 hover:text-gray-700 dark:text-gray-400"
|
class="text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4" />
|
<.icon name="hero-x-mark" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-3 py-2 border-b border-gray-200 dark:border-white/10 flex items-center gap-3">
|
<div class="px-3 py-2 border-b border-cool-steel-200 dark:border-white/10 flex items-center gap-3">
|
||||||
<span class="text-xs text-gray-700 dark:text-gray-300">{t("Units:")}</span>
|
<span class="text-xs text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
|
{t("Units:")}
|
||||||
|
</span>
|
||||||
<label class="flex items-center gap-1 text-xs cursor-pointer">
|
<label class="flex items-center gap-1 text-xs cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|
@ -348,45 +353,45 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<table class="min-w-full text-xs">
|
<table class="min-w-full text-xs">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-3 py-1.5 text-left font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-left font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Tower")}
|
{t("Tower")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-right font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Distance")}
|
{t("Distance")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right font-medium text-gray-700 dark:text-gray-300">
|
<th class="px-3 py-1.5 text-right font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
RSSI
|
RSSI
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for row <- @probe.rows do %>
|
<%= for row <- @probe.rows do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-3 py-1.5 font-medium text-gray-900 dark:text-gray-100 truncate">
|
<td class="px-3 py-1.5 font-medium text-cool-steel-900 dark:text-cool-steel-100 truncate">
|
||||||
{row.coverage.name}
|
{row.coverage.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right font-mono text-gray-700 dark:text-gray-300">
|
<td class="px-3 py-1.5 text-right font-mono text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{fmt_distance(row.distance_m, @probe_units)}
|
{fmt_distance(row.distance_m, @probe_units)}
|
||||||
</td>
|
</td>
|
||||||
<td class={[
|
<td class={[
|
||||||
"px-3 py-1.5 text-right font-mono",
|
"px-3 py-1.5 text-right font-mono",
|
||||||
cond do
|
cond do
|
||||||
row.rssi == :no_coverage ->
|
row.rssi == :no_coverage ->
|
||||||
"text-gray-400"
|
"text-cool-steel-400"
|
||||||
|
|
||||||
is_number(row.rssi) and row.rssi >= -75 ->
|
is_number(row.rssi) and row.rssi >= -75 ->
|
||||||
"text-green-600 dark:text-green-400 font-bold"
|
"text-green-600 dark:text-green-400 font-bold"
|
||||||
|
|
||||||
is_number(row.rssi) and row.rssi >= -85 ->
|
is_number(row.rssi) and row.rssi >= -85 ->
|
||||||
"text-yellow-600 dark:text-yellow-400"
|
"text-wheat-600 dark:text-wheat-400"
|
||||||
|
|
||||||
is_number(row.rssi) ->
|
is_number(row.rssi) ->
|
||||||
"text-red-600 dark:text-red-400"
|
"text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"text-gray-400"
|
"text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{fmt_rssi(row.rssi)}
|
{fmt_rssi(row.rssi)}
|
||||||
|
|
@ -399,10 +404,10 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% true -> %>
|
<% true -> %>
|
||||||
<div class="h-[78vh] w-full rounded-lg border border-gray-200 dark:border-white/10 flex items-center justify-center bg-gray-50 dark:bg-gray-900 text-center px-6">
|
<div class="h-[78vh] w-full rounded-lg border border-cool-steel-200 dark:border-white/10 flex items-center justify-center bg-cool-steel-50 dark:bg-cool-steel-900 text-center px-6">
|
||||||
<div>
|
<div>
|
||||||
<.icon name="hero-signal-slash" class="h-12 w-12 mx-auto text-gray-400 mb-2" />
|
<.icon name="hero-signal-slash" class="h-12 w-12 mx-auto text-cool-steel-400 mb-2" />
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("No heatmap available yet. Run compute to generate the coverage prediction.")}
|
{t("No heatmap available yet. Run compute to generate the coverage prediction.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -258,19 +258,22 @@ defmodule ToweropsWeb.DashboardLive do
|
||||||
defp favicon_status(_devices_down, _alert_count), do: "green"
|
defp favicon_status(_devices_down, _alert_count), do: "green"
|
||||||
|
|
||||||
defp health_score_color(score) when score > 80, do: "text-green-600 dark:text-green-400"
|
defp health_score_color(score) when score > 80, do: "text-green-600 dark:text-green-400"
|
||||||
defp health_score_color(score) when score > 50, do: "text-yellow-600 dark:text-yellow-400"
|
defp health_score_color(score) when score > 50, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
defp health_score_color(_score), do: "text-red-600 dark:text-red-400"
|
defp health_score_color(_score), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
defp source_classes("preseem"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
defp source_classes("preseem"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
defp source_classes("snmp"), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
defp source_classes("snmp"), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
||||||
defp source_classes("gaiia"), do: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400"
|
defp source_classes("gaiia"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
defp source_classes("system"), do: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
|
|
||||||
defp source_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
defp source_classes("system"),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
|
defp source_classes(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
|
|
||||||
defp config_change_impact_color(change) do
|
defp config_change_impact_color(change) do
|
||||||
cond do
|
cond do
|
||||||
change.change_size > 50 -> "bg-red-500"
|
change.change_size > 50 -> "bg-sweet-salmon-500"
|
||||||
change.change_size > 20 -> "bg-yellow-500"
|
change.change_size > 20 -> "bg-wheat-500"
|
||||||
true -> "bg-green-500"
|
true -> "bg-green-500"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -319,15 +322,15 @@ defmodule ToweropsWeb.DashboardLive do
|
||||||
amount = decimal_to_float(mrr)
|
amount = decimal_to_float(mrr)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
amount >= 1000 -> "border-l-4 border-red-500 bg-red-50 dark:bg-red-950/30"
|
amount >= 1000 -> "border-l-4 border-sweet-salmon-500 bg-sweet-salmon-50 dark:bg-sweet-salmon-950/30"
|
||||||
amount >= 100 -> "border-l-4 border-orange-500 bg-orange-50 dark:bg-orange-950/30"
|
amount >= 100 -> "border-l-4 border-orange-500 bg-orange-50 dark:bg-orange-950/30"
|
||||||
amount > 0 -> "border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-950/30"
|
amount > 0 -> "border-l-4 border-wheat-500 bg-wheat-50 dark:bg-wheat-950/30"
|
||||||
true -> "border-l-4 border-gray-300 bg-gray-50 dark:bg-gray-800/50"
|
true -> "border-l-4 border-cool-steel-300 bg-cool-steel-50 dark:bg-cool-steel-800/50"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp site_health_dot(:red), do: "bg-red-500"
|
defp site_health_dot(:red), do: "bg-sweet-salmon-500"
|
||||||
defp site_health_dot(:yellow), do: "bg-yellow-500"
|
defp site_health_dot(:yellow), do: "bg-wheat-500"
|
||||||
defp site_health_dot(_), do: "bg-green-500"
|
defp site_health_dot(_), do: "bg-green-500"
|
||||||
|
|
||||||
defp decimal_to_float(%Decimal{} = d), do: Decimal.to_float(d)
|
defp decimal_to_float(%Decimal{} = d), do: Decimal.to_float(d)
|
||||||
|
|
@ -339,8 +342,8 @@ defmodule ToweropsWeb.DashboardLive do
|
||||||
defp mrr_at_risk_positive?(_), do: false
|
defp mrr_at_risk_positive?(_), do: false
|
||||||
|
|
||||||
defp uptime_color(pct) when pct >= 99.0, do: "text-green-600 dark:text-green-400"
|
defp uptime_color(pct) when pct >= 99.0, do: "text-green-600 dark:text-green-400"
|
||||||
defp uptime_color(pct) when pct >= 95.0, do: "text-yellow-600 dark:text-yellow-400"
|
defp uptime_color(pct) when pct >= 95.0, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
defp uptime_color(_pct), do: "text-red-600 dark:text-red-400"
|
defp uptime_color(_pct), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
defp format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
|
defp format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,14 @@
|
||||||
═══════════════════════════════════════════════ --%>
|
═══════════════════════════════════════════════ --%>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h1 class="text-lg font-bold text-gray-900 dark:text-white">
|
<h1 class="text-lg font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@current_scope.organization.name}
|
{@current_scope.organization.name}
|
||||||
</h1>
|
</h1>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500 dark:text-gray-400 font-mono">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400 font-mono">
|
||||||
NOC
|
NOC
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-500 font-mono">
|
<div class="flex items-center gap-1.5 text-xs text-cool-steel-400 dark:text-cool-steel-500 font-mono">
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -40,61 +40,70 @@
|
||||||
|
|
||||||
<%!-- Empty State --%>
|
<%!-- Empty State --%>
|
||||||
<%= if assigns[:summary] && @device_count == 0 do %>
|
<%= if assigns[:summary] && @device_count == 0 do %>
|
||||||
<div class="mt-12 rounded-xl border-2 border-dashed border-gray-300 bg-gray-50/50 p-16 text-center dark:border-white/20 dark:bg-gray-800/30">
|
<div class="mt-12 rounded-xl border-2 border-dashed border-cool-steel-300 bg-cool-steel-50/50 p-16 text-center dark:border-white/20 dark:bg-cool-steel-800/30">
|
||||||
<.icon name="hero-light-bulb" class="mx-auto h-14 w-14 text-blue-500 dark:text-blue-400" />
|
<.icon
|
||||||
<h3 class="mt-6 text-xl font-semibold text-gray-900 dark:text-white">
|
name="hero-light-bulb"
|
||||||
|
class="mx-auto h-14 w-14 text-cerulean-500 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-6 text-xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Let's get started!")}
|
{t("Let's get started!")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400 max-w-md mx-auto">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400 max-w-md mx-auto">
|
||||||
{t("Set up your network monitoring in a few steps.")}
|
{t("Set up your network monitoring in a few steps.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-10 max-w-lg mx-auto">
|
<div class="mt-10 max-w-lg mx-auto">
|
||||||
<div class="space-y-5 text-left">
|
<div class="space-y-5 text-left">
|
||||||
<%= if @current_scope.organization.use_sites do %>
|
<%= if @current_scope.organization.use_sites do %>
|
||||||
<div class="flex gap-4 items-start">
|
<div class="flex gap-4 items-start">
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 font-semibold text-sm dark:bg-blue-900 dark:text-blue-300">
|
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-cerulean-100 text-cerulean-600 font-semibold text-sm dark:bg-cerulean-900 dark:text-cerulean-300">
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-medium text-gray-900 dark:text-white">{t("Create a Site")}</h4>
|
<h4 class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
|
{t("Create a Site")}
|
||||||
|
</h4>
|
||||||
|
<p class="mt-0.5 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Sites represent your physical locations")}
|
{t("Sites represent your physical locations")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-4 items-start">
|
<div class="flex gap-4 items-start">
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-gray-100 text-gray-500 font-semibold text-sm dark:bg-gray-700 dark:text-gray-400">
|
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-cool-steel-100 text-cool-steel-500 font-semibold text-sm dark:bg-cool-steel-700 dark:text-cool-steel-400">
|
||||||
2
|
2
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-medium text-gray-900 dark:text-white">{t("Add Devices")}</h4>
|
<h4 class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
|
{t("Add Devices")}
|
||||||
|
</h4>
|
||||||
|
<p class="mt-0.5 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Add routers, switches, and servers to your sites")}
|
{t("Add routers, switches, and servers to your sites")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="flex gap-4 items-start">
|
<div class="flex gap-4 items-start">
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 font-semibold text-sm dark:bg-blue-900 dark:text-blue-300">
|
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-cerulean-100 text-cerulean-600 font-semibold text-sm dark:bg-cerulean-900 dark:text-cerulean-300">
|
||||||
1
|
1
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-medium text-gray-900 dark:text-white">{t("Add Devices")}</h4>
|
<h4 class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
<p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
|
{t("Add Devices")}
|
||||||
|
</h4>
|
||||||
|
<p class="mt-0.5 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Add network devices to monitor")}
|
{t("Add network devices to monitor")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex gap-4 items-start">
|
<div class="flex gap-4 items-start">
|
||||||
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-gray-100 text-gray-500 font-semibold text-sm dark:bg-gray-700 dark:text-gray-400">
|
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-cool-steel-100 text-cool-steel-500 font-semibold text-sm dark:bg-cool-steel-700 dark:text-cool-steel-400">
|
||||||
{if @current_scope.organization.use_sites, do: "3", else: "2"}
|
{if @current_scope.organization.use_sites, do: "3", else: "2"}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-medium text-gray-900 dark:text-white">
|
<h4 class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Monitor & Receive Alerts")}
|
{t("Monitor & Receive Alerts")}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-0.5 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("TowerOps automatically monitors your devices and alerts you of issues")}
|
{t("TowerOps automatically monitors your devices and alerts you of issues")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -119,30 +128,33 @@
|
||||||
<%= if assigns[:setup_checklist] && !@setup_checklist.all_done && @device_count > 0 do %>
|
<%= if assigns[:setup_checklist] && !@setup_checklist.all_done && @device_count > 0 do %>
|
||||||
<div
|
<div
|
||||||
id="setup-checklist"
|
id="setup-checklist"
|
||||||
class="mt-4 rounded-lg border border-blue-200 bg-blue-50/50 dark:border-blue-800/50 dark:bg-blue-900/10 p-4"
|
class="mt-4 rounded-lg border border-cerulean-200 bg-cerulean-50/50 dark:border-cerulean-800/50 dark:bg-cerulean-900/10 p-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-rocket-launch" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<.icon
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
name="hero-rocket-launch"
|
||||||
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Setup Progress")}
|
{t("Setup Progress")}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{@setup_checklist.completed}/{@setup_checklist.total}
|
{@setup_checklist.completed}/{@setup_checklist.total}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
phx-click={JS.hide(to: "#setup-checklist")}
|
phx-click={JS.hide(to: "#setup-checklist")}
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
title={t("Dismiss")}
|
title={t("Dismiss")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4" />
|
<.icon name="hero-x-mark" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<%!-- Progress bar --%>
|
<%!-- Progress bar --%>
|
||||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5 mb-3">
|
<div class="w-full bg-cool-steel-200 dark:bg-cool-steel-700 rounded-full h-1.5 mb-3">
|
||||||
<div
|
<div
|
||||||
class="bg-blue-600 dark:bg-blue-400 h-1.5 rounded-full transition-all duration-500"
|
class="bg-cerulean-600 dark:bg-cerulean-400 h-1.5 rounded-full transition-all duration-500"
|
||||||
style={"width: #{@setup_checklist.completed / @setup_checklist.total * 100}%"}
|
style={"width: #{@setup_checklist.completed / @setup_checklist.total * 100}%"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -154,22 +166,22 @@
|
||||||
class={[
|
class={[
|
||||||
"flex items-center gap-2.5 rounded-md px-3 py-2 text-sm transition-colors",
|
"flex items-center gap-2.5 rounded-md px-3 py-2 text-sm transition-colors",
|
||||||
if(step.done,
|
if(step.done,
|
||||||
do: "text-gray-400 dark:text-gray-500",
|
do: "text-cool-steel-400 dark:text-cool-steel-500",
|
||||||
else:
|
else:
|
||||||
"text-gray-700 dark:text-gray-300 hover:bg-blue-100/50 dark:hover:bg-blue-900/20"
|
"text-cool-steel-700 dark:text-cool-steel-300 hover:bg-cerulean-100/50 dark:hover:bg-cerulean-900/20"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<%= if step.done do %>
|
<%= if step.done do %>
|
||||||
<.icon name="hero-check-circle-solid" class="h-5 w-5 text-green-500 shrink-0" />
|
<.icon name="hero-check-circle-solid" class="h-5 w-5 text-green-500 shrink-0" />
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="h-5 w-5 rounded-full border-2 border-gray-300 dark:border-gray-600 shrink-0" />
|
<div class="h-5 w-5 rounded-full border-2 border-cool-steel-300 dark:border-cool-steel-600 shrink-0" />
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class={if step.done, do: "line-through"}>
|
<span class={if step.done, do: "line-through"}>
|
||||||
{step.label}
|
{step.label}
|
||||||
</span>
|
</span>
|
||||||
<%= if step[:optional] do %>
|
<%= if step[:optional] do %>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">optional</span>
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500">optional</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</.link>
|
</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -181,17 +193,17 @@
|
||||||
<%!-- ═══════════════════════════════════════════════
|
<%!-- ═══════════════════════════════════════════════
|
||||||
CONDENSED STATUS BAR — single dense line
|
CONDENSED STATUS BAR — single dense line
|
||||||
═══════════════════════════════════════════════ --%>
|
═══════════════════════════════════════════════ --%>
|
||||||
<div class="mt-4 grid grid-cols-2 gap-0.5 rounded-md border border-gray-200 bg-gray-50 px-1 py-1 dark:border-white/10 dark:bg-gray-800/80 font-mono text-xs sm:flex sm:flex-wrap sm:items-center sm:gap-1">
|
<div class="mt-4 grid grid-cols-2 gap-0.5 rounded-md border border-cool-steel-200 bg-cool-steel-50 px-1 py-1 dark:border-white/10 dark:bg-cool-steel-800/80 font-mono text-xs sm:flex sm:flex-wrap sm:items-center sm:gap-1">
|
||||||
<%!-- Health --%>
|
<%!-- Health --%>
|
||||||
<div class={[
|
<div class={[
|
||||||
"flex items-center gap-1.5 rounded px-2.5 py-1.5",
|
"flex items-center gap-1.5 rounded px-2.5 py-1.5",
|
||||||
cond do
|
cond do
|
||||||
@summary.health_score > 80 -> "bg-green-100/80 dark:bg-green-900/30"
|
@summary.health_score > 80 -> "bg-green-100/80 dark:bg-green-900/30"
|
||||||
@summary.health_score > 50 -> "bg-yellow-100/80 dark:bg-yellow-900/30"
|
@summary.health_score > 50 -> "bg-wheat-100/80 dark:bg-wheat-900/30"
|
||||||
true -> "bg-red-100/80 dark:bg-red-900/30"
|
true -> "bg-sweet-salmon-100/80 dark:bg-sweet-salmon-900/30"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium">
|
||||||
Health
|
Health
|
||||||
</span>
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
|
|
@ -212,11 +224,11 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
|
|
||||||
<%!-- Uptime --%>
|
<%!-- Uptime --%>
|
||||||
<div class="flex items-center gap-1.5 px-2.5 py-1.5">
|
<div class="flex items-center gap-1.5 px-2.5 py-1.5">
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium">
|
||||||
Up
|
Up
|
||||||
</span>
|
</span>
|
||||||
<span class={["font-bold text-sm", uptime_color(@uptime_percentage)]}>
|
<span class={["font-bold text-sm", uptime_color(@uptime_percentage)]}>
|
||||||
|
|
@ -224,29 +236,29 @@
|
||||||
do: "100",
|
do: "100",
|
||||||
else: :erlang.float_to_binary(@uptime_percentage, decimals: 1)}%
|
else: :erlang.float_to_binary(@uptime_percentage, decimals: 1)}%
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{@device_up}/{@device_count}
|
{@device_up}/{@device_count}
|
||||||
</span>
|
</span>
|
||||||
<%= if @device_down > 0 do %>
|
<%= if @device_down > 0 do %>
|
||||||
<span class="text-red-500 font-bold">{@device_down}↓</span>
|
<span class="text-sweet-salmon-500 font-bold">{@device_down}↓</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
|
|
||||||
<%!-- Alerts --%>
|
<%!-- Alerts --%>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/alerts"}
|
navigate={~p"/alerts"}
|
||||||
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-gray-200/50 dark:hover:bg-white/5 transition"
|
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-cool-steel-200/50 dark:hover:bg-white/5 transition"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium">
|
||||||
Alerts
|
Alerts
|
||||||
</span>
|
</span>
|
||||||
<%= if @total_alert_count > 0 do %>
|
<%= if @total_alert_count > 0 do %>
|
||||||
<span class="font-bold text-sm text-red-600 dark:text-red-400">
|
<span class="font-bold text-sm text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{@total_alert_count}
|
{@total_alert_count}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-red-500">
|
<span class="text-sweet-salmon-500">
|
||||||
{String.duplicate("●", min(@total_alert_count, 5))}
|
{String.duplicate("●", min(@total_alert_count, 5))}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -256,34 +268,36 @@
|
||||||
</.link>
|
</.link>
|
||||||
|
|
||||||
<%= if @current_scope.organization.use_sites && @sites_count > 0 do %>
|
<%= if @current_scope.organization.use_sites && @sites_count > 0 do %>
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites"}
|
navigate={~p"/sites"}
|
||||||
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-gray-200/50 dark:hover:bg-white/5 transition"
|
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-cool-steel-200/50 dark:hover:bg-white/5 transition"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium">
|
||||||
Sites
|
Sites
|
||||||
</span>
|
</span>
|
||||||
<span class="font-bold text-sm text-gray-900 dark:text-white">{@sites_count}</span>
|
<span class="font-bold text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{@sites_count}
|
||||||
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @has_subscribers do %>
|
<%= if @has_subscribers do %>
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
<div class="flex items-center gap-1.5 px-2.5 py-1.5">
|
<div class="flex items-center gap-1.5 px-2.5 py-1.5">
|
||||||
<span
|
<span
|
||||||
class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium"
|
class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium"
|
||||||
title="Subscribers"
|
title="Subscribers"
|
||||||
>
|
>
|
||||||
Subs
|
Subs
|
||||||
</span>
|
</span>
|
||||||
<span class="font-bold text-sm text-gray-900 dark:text-white">
|
<span class="font-bold text-sm text-cool-steel-900 dark:text-white">
|
||||||
{format_number(@summary.subscribers.total)}
|
{format_number(@summary.subscribers.total)}
|
||||||
</span>
|
</span>
|
||||||
<%!-- Financial data: role-gated --%>
|
<%!-- Financial data: role-gated --%>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
<span
|
<span
|
||||||
class="text-gray-400 dark:text-gray-500 dark:text-gray-400"
|
class="text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
title="Monthly Recurring Revenue"
|
title="Monthly Recurring Revenue"
|
||||||
>
|
>
|
||||||
{format_mrr(@summary.subscribers.total_mrr)}/mo
|
{format_mrr(@summary.subscribers.total_mrr)}/mo
|
||||||
|
|
@ -293,16 +307,16 @@
|
||||||
|
|
||||||
<%!-- Financial data: role-gated --%>
|
<%!-- Financial data: role-gated --%>
|
||||||
<%= if @can_view_financials and mrr_at_risk_positive?(@impact_summary.mrr_at_risk) do %>
|
<%= if @can_view_financials and mrr_at_risk_positive?(@impact_summary.mrr_at_risk) do %>
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
<div class="flex items-center gap-1.5 px-2.5 py-1.5 bg-red-100/80 dark:bg-red-900/30 rounded">
|
<div class="flex items-center gap-1.5 px-2.5 py-1.5 bg-sweet-salmon-100/80 dark:bg-sweet-salmon-900/30 rounded">
|
||||||
<span class="text-red-500 dark:text-red-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-sweet-salmon-500 dark:text-sweet-salmon-400 text-[10px] uppercase font-sans font-medium">
|
||||||
At Risk
|
At Risk
|
||||||
</span>
|
</span>
|
||||||
<span class="font-bold text-sm text-red-600 dark:text-red-400">
|
<span class="font-bold text-sm text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{format_mrr(@impact_summary.mrr_at_risk)}
|
{format_mrr(@impact_summary.mrr_at_risk)}
|
||||||
</span>
|
</span>
|
||||||
<%= if @impact_summary.subscribers_affected > 0 do %>
|
<%= if @impact_summary.subscribers_affected > 0 do %>
|
||||||
<span class="text-red-500 dark:text-red-400">
|
<span class="text-sweet-salmon-500 dark:text-sweet-salmon-400">
|
||||||
{format_number(@impact_summary.subscribers_affected)} subs
|
{format_number(@impact_summary.subscribers_affected)} subs
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -311,21 +325,21 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%!-- Insights count --%>
|
<%!-- Insights count --%>
|
||||||
<span class="hidden text-gray-300 dark:text-gray-600 sm:inline">│</span>
|
<span class="hidden text-cool-steel-300 dark:text-cool-steel-600 sm:inline">│</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights"}
|
navigate={~p"/insights"}
|
||||||
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-gray-200/50 dark:hover:bg-white/5 transition"
|
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded hover:bg-cool-steel-200/50 dark:hover:bg-white/5 transition"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-[10px] uppercase font-sans font-medium">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 text-[10px] uppercase font-sans font-medium">
|
||||||
Insights
|
Insights
|
||||||
</span>
|
</span>
|
||||||
<span class="font-bold text-sm text-gray-900 dark:text-white">
|
<span class="font-bold text-sm text-cool-steel-900 dark:text-white">
|
||||||
{@summary.insights.critical + @summary.insights.warning + @summary.insights.info}
|
{@summary.insights.critical + @summary.insights.warning + @summary.insights.info}
|
||||||
</span>
|
</span>
|
||||||
<span :if={@summary.insights.critical > 0} class="text-red-500 text-[10px]">
|
<span :if={@summary.insights.critical > 0} class="text-sweet-salmon-500 text-[10px]">
|
||||||
{@summary.insights.critical}c
|
{@summary.insights.critical}c
|
||||||
</span>
|
</span>
|
||||||
<span :if={@summary.insights.warning > 0} class="text-yellow-500 text-[10px]">
|
<span :if={@summary.insights.warning > 0} class="text-wheat-500 text-[10px]">
|
||||||
{@summary.insights.warning}w
|
{@summary.insights.warning}w
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -337,19 +351,19 @@
|
||||||
<%= if @active_incidents != [] || @active_alerts != [] do %>
|
<%= if @active_incidents != [] || @active_alerts != [] do %>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-red-600 dark:text-red-400">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{t("Active Issues")}
|
{t("Active Issues")}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="font-mono text-xs text-red-500 dark:text-red-400">
|
<span class="font-mono text-xs text-sweet-salmon-500 dark:text-sweet-salmon-400">
|
||||||
{length(@active_incidents) + @deduped_alert_count}
|
{length(@active_incidents) + @deduped_alert_count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Incidents (highest priority) --%>
|
<%!-- Incidents (highest priority) --%>
|
||||||
<%= if @active_incidents != [] do %>
|
<%= if @active_incidents != [] do %>
|
||||||
<div class="overflow-hidden rounded-md border border-red-200 dark:border-red-800/50 mb-2">
|
<div class="overflow-hidden rounded-md border border-sweet-salmon-200 dark:border-sweet-salmon-800/50 mb-2">
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<tbody class="divide-y divide-red-100 dark:divide-red-900/30">
|
<tbody class="divide-y divide-sweet-salmon-100 dark:divide-sweet-salmon-900/30">
|
||||||
<tr
|
<tr
|
||||||
:for={incident <- Enum.take(@active_incidents, 10)}
|
:for={incident <- Enum.take(@active_incidents, 10)}
|
||||||
class={[
|
class={[
|
||||||
|
|
@ -366,13 +380,13 @@
|
||||||
"w-1 h-full min-h-[2.5rem]",
|
"w-1 h-full min-h-[2.5rem]",
|
||||||
if @can_view_financials do
|
if @can_view_financials do
|
||||||
cond do
|
cond do
|
||||||
decimal_to_float(incident.mrr_at_risk) >= 1000 -> "bg-red-500"
|
decimal_to_float(incident.mrr_at_risk) >= 1000 -> "bg-sweet-salmon-500"
|
||||||
decimal_to_float(incident.mrr_at_risk) >= 100 -> "bg-orange-500"
|
decimal_to_float(incident.mrr_at_risk) >= 100 -> "bg-orange-500"
|
||||||
decimal_to_float(incident.mrr_at_risk) > 0 -> "bg-yellow-500"
|
decimal_to_float(incident.mrr_at_risk) > 0 -> "bg-wheat-500"
|
||||||
true -> "bg-gray-400"
|
true -> "bg-cool-steel-400"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
"bg-gray-400"
|
"bg-cool-steel-400"
|
||||||
end
|
end
|
||||||
]} />
|
]} />
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -380,17 +394,17 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-exclamation-triangle"
|
name="hero-exclamation-triangle"
|
||||||
class="h-3.5 w-3.5 text-red-500 flex-shrink-0"
|
class="h-3.5 w-3.5 text-sweet-salmon-500 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{incident.device_id}"}
|
navigate={~p"/devices/#{incident.device_id}"}
|
||||||
class="text-sm font-semibold text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
|
class="text-sm font-semibold text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{incident.device_name}
|
{incident.device_name}
|
||||||
</.link>
|
</.link>
|
||||||
<span
|
<span
|
||||||
:if={incident.site_name}
|
:if={incident.site_name}
|
||||||
class="text-[10px] text-gray-400 dark:text-gray-500 dark:text-gray-400"
|
class="text-[10px] text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
@ {incident.site_name}
|
@ {incident.site_name}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -398,12 +412,15 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right whitespace-nowrap">
|
<td class="px-3 py-1.5 text-right whitespace-nowrap">
|
||||||
<div class="flex items-center justify-end gap-3 font-mono text-xs">
|
<div class="flex items-center justify-end gap-3 font-mono text-xs">
|
||||||
<span class="text-red-600 dark:text-red-400 font-semibold" title="Duration">
|
<span
|
||||||
|
class="text-sweet-salmon-600 dark:text-sweet-salmon-400 font-semibold"
|
||||||
|
title="Duration"
|
||||||
|
>
|
||||||
{format_duration(incident.duration_seconds)}
|
{format_duration(incident.duration_seconds)}
|
||||||
</span>
|
</span>
|
||||||
<%= if incident.subscribers_affected > 0 do %>
|
<%= if incident.subscribers_affected > 0 do %>
|
||||||
<span
|
<span
|
||||||
class="text-amber-600 dark:text-amber-400"
|
class="text-wheat-600 dark:text-wheat-400"
|
||||||
title="Subscribers affected"
|
title="Subscribers affected"
|
||||||
>
|
>
|
||||||
{format_number(incident.subscribers_affected)}s
|
{format_number(incident.subscribers_affected)}s
|
||||||
|
|
@ -411,7 +428,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%!-- Financial data: role-gated --%>
|
<%!-- Financial data: role-gated --%>
|
||||||
<%= if @can_view_financials and mrr_at_risk_positive?(incident.mrr_at_risk) do %>
|
<%= if @can_view_financials and mrr_at_risk_positive?(incident.mrr_at_risk) do %>
|
||||||
<span class="text-amber-600 dark:text-amber-400" title="MRR at risk">
|
<span class="text-wheat-600 dark:text-wheat-400" title="MRR at risk">
|
||||||
{format_mrr(incident.mrr_at_risk)}
|
{format_mrr(incident.mrr_at_risk)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -425,31 +442,31 @@
|
||||||
|
|
||||||
<%!-- Alerts table --%>
|
<%!-- Alerts table --%>
|
||||||
<%= if @active_alerts != [] do %>
|
<%= if @active_alerts != [] do %>
|
||||||
<div class="overflow-x-auto rounded-md border border-gray-200 dark:border-white/10">
|
<div class="overflow-x-auto rounded-md border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead class="bg-gray-50/80 dark:bg-gray-800/80">
|
<thead class="bg-cool-steel-50/80 dark:bg-cool-steel-800/80">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-1 p-0"></th>
|
<th class="w-1 p-0"></th>
|
||||||
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Device")}
|
{t("Device")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Alert")}
|
{t("Alert")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Impact")}
|
{t("Impact")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("When")}
|
{t("When")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 w-12"></th>
|
<th class="hidden sm:table-cell px-3 py-1.5 w-12"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-800/50">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-800/50">
|
||||||
<tr
|
<tr
|
||||||
:for={alert <- @active_alerts}
|
:for={alert <- @active_alerts}
|
||||||
id={"alert-#{alert.id}"}
|
id={"alert-#{alert.id}"}
|
||||||
class="hover:bg-gray-50 dark:hover:bg-white/5 group"
|
class="hover:bg-cool-steel-50 dark:hover:bg-white/5 group"
|
||||||
>
|
>
|
||||||
<td class="w-1 p-0">
|
<td class="w-1 p-0">
|
||||||
<div class={[
|
<div class={[
|
||||||
|
|
@ -457,14 +474,14 @@
|
||||||
cond do
|
cond do
|
||||||
alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] &&
|
alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] &&
|
||||||
alert.gaiia_impact["total_subscribers"] > 10 ->
|
alert.gaiia_impact["total_subscribers"] > 10 ->
|
||||||
"bg-red-500"
|
"bg-sweet-salmon-500"
|
||||||
|
|
||||||
alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] &&
|
alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] &&
|
||||||
alert.gaiia_impact["total_subscribers"] > 0 ->
|
alert.gaiia_impact["total_subscribers"] > 0 ->
|
||||||
"bg-orange-500"
|
"bg-orange-500"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"bg-yellow-500"
|
"bg-wheat-500"
|
||||||
end
|
end
|
||||||
]} />
|
]} />
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -472,30 +489,30 @@
|
||||||
<div class="flex items-center gap-1.5 flex-wrap">
|
<div class="flex items-center gap-1.5 flex-wrap">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{alert.device.id}"}
|
navigate={~p"/devices/#{alert.device.id}"}
|
||||||
class="text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
|
class="text-sm font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{alert.device.name}
|
{alert.device.name}
|
||||||
</.link>
|
</.link>
|
||||||
<span
|
<span
|
||||||
:if={alert.device.ip_address}
|
:if={alert.device.ip_address}
|
||||||
class="hidden sm:inline font-mono text-[11px] text-gray-400 dark:text-gray-500"
|
class="hidden sm:inline font-mono text-[11px] text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
>
|
>
|
||||||
{alert.device.ip_address}
|
{alert.device.ip_address}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
:if={alert.device.site}
|
:if={alert.device.site}
|
||||||
class="text-[10px] text-gray-400 dark:text-gray-500"
|
class="text-[10px] text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
>
|
>
|
||||||
{alert.device.site.name}
|
{alert.device.site.name}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-xs text-gray-600 dark:text-gray-400 max-w-[120px] sm:max-w-xs truncate">
|
<td class="px-3 py-1.5 text-xs text-cool-steel-600 dark:text-cool-steel-400 max-w-[120px] sm:max-w-xs truncate">
|
||||||
{alert.message}
|
{alert.message}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-1.5 text-right whitespace-nowrap">
|
<td class="hidden sm:table-cell px-3 py-1.5 text-right whitespace-nowrap">
|
||||||
<%= if alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] && alert.gaiia_impact["total_subscribers"] > 0 do %>
|
<%= if alert.gaiia_impact && alert.gaiia_impact["total_subscribers"] && alert.gaiia_impact["total_subscribers"] > 0 do %>
|
||||||
<span class="font-mono text-xs text-amber-600 dark:text-amber-400">
|
<span class="font-mono text-xs text-wheat-600 dark:text-wheat-400">
|
||||||
{format_subscriber_count(alert.gaiia_impact["total_subscribers"])}
|
{format_subscriber_count(alert.gaiia_impact["total_subscribers"])}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -504,7 +521,7 @@
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={alert.triggered_at}
|
datetime={alert.triggered_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
class="font-mono text-[11px] text-gray-400 dark:text-gray-500"
|
class="font-mono text-[11px] text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-1.5 text-right">
|
<td class="hidden sm:table-cell px-3 py-1.5 text-right">
|
||||||
|
|
@ -513,12 +530,12 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="acknowledge_alert"
|
phx-click="acknowledge_alert"
|
||||||
phx-value-id={alert.id}
|
phx-value-id={alert.id}
|
||||||
class="rounded px-1.5 py-0.5 text-[10px] font-medium text-gray-500 ring-1 ring-inset ring-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:ring-gray-600 dark:hover:bg-gray-700 dark:hover:text-gray-200 opacity-0 group-hover:opacity-100 transition"
|
class="rounded px-1.5 py-0.5 text-[10px] font-medium text-cool-steel-500 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:ring-cool-steel-600 dark:hover:bg-cool-steel-700 dark:hover:text-cool-steel-200 opacity-0 group-hover:opacity-100 transition"
|
||||||
>
|
>
|
||||||
ACK
|
ACK
|
||||||
</button>
|
</button>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-[10px] text-blue-500 dark:text-blue-400 font-mono">
|
<span class="text-[10px] text-cerulean-500 dark:text-cerulean-400 font-mono">
|
||||||
ACK
|
ACK
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -527,10 +544,10 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<%= if @total_alert_count > 20 do %>
|
<%= if @total_alert_count > 20 do %>
|
||||||
<div class="border-t border-gray-100 dark:border-white/5 px-3 py-1.5 bg-gray-50/50 dark:bg-gray-800/30">
|
<div class="border-t border-cool-steel-100 dark:border-white/5 px-3 py-1.5 bg-cool-steel-50/50 dark:bg-cool-steel-800/30">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/alerts"}
|
navigate={~p"/alerts"}
|
||||||
class="text-[10px] font-medium text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-[10px] font-medium text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
+{@total_alert_count - 20} more alerts →
|
+{@total_alert_count - 20} more alerts →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -547,52 +564,55 @@
|
||||||
<%= if @current_scope.organization.use_sites && @site_impact_summaries != [] do %>
|
<%= if @current_scope.organization.use_sites && @site_impact_summaries != [] do %>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Site Health")}
|
{t("Site Health")}
|
||||||
</h2>
|
</h2>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites"}
|
navigate={~p"/sites"}
|
||||||
class="text-[10px] font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
class="text-[10px] font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
all sites →
|
all sites →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto rounded-md border border-gray-200 dark:border-white/10">
|
<div class="overflow-x-auto rounded-md border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead class="bg-gray-50/80 dark:bg-gray-800/80">
|
<thead class="bg-cool-steel-50/80 dark:bg-cool-steel-800/80">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="px-3 py-1.5 text-left text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Site")}
|
{t("Site")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Devices")}
|
{t("Devices")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Down")}
|
{t("Down")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("QoE")}
|
{t("QoE")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Subs")}
|
{t("Subs")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
<th class="hidden sm:table-cell px-3 py-1.5 text-right text-[10px] font-medium uppercase tracking-wider text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{t("Affected")}
|
{t("Affected")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-800/50">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-800/50">
|
||||||
<tr
|
<tr
|
||||||
:for={site <- @site_impact_summaries}
|
:for={site <- @site_impact_summaries}
|
||||||
class={[
|
class={[
|
||||||
"hover:bg-gray-50 dark:hover:bg-white/5",
|
"hover:bg-cool-steel-50 dark:hover:bg-white/5",
|
||||||
if(site.devices_down > 0, do: "bg-red-50/50 dark:bg-red-950/10", else: "")
|
if(site.devices_down > 0,
|
||||||
|
do: "bg-sweet-salmon-50/50 dark:bg-sweet-salmon-950/10",
|
||||||
|
else: ""
|
||||||
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<td class="px-3 py-1.5">
|
<td class="px-3 py-1.5">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{site.site_id}"}
|
navigate={~p"/sites/#{site.site_id}"}
|
||||||
class="flex items-center gap-1.5 text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
|
class="flex items-center gap-1.5 text-sm font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
<span class={[
|
<span class={[
|
||||||
"h-2 w-2 rounded-full flex-shrink-0",
|
"h-2 w-2 rounded-full flex-shrink-0",
|
||||||
|
|
@ -602,47 +622,47 @@
|
||||||
{site.name}
|
{site.name}
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right font-mono text-xs text-gray-600 dark:text-gray-400">
|
<td class="px-3 py-1.5 text-right font-mono text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{site.device_count}
|
{site.device_count}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1.5 text-right font-mono text-xs">
|
<td class="px-3 py-1.5 text-right font-mono text-xs">
|
||||||
<%= if site.devices_down > 0 do %>
|
<%= if site.devices_down > 0 do %>
|
||||||
<span class="text-red-600 dark:text-red-400 font-bold">
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400 font-bold">
|
||||||
{site.devices_down}
|
{site.devices_down}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-300 dark:text-gray-600">0</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600">0</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs">
|
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs">
|
||||||
<%= if site.avg_qoe do %>
|
<%= if site.avg_qoe do %>
|
||||||
<span class={
|
<span class={
|
||||||
cond do
|
cond do
|
||||||
site.avg_qoe < 2.0 -> "text-red-600 dark:text-red-400"
|
site.avg_qoe < 2.0 -> "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
site.avg_qoe < 4.0 -> "text-yellow-600 dark:text-yellow-400"
|
site.avg_qoe < 4.0 -> "text-wheat-600 dark:text-wheat-400"
|
||||||
true -> "text-green-600 dark:text-green-400"
|
true -> "text-green-600 dark:text-green-400"
|
||||||
end
|
end
|
||||||
}>
|
}>
|
||||||
{format_qoe(site.avg_qoe)}
|
{format_qoe(site.avg_qoe)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-300 dark:text-gray-600">—</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs text-gray-600 dark:text-gray-400">
|
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if site.subscribers do %>
|
<%= if site.subscribers do %>
|
||||||
{site.subscribers}
|
{site.subscribers}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-300 dark:text-gray-600">—</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs">
|
<td class="hidden sm:table-cell px-3 py-1.5 text-right font-mono text-xs">
|
||||||
<%= if site.subscribers_affected > 0 do %>
|
<%= if site.subscribers_affected > 0 do %>
|
||||||
<span class="text-red-600 dark:text-red-400 font-bold">
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400 font-bold">
|
||||||
{format_number(site.subscribers_affected)}
|
{format_number(site.subscribers_affected)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-300 dark:text-gray-600">—</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -659,7 +679,7 @@
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Insights")}
|
{t("Insights")}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex items-center gap-0.5">
|
<div class="flex items-center gap-0.5">
|
||||||
|
|
@ -668,8 +688,8 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded px-1.5 py-0.5 text-[10px] font-medium transition",
|
"rounded px-1.5 py-0.5 text-[10px] font-medium transition",
|
||||||
if(is_nil(@insight_source),
|
if(is_nil(@insight_source),
|
||||||
do: "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
do: "bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
else: "text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10"
|
else: "text-cool-steel-400 hover:bg-cool-steel-100 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -681,8 +701,8 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded px-1.5 py-0.5 text-[10px] font-medium transition",
|
"rounded px-1.5 py-0.5 text-[10px] font-medium transition",
|
||||||
if(@insight_source == source,
|
if(@insight_source == source,
|
||||||
do: "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
do: "bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
else: "text-gray-400 hover:bg-gray-100 dark:hover:bg-white/10"
|
else: "text-cool-steel-400 hover:bg-cool-steel-100 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -692,35 +712,35 @@
|
||||||
</div>
|
</div>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights"}
|
navigate={~p"/insights"}
|
||||||
class="text-[10px] font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
class="text-[10px] font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
all insights →
|
all insights →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-md border border-gray-200 dark:border-white/10 divide-y divide-gray-100 dark:divide-white/5">
|
<div class="overflow-hidden rounded-md border border-cool-steel-200 dark:border-white/10 divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<div
|
<div
|
||||||
:for={insight <- Enum.take(@insights, 6)}
|
:for={insight <- Enum.take(@insights, 6)}
|
||||||
id={"insight-#{insight.id}"}
|
id={"insight-#{insight.id}"}
|
||||||
class="flex items-center justify-between gap-2 px-3 py-2 bg-white dark:bg-gray-800/50 hover:bg-gray-50 dark:hover:bg-white/5 group"
|
class="flex items-center justify-between gap-2 px-3 py-2 bg-white dark:bg-cool-steel-800/50 hover:bg-cool-steel-50 dark:hover:bg-white/5 group"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||||
<%!-- Urgency indicator --%>
|
<%!-- Urgency indicator --%>
|
||||||
<span class={[
|
<span class={[
|
||||||
"w-1.5 h-1.5 rounded-full flex-shrink-0",
|
"w-1.5 h-1.5 rounded-full flex-shrink-0",
|
||||||
case insight.urgency do
|
case insight.urgency do
|
||||||
"critical" -> "bg-red-500"
|
"critical" -> "bg-sweet-salmon-500"
|
||||||
"warning" -> "bg-yellow-500"
|
"warning" -> "bg-wheat-500"
|
||||||
"info" -> "bg-blue-500"
|
"info" -> "bg-cerulean-500"
|
||||||
_ -> "bg-gray-400"
|
_ -> "bg-cool-steel-400"
|
||||||
end
|
end
|
||||||
]} />
|
]} />
|
||||||
<span class={[
|
<span class={[
|
||||||
"text-[10px] font-medium uppercase w-8 flex-shrink-0",
|
"text-[10px] font-medium uppercase w-8 flex-shrink-0",
|
||||||
case insight.urgency do
|
case insight.urgency do
|
||||||
"critical" -> "text-red-600 dark:text-red-400"
|
"critical" -> "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
"warning" -> "text-yellow-600 dark:text-yellow-400"
|
"warning" -> "text-wheat-600 dark:text-wheat-400"
|
||||||
"info" -> "text-blue-600 dark:text-blue-400"
|
"info" -> "text-cerulean-600 dark:text-cerulean-400"
|
||||||
_ -> "text-gray-500 dark:text-gray-400"
|
_ -> "text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{case insight.urgency do
|
{case insight.urgency do
|
||||||
|
|
@ -738,7 +758,7 @@
|
||||||
</span>
|
</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights?source=#{insight.source}"}
|
navigate={~p"/insights?source=#{insight.source}"}
|
||||||
class="text-sm text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 truncate"
|
class="text-sm text-cool-steel-900 dark:text-white hover:text-cerulean-600 dark:hover:text-cerulean-400 truncate"
|
||||||
>
|
>
|
||||||
{insight.title}
|
{insight.title}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -746,7 +766,7 @@
|
||||||
<div class="flex items-center gap-1 flex-shrink-0">
|
<div class="flex items-center gap-1 flex-shrink-0">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights?source=#{insight.source}"}
|
navigate={~p"/insights?source=#{insight.source}"}
|
||||||
class="rounded px-1.5 py-0.5 text-[10px] font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 opacity-0 group-hover:opacity-100 transition"
|
class="rounded px-1.5 py-0.5 text-[10px] font-medium text-cerulean-600 dark:text-cerulean-400 hover:bg-cerulean-50 dark:hover:bg-cerulean-900/20 opacity-0 group-hover:opacity-100 transition"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -754,7 +774,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="dismiss_insight"
|
phx-click="dismiss_insight"
|
||||||
phx-value-id={insight.id}
|
phx-value-id={insight.id}
|
||||||
class="rounded p-0.5 text-gray-300 hover:text-gray-500 dark:text-gray-400 dark:text-gray-600 dark:hover:text-gray-400 opacity-0 group-hover:opacity-100 transition"
|
class="rounded p-0.5 text-cool-steel-300 hover:text-cool-steel-500 dark:text-cool-steel-400 dark:text-cool-steel-600 dark:hover:text-cool-steel-400 opacity-0 group-hover:opacity-100 transition"
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-3 w-3" />
|
<.icon name="hero-x-mark" class="h-3 w-3" />
|
||||||
|
|
@ -771,39 +791,41 @@
|
||||||
<div class="mt-5 grid gap-4 lg:grid-cols-2">
|
<div class="mt-5 grid gap-4 lg:grid-cols-2">
|
||||||
<%!-- Network Pulse --%>
|
<%!-- Network Pulse --%>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-2">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-2">
|
||||||
{t("Network Pulse")}
|
{t("Network Pulse")}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="rounded-md border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-800/50 divide-y divide-gray-100 dark:divide-white/5">
|
<div class="rounded-md border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-800/50 divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<div class="flex items-center justify-between px-3 py-2">
|
<div class="flex items-center justify-between px-3 py-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Devices monitored</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<span class="font-mono text-sm font-bold text-gray-900 dark:text-white">
|
Devices monitored
|
||||||
|
</span>
|
||||||
|
<span class="font-mono text-sm font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@device_count}
|
{@device_count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-3 py-2">
|
<div class="flex items-center justify-between px-3 py-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Online</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">Online</span>
|
||||||
<span class="font-mono text-sm text-green-600 dark:text-green-400">{@device_up}</span>
|
<span class="font-mono text-sm text-green-600 dark:text-green-400">{@device_up}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-3 py-2">
|
<div class="flex items-center justify-between px-3 py-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Down</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">Down</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-mono text-sm",
|
"font-mono text-sm",
|
||||||
if(@device_down > 0,
|
if(@device_down > 0,
|
||||||
do: "text-red-600 dark:text-red-400 font-bold",
|
do: "text-sweet-salmon-600 dark:text-sweet-salmon-400 font-bold",
|
||||||
else: "text-gray-400"
|
else: "text-cool-steel-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{@device_down}
|
{@device_down}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-3 py-2">
|
<div class="flex items-center justify-between px-3 py-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Unknown</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">Unknown</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-mono text-sm",
|
"font-mono text-sm",
|
||||||
if(@device_unknown > 0,
|
if(@device_unknown > 0,
|
||||||
do: "text-yellow-600 dark:text-yellow-400",
|
do: "text-wheat-600 dark:text-wheat-400",
|
||||||
else: "text-gray-400"
|
else: "text-cool-steel-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{@device_unknown}
|
{@device_unknown}
|
||||||
|
|
@ -811,12 +833,14 @@
|
||||||
</div>
|
</div>
|
||||||
<%!-- Config changes row hidden — feature temporarily disabled --%>
|
<%!-- Config changes row hidden — feature temporarily disabled --%>
|
||||||
<div class="flex items-center justify-between px-3 py-2">
|
<div class="flex items-center justify-between px-3 py-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">Active incidents</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Active incidents
|
||||||
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-mono text-sm",
|
"font-mono text-sm",
|
||||||
if(length(@active_incidents) > 0,
|
if(length(@active_incidents) > 0,
|
||||||
do: "text-red-600 dark:text-red-400 font-bold",
|
do: "text-sweet-salmon-600 dark:text-sweet-salmon-400 font-bold",
|
||||||
else: "text-gray-400"
|
else: "text-cool-steel-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{length(@active_incidents)}
|
{length(@active_incidents)}
|
||||||
|
|
@ -828,45 +852,45 @@
|
||||||
<%!-- Config Changes — hidden, feature temporarily disabled --%>
|
<%!-- Config Changes — hidden, feature temporarily disabled --%>
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Config Changes
|
Config Changes
|
||||||
<span class="font-normal text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<span class="font-normal text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
(7d)
|
(7d)
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights"}
|
navigate={~p"/insights"}
|
||||||
class="text-[10px] font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
class="text-[10px] font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
all →
|
all →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<%= if Enum.any?(@recent_config_changes) do %>
|
<%= if Enum.any?(@recent_config_changes) do %>
|
||||||
<div class="overflow-hidden rounded-md border border-gray-200 dark:border-white/10 divide-y divide-gray-100 dark:divide-white/5">
|
<div class="overflow-hidden rounded-md border border-cool-steel-200 dark:border-white/10 divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<%= for change <- @recent_config_changes do %>
|
<%= for change <- @recent_config_changes do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{change.device_id}/config-timeline"}
|
navigate={~p"/devices/#{change.device_id}/config-timeline"}
|
||||||
class="flex items-center gap-2 px-3 py-1.5 bg-white hover:bg-gray-50 dark:bg-gray-800/50 dark:hover:bg-white/5 transition-colors"
|
class="flex items-center gap-2 px-3 py-1.5 bg-white hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:hover:bg-white/5 transition-colors"
|
||||||
>
|
>
|
||||||
<span class={"w-1.5 h-1.5 rounded-full flex-shrink-0 #{config_change_impact_color(change)}"} />
|
<span class={"w-1.5 h-1.5 rounded-full flex-shrink-0 #{config_change_impact_color(change)}"} />
|
||||||
<span class="text-xs font-medium text-gray-900 dark:text-white truncate">
|
<span class="text-xs font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
{if change.device, do: change.device.name, else: "Unknown"}
|
{if change.device, do: change.device.name, else: "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-[10px] text-gray-400 dark:text-gray-500 dark:text-gray-400 flex-shrink-0">
|
<span class="font-mono text-[10px] text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400 flex-shrink-0">
|
||||||
{change.change_size}L
|
{change.change_size}L
|
||||||
</span>
|
</span>
|
||||||
<span class="text-[10px] text-gray-400 dark:text-gray-500 dark:text-gray-400 truncate">
|
<span class="text-[10px] text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{Enum.join(Enum.take(change.sections_changed, 2), ", ")}
|
{Enum.join(Enum.take(change.sections_changed, 2), ", ")}
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-auto flex-shrink-0 font-mono text-[10px] text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<span class="ml-auto flex-shrink-0 font-mono text-[10px] text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{format_short_datetime(change.changed_at, @timezone)}
|
{format_short_datetime(change.changed_at, @timezone)}
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="rounded-md border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-800/50 px-3 py-6 text-center">
|
<div class="rounded-md border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-800/50 px-3 py-6 text-center">
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No config changes in 7 days
|
No config changes in 7 days
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -880,26 +904,26 @@
|
||||||
<%= if assigns[:recent_activity] && @recent_activity != [] do %>
|
<%= if assigns[:recent_activity] && @recent_activity != [] do %>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h2 class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<h2 class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Recent Activity")}
|
{t("Recent Activity")}
|
||||||
</h2>
|
</h2>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/activity"}
|
navigate={~p"/activity"}
|
||||||
class="text-[10px] font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
class="text-[10px] font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
all activity →
|
all activity →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-md border border-gray-200 dark:border-white/10 divide-y divide-gray-100 dark:divide-white/5">
|
<div class="overflow-hidden rounded-md border border-cool-steel-200 dark:border-white/10 divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<div
|
<div
|
||||||
:for={activity <- @recent_activity}
|
:for={activity <- @recent_activity}
|
||||||
class="flex items-center gap-2 px-3 py-1.5 bg-white dark:bg-gray-800/50"
|
class="flex items-center gap-2 px-3 py-1.5 bg-white dark:bg-cool-steel-800/50"
|
||||||
>
|
>
|
||||||
<span class="h-1 w-1 rounded-full bg-blue-400 dark:bg-blue-500 flex-shrink-0" />
|
<span class="h-1 w-1 rounded-full bg-cerulean-400 dark:bg-cerulean-500 flex-shrink-0" />
|
||||||
<p class="text-xs text-gray-700 dark:text-gray-300 min-w-0 flex-1 truncate">
|
<p class="text-xs text-cool-steel-700 dark:text-cool-steel-300 min-w-0 flex-1 truncate">
|
||||||
{activity.summary}
|
{activity.summary}
|
||||||
</p>
|
</p>
|
||||||
<span class="flex-shrink-0 font-mono text-[10px] text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<span class="flex-shrink-0 font-mono text-[10px] text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{format_short_datetime(activity.timestamp, @timezone)}
|
{format_short_datetime(activity.timestamp, @timezone)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -910,30 +934,30 @@
|
||||||
<%!-- ═══════════════════════════════════════════════
|
<%!-- ═══════════════════════════════════════════════
|
||||||
FOOTER — Command palette hint
|
FOOTER — Command palette hint
|
||||||
═══════════════════════════════════════════════ --%>
|
═══════════════════════════════════════════════ --%>
|
||||||
<div class="mt-6 flex items-center justify-center border-t border-gray-100 dark:border-white/5 pt-4 pb-2">
|
<div class="mt-6 flex items-center justify-center border-t border-cool-steel-100 dark:border-white/5 pt-4 pb-2">
|
||||||
<span class="text-[10px] text-gray-300 dark:text-gray-600">
|
<span class="text-[10px] text-cool-steel-300 dark:text-cool-steel-600">
|
||||||
<kbd class="px-1 py-0.5 rounded border border-gray-200 dark:border-gray-700 font-mono text-gray-400 dark:text-gray-500 dark:text-gray-400">
|
<kbd class="px-1 py-0.5 rounded border border-cool-steel-200 dark:border-cool-steel-700 font-mono text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
⌘K
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
<span class="ml-1">to search</span>
|
<span class="ml-1">to search</span>
|
||||||
<span class="mx-2">·</span>
|
<span class="mx-2">·</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/new"}
|
navigate={~p"/devices/new"}
|
||||||
class="hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-400"
|
class="hover:text-cool-steel-500 dark:text-cool-steel-400 dark:hover:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
+ device
|
+ device
|
||||||
</.link>
|
</.link>
|
||||||
<span class="mx-2">·</span>
|
<span class="mx-2">·</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/trace"}
|
navigate={~p"/trace"}
|
||||||
class="hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-400"
|
class="hover:text-cool-steel-500 dark:text-cool-steel-400 dark:hover:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
trace
|
trace
|
||||||
</.link>
|
</.link>
|
||||||
<span class="mx-2">·</span>
|
<span class="mx-2">·</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/network-map"}
|
navigate={~p"/network-map"}
|
||||||
class="hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-400"
|
class="hover:text-cool-steel-500 dark:text-cool-steel-400 dark:hover:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
map
|
map
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div phx-hook="ScrollToTop" id="scroll-container" phx-update="ignore">
|
<div phx-hook="ScrollToTop" id="scroll-container" phx-update="ignore">
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={
|
navigate={
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
do: ~p"/devices/#{@device.id}",
|
do: ~p"/devices/#{@device.id}",
|
||||||
else: ~p"/devices"
|
else: ~p"/devices"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="inline-flex items-center gap-1 text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
||||||
{if @live_action == :edit, do: "Back to Device", else: "Back to Device List"}
|
{if @live_action == :edit, do: "Back to Device", else: "Back to Device List"}
|
||||||
|
|
@ -21,10 +21,10 @@
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{@page_title}
|
{@page_title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{if @live_action == :new,
|
{if @live_action == :new,
|
||||||
do: "Add new device to monitor",
|
do: "Add new device to monitor",
|
||||||
else: "Update device details"}
|
else: "Update device details"}
|
||||||
|
|
@ -42,19 +42,19 @@
|
||||||
<% badge_class =
|
<% badge_class =
|
||||||
cond do
|
cond do
|
||||||
@current_scope.user.is_superuser ->
|
@current_scope.user.is_superuser ->
|
||||||
"bg-blue-50 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400"
|
"bg-cerulean-50 text-cerulean-800 dark:bg-cerulean-900/20 dark:text-cerulean-400"
|
||||||
|
|
||||||
@device_quota.at_limit ->
|
@device_quota.at_limit ->
|
||||||
"bg-red-50 text-red-800 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800"
|
"bg-sweet-salmon-50 text-sweet-salmon-800 border-sweet-salmon-200 dark:bg-sweet-salmon-900/20 dark:text-sweet-salmon-400 dark:border-sweet-salmon-800"
|
||||||
|
|
||||||
percent >= 90 ->
|
percent >= 90 ->
|
||||||
"bg-orange-50 text-orange-800 border-orange-200 dark:bg-orange-900/20 dark:text-orange-400 dark:border-orange-800"
|
"bg-orange-50 text-orange-800 border-orange-200 dark:bg-orange-900/20 dark:text-orange-400 dark:border-orange-800"
|
||||||
|
|
||||||
percent >= 75 ->
|
percent >= 75 ->
|
||||||
"bg-yellow-50 text-yellow-800 border-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-400 dark:border-yellow-800"
|
"bg-wheat-50 text-wheat-800 border-wheat-200 dark:bg-wheat-900/20 dark:text-wheat-400 dark:border-wheat-800"
|
||||||
|
|
||||||
@device_quota.limit == :unlimited ->
|
@device_quota.limit == :unlimited ->
|
||||||
"bg-blue-50 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400"
|
"bg-cerulean-50 text-cerulean-800 dark:bg-cerulean-900/20 dark:text-cerulean-400"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"bg-green-50 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-400 dark:border-green-800"
|
"bg-green-50 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-400 dark:border-green-800"
|
||||||
|
|
@ -96,14 +96,14 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<.form for={@form} id="device-form" phx-change="validate" phx-submit="save">
|
<.form for={@form} id="device-form" phx-change="validate" phx-submit="save">
|
||||||
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<!-- Basic Information -->
|
<!-- Basic Information -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Basic Information")}
|
{t("Basic Information")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Core device identification and location details.")}
|
{t("Core device identification and location details.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -129,15 +129,15 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:if={@duplicate_device}
|
:if={@duplicate_device}
|
||||||
class="rounded-md bg-amber-50 dark:bg-amber-900/20 p-3 mt-2"
|
class="rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-3 mt-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2 text-amber-800 dark:text-amber-200">
|
<div class="flex items-center gap-2 text-wheat-800 dark:text-wheat-200">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0" />
|
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0" />
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
{t("A device with this IP address already exists:")}
|
{t("A device with this IP address already exists:")}
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@duplicate_device.id}"}
|
navigate={~p"/devices/#{@duplicate_device.id}"}
|
||||||
class="font-medium underline hover:text-amber-900 dark:hover:text-amber-100"
|
class="font-medium underline hover:text-wheat-900 dark:hover:text-wheat-100"
|
||||||
>
|
>
|
||||||
{@duplicate_device.name || @duplicate_device.ip_address}
|
{@duplicate_device.name || @duplicate_device.ip_address}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -147,9 +147,9 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:if={@non_routable_ip_error}
|
:if={@non_routable_ip_error}
|
||||||
class="rounded-md bg-red-50 dark:bg-red-900/20 p-3 mt-2"
|
class="rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 p-3 mt-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-2 text-red-800 dark:text-red-200">
|
<div class="flex items-start gap-2 text-sweet-salmon-800 dark:text-sweet-salmon-200">
|
||||||
<.icon name="hero-exclamation-circle" class="h-5 w-5 flex-shrink-0 mt-0.5" />
|
<.icon name="hero-exclamation-circle" class="h-5 w-5 flex-shrink-0 mt-0.5" />
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p class="font-medium">Non-routable IP address detected</p>
|
<p class="font-medium">Non-routable IP address detected</p>
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
prompt="No site (ungrouped)"
|
prompt="No site (ungrouped)"
|
||||||
options={Enum.map(@available_sites, &{&1.name, &1.id})}
|
options={Enum.map(@available_sites, &{&1.name, &1.id})}
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Optionally group this device into a site (office, datacenter, etc.)")}
|
{t("Optionally group this device into a site (office, datacenter, etc.)")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -201,7 +201,7 @@
|
||||||
{"Other", "other"}
|
{"Other", "other"}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Select the type of device. This helps with network visualization and organization."
|
"Select the type of device. This helps with network visualization and organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -214,10 +214,10 @@
|
||||||
<!-- Monitoring Configuration -->
|
<!-- Monitoring Configuration -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Monitoring Configuration")}
|
{t("Monitoring Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Configure how and how often this device should be monitored.")}
|
{t("Configure how and how often this device should be monitored.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -263,35 +263,35 @@
|
||||||
<%= if @live_action == :edit and Map.has_key?(assigns, :agent_source) do %>
|
<%= if @live_action == :edit and Map.has_key?(assigns, :agent_source) do %>
|
||||||
<%= case @agent_source do %>
|
<%= case @agent_source do %>
|
||||||
<% :device -> %>
|
<% :device -> %>
|
||||||
<p class="mt-2 text-sm text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-wheat-600 dark:text-wheat-400 flex items-center gap-1">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
||||||
<strong>{t("Overriding")}</strong>
|
<strong>{t("Overriding")}</strong>
|
||||||
site/organization defaults - this device is explicitly assigned
|
site/organization defaults - this device is explicitly assigned
|
||||||
</p>
|
</p>
|
||||||
<% :site -> %>
|
<% :site -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-building-office" class="h-4 w-4" />
|
<.icon name="hero-building-office" class="h-4 w-4" />
|
||||||
Inherited from site: <strong>{@effective_agent_name}</strong>
|
Inherited from site: <strong>{@effective_agent_name}</strong>
|
||||||
</p>
|
</p>
|
||||||
<% :organization -> %>
|
<% :organization -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
||||||
Inherited from organization: <strong>{@effective_agent_name}</strong>
|
Inherited from organization: <strong>{@effective_agent_name}</strong>
|
||||||
</p>
|
</p>
|
||||||
<% :global -> %>
|
<% :global -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-globe-alt" class="h-4 w-4" />
|
<.icon name="hero-globe-alt" class="h-4 w-4" />
|
||||||
{t("Using global default cloud poller:")}
|
{t("Using global default cloud poller:")}
|
||||||
<strong>{@effective_agent_name}</strong>
|
<strong>{@effective_agent_name}</strong>
|
||||||
</p>
|
</p>
|
||||||
<% :none -> %>
|
<% :none -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-cloud" class="h-4 w-4" />
|
<.icon name="hero-cloud" class="h-4 w-4" />
|
||||||
{t("No agent assigned - cloud polling")}
|
{t("No agent assigned - cloud polling")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Assign this device to a remote agent for local SNMP polling. Leave empty to inherit from site or organization defaults."
|
"Assign this device to a remote agent for local SNMP polling. Leave empty to inherit from site or organization defaults."
|
||||||
)}
|
)}
|
||||||
|
|
@ -309,7 +309,7 @@
|
||||||
prompt={t("Inherit from organization default")}
|
prompt={t("Inherit from organization default")}
|
||||||
options={Enum.map(@escalation_policies, &{&1.name, &1.id})}
|
options={Enum.map(@escalation_policies, &{&1.name, &1.id})}
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Select an escalation policy for alert notifications. Leave empty to use the organization default."
|
"Select an escalation policy for alert notifications. Leave empty to use the organization default."
|
||||||
)}
|
)}
|
||||||
|
|
@ -323,10 +323,10 @@
|
||||||
<!-- SNMP Configuration -->
|
<!-- SNMP Configuration -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("SNMP Configuration")}
|
{t("SNMP Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Choose monitoring mode and configure SNMP settings for detailed device information."
|
"Choose monitoring mode and configure SNMP settings for detailed device information."
|
||||||
)}
|
)}
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Monitoring Mode Tabs -->
|
<!-- Monitoring Mode Tabs -->
|
||||||
<div>
|
<div>
|
||||||
<div class="border-b border-gray-200 dark:border-white/10">
|
<div class="border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="-mb-px flex space-x-8" aria-label={t("Monitoring mode")}>
|
<nav class="-mb-px flex space-x-8" aria-label={t("Monitoring mode")}>
|
||||||
<button
|
<button
|
||||||
id="monitoring-mode-snmp-and-icmp"
|
id="monitoring-mode-snmp-and-icmp"
|
||||||
|
|
@ -347,9 +347,9 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||||
@monitoring_mode == "snmp_and_icmp" &&
|
@monitoring_mode == "snmp_and_icmp" &&
|
||||||
"border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-400",
|
"border-cerulean-500 text-cerulean-600 dark:border-cerulean-400 dark:text-cerulean-400",
|
||||||
@monitoring_mode != "snmp_and_icmp" &&
|
@monitoring_mode != "snmp_and_icmp" &&
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("SNMP & ICMP")}
|
{t("SNMP & ICMP")}
|
||||||
|
|
@ -362,16 +362,16 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||||
@monitoring_mode == "icmp_only" &&
|
@monitoring_mode == "icmp_only" &&
|
||||||
"border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-400",
|
"border-cerulean-500 text-cerulean-600 dark:border-cerulean-400 dark:text-cerulean-400",
|
||||||
@monitoring_mode != "icmp_only" &&
|
@monitoring_mode != "icmp_only" &&
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("ICMP Only")}
|
{t("ICMP Only")}
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<div class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if @monitoring_mode == "snmp_and_icmp" do %>
|
<%= if @monitoring_mode == "snmp_and_icmp" do %>
|
||||||
<p>
|
<p>
|
||||||
{t(
|
{t(
|
||||||
|
|
@ -415,12 +415,12 @@
|
||||||
<%= if @live_action == :edit and Map.has_key?(assigns, :snmp_config_source) do %>
|
<%= if @live_action == :edit and Map.has_key?(assigns, :snmp_config_source) do %>
|
||||||
<%= case @snmp_config_source do %>
|
<%= case @snmp_config_source do %>
|
||||||
<% :device -> %>
|
<% :device -> %>
|
||||||
<p class="mt-2 text-sm text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-wheat-600 dark:text-wheat-400 flex items-center gap-1">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
||||||
<strong>{t("Overriding")}</strong> site/organization SNMP defaults
|
<strong>{t("Overriding")}</strong> site/organization SNMP defaults
|
||||||
</p>
|
</p>
|
||||||
<% :site -> %>
|
<% :site -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-building-office" class="h-4 w-4" />
|
<.icon name="hero-building-office" class="h-4 w-4" />
|
||||||
Inherited from site (v{@effective_snmp_version}, community: {String.slice(
|
Inherited from site (v{@effective_snmp_version}, community: {String.slice(
|
||||||
@effective_snmp_community,
|
@effective_snmp_community,
|
||||||
|
|
@ -431,7 +431,7 @@
|
||||||
)})
|
)})
|
||||||
</p>
|
</p>
|
||||||
<% :organization -> %>
|
<% :organization -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
||||||
Inherited from organization (v{@effective_snmp_version}, community: {String.slice(
|
Inherited from organization (v{@effective_snmp_version}, community: {String.slice(
|
||||||
@effective_snmp_community,
|
@effective_snmp_community,
|
||||||
|
|
@ -442,7 +442,7 @@
|
||||||
)})
|
)})
|
||||||
</p>
|
</p>
|
||||||
<% :default -> %>
|
<% :default -> %>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-information-circle" class="h-4 w-4" />
|
<.icon name="hero-information-circle" class="h-4 w-4" />
|
||||||
{t(
|
{t(
|
||||||
"Using default (v2c, no community set - configure at organization or site level)"
|
"Using default (v2c, no community set - configure at organization or site level)"
|
||||||
|
|
@ -589,16 +589,16 @@
|
||||||
<%= if @snmp_test_result do %>
|
<%= if @snmp_test_result do %>
|
||||||
<%= cond do %>
|
<%= cond do %>
|
||||||
<% Map.get(@snmp_test_result, :testing) -> %>
|
<% Map.get(@snmp_test_result, :testing) -> %>
|
||||||
<div class="mt-3 rounded-md bg-blue-50 p-4 dark:bg-blue-900/20">
|
<div class="mt-3 rounded-md bg-cerulean-50 p-4 dark:bg-cerulean-900/20">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-path"
|
name="hero-arrow-path"
|
||||||
class="h-5 w-5 text-blue-400 animate-spin"
|
class="h-5 w-5 text-cerulean-400 animate-spin"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm text-blue-700 dark:text-blue-400">
|
<p class="text-sm text-cerulean-700 dark:text-cerulean-400">
|
||||||
{t("Testing SNMP credentials...")}
|
{t("Testing SNMP credentials...")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -621,16 +621,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% true -> %>
|
<% true -> %>
|
||||||
<div class="mt-3 rounded-md bg-red-50 p-4 dark:bg-red-900/20">
|
<div class="mt-3 rounded-md bg-sweet-salmon-50 p-4 dark:bg-sweet-salmon-900/20">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<.icon name="hero-x-circle" class="h-5 w-5 text-red-400" />
|
<.icon name="hero-x-circle" class="h-5 w-5 text-sweet-salmon-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm font-medium text-red-800 dark:text-red-400">
|
<p class="text-sm font-medium text-sweet-salmon-800 dark:text-sweet-salmon-400">
|
||||||
{t("Connection failed")}
|
{t("Connection failed")}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-red-700 dark:text-red-300">
|
<p class="mt-1 text-sm text-sweet-salmon-700 dark:text-sweet-salmon-300">
|
||||||
{Map.get(@snmp_test_result, :message)}
|
{Map.get(@snmp_test_result, :message)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -653,16 +653,16 @@
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("MikroTik API Configuration")}
|
{t("MikroTik API Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Configure MikroTik RouterOS API access. Works alongside SNMP for enhanced device management. SSH is used for device backups."
|
"Configure MikroTik RouterOS API access. Works alongside SNMP for enhanced device management. SSH is used for device backups."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
|
<div class="mt-3 rounded-md bg-cerulean-50 dark:bg-cerulean-900/20 p-3">
|
||||||
<p class="text-xs text-blue-700 dark:text-blue-300">
|
<p class="text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
||||||
<strong>{t("Experimental Feature:")}</strong>
|
<strong>{t("Experimental Feature:")}</strong>
|
||||||
{t("MikroTik API integration is under active development.")}
|
{t("MikroTik API integration is under active development.")}
|
||||||
|
|
@ -673,8 +673,8 @@
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<%= if Map.has_key?(assigns, :mikrotik_config_source) and @mikrotik_config_source != :device do %>
|
<%= if Map.has_key?(assigns, :mikrotik_config_source) and @mikrotik_config_source != :device do %>
|
||||||
<div class="rounded-md bg-blue-50 dark:bg-blue-900/20 p-4">
|
<div class="rounded-md bg-cerulean-50 dark:bg-cerulean-900/20 p-4">
|
||||||
<p class="text-sm text-blue-700 dark:text-blue-300">
|
<p class="text-sm text-cerulean-700 dark:text-cerulean-300">
|
||||||
<strong>Inheriting credentials from {@mikrotik_config_source}.</strong>
|
<strong>Inheriting credentials from {@mikrotik_config_source}.</strong>
|
||||||
Username: <span class="font-mono">{@effective_mikrotik_username}</span>
|
Username: <span class="font-mono">{@effective_mikrotik_username}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -727,8 +727,8 @@
|
||||||
|
|
||||||
<%= if @form[:mikrotik_use_ssl].value == false do %>
|
<%= if @form[:mikrotik_use_ssl].value == false do %>
|
||||||
<div class="col-span-full">
|
<div class="col-span-full">
|
||||||
<div class="rounded-md bg-red-50 dark:bg-red-900/20 p-4">
|
<div class="rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 p-4">
|
||||||
<p class="text-sm text-red-700 dark:text-red-300">
|
<p class="text-sm text-sweet-salmon-700 dark:text-sweet-salmon-300">
|
||||||
<strong>🚨 Critical Security Warning:</strong>
|
<strong>🚨 Critical Security Warning:</strong>
|
||||||
{t(
|
{t(
|
||||||
"Plain API (port 8728) sends credentials unencrypted over the network."
|
"Plain API (port 8728) sends credentials unencrypted over the network."
|
||||||
|
|
@ -750,7 +750,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Save/Cancel Footer -->
|
<!-- Save/Cancel Footer -->
|
||||||
<div class="flex items-center justify-between gap-x-6 border-t border-gray-200 px-4 py-4 sm:px-8 dark:border-white/10">
|
<div class="flex items-center justify-between gap-x-6 border-t border-cool-steel-200 px-4 py-4 sm:px-8 dark:border-white/10">
|
||||||
<.button navigate={~p"/devices"}>{t("Cancel")}</.button>
|
<.button navigate={~p"/devices"}>{t("Cancel")}</.button>
|
||||||
<.button
|
<.button
|
||||||
phx-disable-with={t("Saving...")}
|
phx-disable-with={t("Saving...")}
|
||||||
|
|
@ -764,19 +764,19 @@
|
||||||
|
|
||||||
<!-- Danger Zone -->
|
<!-- Danger Zone -->
|
||||||
<%= if @live_action == :edit do %>
|
<%= if @live_action == :edit do %>
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8 border-t border-gray-200 dark:border-white/10">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-red-600 dark:text-red-500">
|
<h2 class="text-base/7 font-semibold text-sweet-salmon-600 dark:text-sweet-salmon-500">
|
||||||
{t("Danger Zone")}
|
{t("Danger Zone")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Permanently delete this device and all its data.")}
|
{t("Permanently delete this device and all its data.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<div class="rounded-md bg-red-50 dark:bg-red-900/20 p-4 border border-red-200 dark:border-red-800">
|
<div class="rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 p-4 border border-sweet-salmon-200 dark:border-sweet-salmon-800">
|
||||||
<p class="text-sm text-red-800 dark:text-red-200 mb-4">
|
<p class="text-sm text-sweet-salmon-800 dark:text-sweet-salmon-200 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Once you delete this device, there is no going back. All monitoring history and alerts will be permanently deleted."
|
"Once you delete this device, there is no going back. All monitoring history and alerts will be permanently deleted."
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -251,15 +251,18 @@ defmodule ToweropsWeb.DeviceLive.Helpers.Formatters do
|
||||||
Returns Tailwind CSS classes for capacity source badges.
|
Returns Tailwind CSS classes for capacity source badges.
|
||||||
"""
|
"""
|
||||||
@spec capacity_source_class(String.t()) :: String.t()
|
@spec capacity_source_class(String.t()) :: String.t()
|
||||||
def capacity_source_class("manual"), do: "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
|
def capacity_source_class("manual"),
|
||||||
|
do: "bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/50 dark:text-cerulean-300"
|
||||||
|
|
||||||
def capacity_source_class("sensor"), do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"
|
def capacity_source_class("sensor"), do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"
|
||||||
|
|
||||||
def capacity_source_class("if_speed"), do: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
def capacity_source_class("if_speed"),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def capacity_source_class("peak"), do: "bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300"
|
def capacity_source_class("peak"), do: "bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300"
|
||||||
|
|
||||||
def capacity_source_class(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
def capacity_source_class(_),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns background color class for utilization percentage.
|
Returns background color class for utilization percentage.
|
||||||
|
|
@ -269,8 +272,8 @@ defmodule ToweropsWeb.DeviceLive.Helpers.Formatters do
|
||||||
- < 70%: green
|
- < 70%: green
|
||||||
"""
|
"""
|
||||||
@spec utilization_color(number()) :: String.t()
|
@spec utilization_color(number()) :: String.t()
|
||||||
def utilization_color(pct) when pct >= 90, do: "bg-red-500"
|
def utilization_color(pct) when pct >= 90, do: "bg-sweet-salmon-500"
|
||||||
def utilization_color(pct) when pct >= 70, do: "bg-yellow-500"
|
def utilization_color(pct) when pct >= 70, do: "bg-wheat-500"
|
||||||
def utilization_color(_pct), do: "bg-green-500"
|
def utilization_color(_pct), do: "bg-green-500"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -281,7 +284,7 @@ defmodule ToweropsWeb.DeviceLive.Helpers.Formatters do
|
||||||
- < 70%: green
|
- < 70%: green
|
||||||
"""
|
"""
|
||||||
@spec utilization_text_color(number()) :: String.t()
|
@spec utilization_text_color(number()) :: String.t()
|
||||||
def utilization_text_color(pct) when pct >= 90, do: "text-red-600 dark:text-red-400"
|
def utilization_text_color(pct) when pct >= 90, do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def utilization_text_color(pct) when pct >= 70, do: "text-yellow-600 dark:text-yellow-400"
|
def utilization_text_color(pct) when pct >= 70, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -508,12 +508,12 @@ defmodule ToweropsWeb.DeviceLive.Index do
|
||||||
|
|
||||||
# Health indicator helpers
|
# Health indicator helpers
|
||||||
|
|
||||||
defp health_dot_color(nil), do: "bg-gray-400"
|
defp health_dot_color(nil), do: "bg-cool-steel-400"
|
||||||
defp health_dot_color(%{worst_status: 0}), do: "bg-green-500"
|
defp health_dot_color(%{worst_status: 0}), do: "bg-green-500"
|
||||||
defp health_dot_color(%{worst_status: 1}), do: "bg-yellow-500"
|
defp health_dot_color(%{worst_status: 1}), do: "bg-wheat-500"
|
||||||
defp health_dot_color(%{worst_status: 2}), do: "bg-red-500"
|
defp health_dot_color(%{worst_status: 2}), do: "bg-sweet-salmon-500"
|
||||||
defp health_dot_color(%{worst_status: 3}), do: "bg-gray-400"
|
defp health_dot_color(%{worst_status: 3}), do: "bg-cool-steel-400"
|
||||||
defp health_dot_color(_), do: "bg-gray-400"
|
defp health_dot_color(_), do: "bg-cool-steel-400"
|
||||||
|
|
||||||
defp health_dot_title(nil), do: "No check data"
|
defp health_dot_title(nil), do: "No check data"
|
||||||
defp health_dot_title(%{worst_status: 0}), do: "All checks passing"
|
defp health_dot_title(%{worst_status: 0}), do: "All checks passing"
|
||||||
|
|
@ -522,7 +522,7 @@ defmodule ToweropsWeb.DeviceLive.Index do
|
||||||
defp health_dot_title(%{worst_status: 3}), do: "Unknown"
|
defp health_dot_title(%{worst_status: 3}), do: "Unknown"
|
||||||
defp health_dot_title(_), do: "No check data"
|
defp health_dot_title(_), do: "No check data"
|
||||||
|
|
||||||
defp last_seen_text(nil), do: {"Never", "text-gray-400 dark:text-gray-600"}
|
defp last_seen_text(nil), do: {"Never", "text-cool-steel-400 dark:text-cool-steel-600"}
|
||||||
|
|
||||||
defp last_seen_text(datetime) do
|
defp last_seen_text(datetime) do
|
||||||
diff_seconds = DateTime.diff(DateTime.utc_now(), datetime, :second)
|
diff_seconds = DateTime.diff(DateTime.utc_now(), datetime, :second)
|
||||||
|
|
@ -531,15 +531,15 @@ defmodule ToweropsWeb.DeviceLive.Index do
|
||||||
diff_seconds < 0 -> {"just now", "text-green-600 dark:text-green-400"}
|
diff_seconds < 0 -> {"just now", "text-green-600 dark:text-green-400"}
|
||||||
diff_seconds < 60 -> {"#{diff_seconds}s ago", "text-green-600 dark:text-green-400"}
|
diff_seconds < 60 -> {"#{diff_seconds}s ago", "text-green-600 dark:text-green-400"}
|
||||||
diff_seconds < 600 -> {"#{div(diff_seconds, 60)}m ago", "text-green-600 dark:text-green-400"}
|
diff_seconds < 600 -> {"#{div(diff_seconds, 60)}m ago", "text-green-600 dark:text-green-400"}
|
||||||
diff_seconds < 3600 -> {"#{div(diff_seconds, 60)}m ago", "text-yellow-600 dark:text-yellow-400"}
|
diff_seconds < 3600 -> {"#{div(diff_seconds, 60)}m ago", "text-wheat-600 dark:text-wheat-400"}
|
||||||
diff_seconds < 86_400 -> {"#{div(diff_seconds, 3600)}h ago", "text-red-600 dark:text-red-400"}
|
diff_seconds < 86_400 -> {"#{div(diff_seconds, 3600)}h ago", "text-sweet-salmon-600 dark:text-sweet-salmon-400"}
|
||||||
true -> {"#{div(diff_seconds, 86_400)}d ago", "text-red-600 dark:text-red-400"}
|
true -> {"#{div(diff_seconds, 86_400)}d ago", "text-sweet-salmon-600 dark:text-sweet-salmon-400"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp response_time_badge(nil), do: {"-", "text-gray-400"}
|
defp response_time_badge(nil), do: {"-", "text-cool-steel-400"}
|
||||||
|
|
||||||
defp response_time_badge(%{response_time_ms: nil}), do: {"-", "text-gray-400"}
|
defp response_time_badge(%{response_time_ms: nil}), do: {"-", "text-cool-steel-400"}
|
||||||
|
|
||||||
defp response_time_badge(%{response_time_ms: ms}) do
|
defp response_time_badge(%{response_time_ms: ms}) do
|
||||||
formatted =
|
formatted =
|
||||||
|
|
@ -552,8 +552,8 @@ defmodule ToweropsWeb.DeviceLive.Index do
|
||||||
color =
|
color =
|
||||||
cond do
|
cond do
|
||||||
ms < 20 -> "text-green-600 dark:text-green-400"
|
ms < 20 -> "text-green-600 dark:text-green-400"
|
||||||
ms <= 100 -> "text-yellow-600 dark:text-yellow-400"
|
ms <= 100 -> "text-wheat-600 dark:text-wheat-400"
|
||||||
true -> "text-red-600 dark:text-red-400"
|
true -> "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
end
|
end
|
||||||
|
|
||||||
{formatted, color}
|
{formatted, color}
|
||||||
|
|
@ -567,14 +567,14 @@ defmodule ToweropsWeb.DeviceLive.Index do
|
||||||
min(100, round(ms / 2))
|
min(100, round(ms / 2))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp response_time_bar_color(nil), do: "bg-gray-300"
|
defp response_time_bar_color(nil), do: "bg-cool-steel-300"
|
||||||
defp response_time_bar_color(%{response_time_ms: nil}), do: "bg-gray-300"
|
defp response_time_bar_color(%{response_time_ms: nil}), do: "bg-cool-steel-300"
|
||||||
|
|
||||||
defp response_time_bar_color(%{response_time_ms: ms}) do
|
defp response_time_bar_color(%{response_time_ms: ms}) do
|
||||||
cond do
|
cond do
|
||||||
ms < 20 -> "bg-green-500"
|
ms < 20 -> "bg-green-500"
|
||||||
ms <= 100 -> "bg-yellow-500"
|
ms <= 100 -> "bg-wheat-500"
|
||||||
true -> "bg-red-500"
|
true -> "bg-sweet-salmon-500"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<%!-- Header: title + actions, then tabs below on mobile --%>
|
<%!-- Header: title + actions, then tabs below on mobile --%>
|
||||||
<div class="mb-4 border-b border-gray-200 dark:border-white/10 pb-3">
|
<div class="mb-4 border-b border-cool-steel-200 dark:border-white/10 pb-3">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white truncate">{@page_title}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white truncate">
|
||||||
|
{@page_title}
|
||||||
|
</h1>
|
||||||
<% percent =
|
<% percent =
|
||||||
if @device_quota.limit != :unlimited and @device_quota.limit > 0 do
|
if @device_quota.limit != :unlimited and @device_quota.limit > 0 do
|
||||||
(@device_quota.current / @device_quota.limit * 100) |> trunc()
|
(@device_quota.current / @device_quota.limit * 100) |> trunc()
|
||||||
|
|
@ -18,16 +20,16 @@
|
||||||
<% badge_class =
|
<% badge_class =
|
||||||
cond do
|
cond do
|
||||||
@current_scope.user.is_superuser ->
|
@current_scope.user.is_superuser ->
|
||||||
"bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400"
|
"bg-cerulean-50 text-cerulean-700 dark:bg-cerulean-900/20 dark:text-cerulean-400"
|
||||||
|
|
||||||
@device_quota.limit != :unlimited and @device_quota.current >= @device_quota.limit ->
|
@device_quota.limit != :unlimited and @device_quota.current >= @device_quota.limit ->
|
||||||
"bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400"
|
"bg-sweet-salmon-50 text-sweet-salmon-700 dark:bg-sweet-salmon-900/20 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
percent >= 90 ->
|
percent >= 90 ->
|
||||||
"bg-orange-50 text-orange-700 dark:bg-orange-900/20 dark:text-orange-400"
|
"bg-orange-50 text-orange-700 dark:bg-orange-900/20 dark:text-orange-400"
|
||||||
|
|
||||||
@device_quota.limit == :unlimited ->
|
@device_quota.limit == :unlimited ->
|
||||||
"bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400"
|
"bg-cerulean-50 text-cerulean-700 dark:bg-cerulean-900/20 dark:text-cerulean-400"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400"
|
"bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400"
|
||||||
|
|
@ -98,9 +100,9 @@
|
||||||
class={[
|
class={[
|
||||||
"px-2.5 py-1 rounded-md text-sm font-medium transition-colors",
|
"px-2.5 py-1 rounded-md text-sm font-medium transition-colors",
|
||||||
if @active_tab == "existing" do
|
if @active_tab == "existing" do
|
||||||
"bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-white"
|
"bg-cool-steel-100 text-cool-steel-900 dark:bg-cool-steel-800 dark:text-white"
|
||||||
else
|
else
|
||||||
"text-gray-500 hover:text-gray-700 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:bg-gray-800/50"
|
"text-cool-steel-500 hover:text-cool-steel-700 hover:bg-cool-steel-50 dark:text-cool-steel-400 dark:hover:text-cool-steel-300 dark:hover:bg-cool-steel-800/50"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -111,16 +113,16 @@
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-sm font-medium transition-colors",
|
"inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-sm font-medium transition-colors",
|
||||||
if @active_tab == "discovered" do
|
if @active_tab == "discovered" do
|
||||||
"bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-white"
|
"bg-cool-steel-100 text-cool-steel-900 dark:bg-cool-steel-800 dark:text-white"
|
||||||
else
|
else
|
||||||
"text-gray-500 hover:text-gray-700 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:bg-gray-800/50"
|
"text-cool-steel-500 hover:text-cool-steel-700 hover:bg-cool-steel-50 dark:text-cool-steel-400 dark:hover:text-cool-steel-300 dark:hover:bg-cool-steel-800/50"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Discovered")}
|
{t("Discovered")}
|
||||||
<span
|
<span
|
||||||
:if={@pagination && @pagination.total_count > 0}
|
:if={@pagination && @pagination.total_count > 0}
|
||||||
class="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
class="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900 dark:text-cerulean-200"
|
||||||
>
|
>
|
||||||
{@pagination.total_count}
|
{@pagination.total_count}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -131,9 +133,9 @@
|
||||||
<%= case @active_tab do %>
|
<%= case @active_tab do %>
|
||||||
<% "existing" -> %>
|
<% "existing" -> %>
|
||||||
<%= if @sites_enabled && !@has_sites && @has_devices do %>
|
<%= if @sites_enabled && !@has_sites && @has_devices do %>
|
||||||
<div class="rounded-md bg-blue-50 dark:bg-blue-900/20 p-3 mb-4 flex items-center gap-3">
|
<div class="rounded-md bg-cerulean-50 dark:bg-cerulean-900/20 p-3 mb-4 flex items-center gap-3">
|
||||||
<.icon name="hero-information-circle" class="h-5 w-5 text-blue-400 flex-shrink-0" />
|
<.icon name="hero-information-circle" class="h-5 w-5 text-cerulean-400 flex-shrink-0" />
|
||||||
<p class="text-sm text-blue-800 dark:text-blue-200 flex-1">
|
<p class="text-sm text-cerulean-800 dark:text-cerulean-200 flex-1">
|
||||||
{t("Organize your devices with sites")} —
|
{t("Organize your devices with sites")} —
|
||||||
<.link navigate={~p"/sites/new"} class="font-medium underline">
|
<.link navigate={~p"/sites/new"} class="font-medium underline">
|
||||||
{t("Create a site")}
|
{t("Create a site")}
|
||||||
|
|
@ -146,12 +148,12 @@
|
||||||
<div class="text-center py-16">
|
<div class="text-center py-16">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-server"
|
name="hero-server"
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500 dark:text-gray-400"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No devices")}
|
{t("No devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Get started by adding your first device.")}
|
{t("Get started by adding your first device.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
|
@ -162,14 +164,14 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%!-- Sticky Status Bar + Search --%>
|
<%!-- Sticky Status Bar + Search --%>
|
||||||
<div class="sticky top-0 z-10 bg-white dark:bg-gray-900 pb-3 -mx-4 px-4 sm:-mx-6 sm:px-6 lg:-ml-4 lg:-mr-8 lg:pl-4 lg:pr-8 border-b border-gray-100 dark:border-white/5">
|
<div class="sticky top-0 z-10 bg-white dark:bg-cool-steel-900 pb-3 -mx-4 px-4 sm:-mx-6 sm:px-6 lg:-ml-4 lg:-mr-8 lg:pl-4 lg:pr-8 border-b border-cool-steel-100 dark:border-white/5">
|
||||||
<div class="flex items-center gap-3 pt-2">
|
<div class="flex items-center gap-3 pt-2">
|
||||||
<%!-- Search --%>
|
<%!-- Search --%>
|
||||||
<form phx-change="search" class="flex-1 max-w-sm">
|
<form phx-change="search" class="flex-1 max-w-sm">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-magnifying-glass"
|
name="hero-magnifying-glass"
|
||||||
class="absolute left-2.5 top-2 h-4 w-4 text-gray-400"
|
class="absolute left-2.5 top-2 h-4 w-4 text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -177,7 +179,7 @@
|
||||||
value={@search_query}
|
value={@search_query}
|
||||||
placeholder={t("Filter by name or IP...")}
|
placeholder={t("Filter by name or IP...")}
|
||||||
phx-debounce="150"
|
phx-debounce="150"
|
||||||
class="block w-full rounded-md border-0 py-1.5 pl-9 pr-3 text-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-white/10 placeholder:text-gray-400 focus:ring-2 focus:ring-blue-500 font-mono"
|
class="block w-full rounded-md border-0 py-1.5 pl-9 pr-3 text-sm bg-cool-steel-50 dark:bg-cool-steel-800 text-cool-steel-900 dark:text-white ring-1 ring-inset ring-cool-steel-300 dark:ring-white/10 placeholder:text-cool-steel-400 focus:ring-2 focus:ring-cerulean-500 font-mono"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
/>
|
/>
|
||||||
|
|
@ -186,7 +188,9 @@
|
||||||
|
|
||||||
<%!-- Status summary + filter buttons --%>
|
<%!-- Status summary + filter buttons --%>
|
||||||
<div class="flex items-center gap-2 text-xs font-medium">
|
<div class="flex items-center gap-2 text-xs font-medium">
|
||||||
<span class="text-gray-500 dark:text-gray-400 font-mono">{@total_devices}</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 font-mono">
|
||||||
|
{@total_devices}
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="filter_status"
|
phx-click="filter_status"
|
||||||
|
|
@ -210,12 +214,12 @@
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1 px-2 py-1 rounded-md transition-colors",
|
"inline-flex items-center gap-1 px-2 py-1 rounded-md transition-colors",
|
||||||
@status_filter == "down" &&
|
@status_filter == "down" &&
|
||||||
"bg-red-100 dark:bg-red-900/40 text-red-800 dark:text-red-300 ring-1 ring-red-300 dark:ring-red-700",
|
"bg-sweet-salmon-100 dark:bg-sweet-salmon-900/40 text-sweet-salmon-800 dark:text-sweet-salmon-300 ring-1 ring-sweet-salmon-300 dark:ring-sweet-salmon-700",
|
||||||
@status_filter != "down" &&
|
@status_filter != "down" &&
|
||||||
"text-red-700 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20"
|
"text-sweet-salmon-700 dark:text-sweet-salmon-400 hover:bg-sweet-salmon-50 dark:hover:bg-sweet-salmon-900/20"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-2 w-2 rounded-full bg-red-500"></span>
|
<span class="inline-block h-2 w-2 rounded-full bg-sweet-salmon-500"></span>
|
||||||
{@total_down}
|
{@total_down}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -226,12 +230,12 @@
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center gap-1 px-2 py-1 rounded-md transition-colors",
|
"inline-flex items-center gap-1 px-2 py-1 rounded-md transition-colors",
|
||||||
@status_filter == "unknown" &&
|
@status_filter == "unknown" &&
|
||||||
"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 ring-1 ring-gray-400 dark:ring-gray-600",
|
"bg-cool-steel-200 dark:bg-cool-steel-700 text-cool-steel-800 dark:text-cool-steel-200 ring-1 ring-cool-steel-400 dark:ring-cool-steel-600",
|
||||||
@status_filter != "unknown" &&
|
@status_filter != "unknown" &&
|
||||||
"text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
|
"text-cool-steel-500 dark:text-cool-steel-400 hover:bg-cool-steel-100 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<span class="inline-block h-2 w-2 rounded-full bg-gray-400"></span>
|
<span class="inline-block h-2 w-2 rounded-full bg-cool-steel-400"></span>
|
||||||
{@total_unknown}
|
{@total_unknown}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -239,7 +243,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="filter_status"
|
phx-click="filter_status"
|
||||||
phx-value-status="all"
|
phx-value-status="all"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 ml-1"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300 ml-1"
|
||||||
title={t("Clear filter")}
|
title={t("Clear filter")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-4 w-4" />
|
<.icon name="hero-x-mark" class="h-4 w-4" />
|
||||||
|
|
@ -258,7 +262,7 @@
|
||||||
id="device-list"
|
id="device-list"
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
>
|
>
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th :if={@reorder_mode} scope="col" class="py-2 pl-4 pr-2 w-8 sm:pl-3">
|
<th :if={@reorder_mode} scope="col" class="py-2 pl-4 pr-2 w-8 sm:pl-3">
|
||||||
<span class="sr-only">Drag</span>
|
<span class="sr-only">Drag</span>
|
||||||
|
|
@ -266,7 +270,7 @@
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class={[
|
class={[
|
||||||
"py-2 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400",
|
"py-2 pr-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400",
|
||||||
@reorder_mode && "pl-2",
|
@reorder_mode && "pl-2",
|
||||||
!@reorder_mode && "pl-4 sm:pl-3"
|
!@reorder_mode && "pl-4 sm:pl-3"
|
||||||
]}
|
]}
|
||||||
|
|
@ -275,31 +279,31 @@
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden sm:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="hidden sm:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden md:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="hidden md:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Last Seen")}
|
{t("Last Seen")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden lg:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="hidden lg:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Response")}
|
{t("Response")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden lg:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="hidden lg:table-cell px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Subs")}
|
{t("Subs")}
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -307,20 +311,20 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
id="device-rows"
|
id="device-rows"
|
||||||
class="divide-y divide-gray-100 dark:divide-white/5"
|
class="divide-y divide-cool-steel-100 dark:divide-white/5"
|
||||||
>
|
>
|
||||||
<%= for row <- @device_rows do %>
|
<%= for row <- @device_rows do %>
|
||||||
<%= if row.type == :site_header do %>
|
<%= if row.type == :site_header do %>
|
||||||
<tr
|
<tr
|
||||||
id={row.id}
|
id={row.id}
|
||||||
class="bg-gray-50/80 dark:bg-gray-800/30"
|
class="bg-cool-steel-50/80 dark:bg-cool-steel-800/30"
|
||||||
data-site-id={if row.site, do: row.site.id, else: "no-site"}
|
data-site-id={if row.site, do: row.site.id, else: "no-site"}
|
||||||
draggable={if @reorder_mode && row.site, do: "true", else: "false"}
|
draggable={if @reorder_mode && row.site, do: "true", else: "false"}
|
||||||
>
|
>
|
||||||
<td :if={@reorder_mode && row.site} class="py-1.5 pl-4 pr-2 sm:pl-3">
|
<td :if={@reorder_mode && row.site} class="py-1.5 pl-4 pr-2 sm:pl-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="drag-handle cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="drag-handle cursor-grab active:cursor-grabbing text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
title="Drag to reorder site"
|
title="Drag to reorder site"
|
||||||
>
|
>
|
||||||
<.icon name="hero-bars-3" class="h-4 w-4" />
|
<.icon name="hero-bars-3" class="h-4 w-4" />
|
||||||
|
|
@ -336,17 +340,17 @@
|
||||||
<%= if row.site do %>
|
<%= if row.site do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{row.site.id}"}
|
navigate={~p"/sites/#{row.site.id}"}
|
||||||
class="font-semibold text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400"
|
class="font-semibold text-cool-steel-900 dark:text-white hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{row.site.name}
|
{row.site.name}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="font-semibold text-gray-700 dark:text-gray-300">
|
<span class="font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{if @sites_enabled, do: "Unassigned", else: "All Devices"}
|
{if @sites_enabled, do: "Unassigned", else: "All Devices"}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="text-gray-400 dark:text-gray-600">·</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-600">·</span>
|
||||||
<span class="text-gray-500 dark:text-gray-400 font-mono">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400 font-mono">
|
||||||
{row.stats.total}
|
{row.stats.total}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -359,14 +363,14 @@
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
:if={row.stats.down > 0}
|
:if={row.stats.down > 0}
|
||||||
class="text-red-600 dark:text-red-400 font-medium"
|
class="text-sweet-salmon-600 dark:text-sweet-salmon-400 font-medium"
|
||||||
>
|
>
|
||||||
<span class="inline-block h-1.5 w-1.5 rounded-full bg-red-500">
|
<span class="inline-block h-1.5 w-1.5 rounded-full bg-sweet-salmon-500">
|
||||||
</span>
|
</span>
|
||||||
{row.stats.down}
|
{row.stats.down}
|
||||||
</span>
|
</span>
|
||||||
<%= if row.site && @site_subscribers[row.site.id] do %>
|
<%= if row.site && @site_subscribers[row.site.id] do %>
|
||||||
<span class="text-amber-600 dark:text-amber-400">
|
<span class="text-wheat-600 dark:text-wheat-400">
|
||||||
<.icon name="hero-users" class="inline h-3 w-3" />
|
<.icon name="hero-users" class="inline h-3 w-3" />
|
||||||
{@site_subscribers[row.site.id].account_count}
|
{@site_subscribers[row.site.id].account_count}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -374,7 +378,7 @@
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
:if={row.site && row.site.location}
|
:if={row.site && row.site.location}
|
||||||
class="text-xs text-gray-400 dark:text-gray-500 dark:text-gray-400"
|
class="text-xs text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-map-pin" class="inline h-3 w-3" /> {row.site.location}
|
<.icon name="hero-map-pin" class="inline h-3 w-3" /> {row.site.location}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -385,8 +389,9 @@
|
||||||
<tr
|
<tr
|
||||||
id={row.id}
|
id={row.id}
|
||||||
class={[
|
class={[
|
||||||
"device-row hover:bg-blue-50/50 dark:hover:bg-blue-900/10 transition-colors cursor-pointer",
|
"device-row hover:bg-cerulean-50/50 dark:hover:bg-cerulean-900/10 transition-colors cursor-pointer",
|
||||||
rem(row.device_index, 2) == 1 && "bg-gray-50/40 dark:bg-gray-800/20"
|
rem(row.device_index, 2) == 1 &&
|
||||||
|
"bg-cool-steel-50/40 dark:bg-cool-steel-800/20"
|
||||||
]}
|
]}
|
||||||
title={device_row_title(row.device)}
|
title={device_row_title(row.device)}
|
||||||
data-device-id={row.device.id}
|
data-device-id={row.device.id}
|
||||||
|
|
@ -403,13 +408,13 @@
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="drag-handle cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="drag-handle cursor-grab active:cursor-grabbing text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
title="Drag to reorder device"
|
title="Drag to reorder device"
|
||||||
>
|
>
|
||||||
<.icon name="hero-bars-3" class="h-4 w-4" />
|
<.icon name="hero-bars-3" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-0 text-sm font-medium whitespace-nowrap text-gray-900 dark:text-white">
|
<td class="p-0 text-sm font-medium whitespace-nowrap text-cool-steel-900 dark:text-white">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{row.device.id}"}
|
navigate={~p"/devices/#{row.device.id}"}
|
||||||
class={[
|
class={[
|
||||||
|
|
@ -430,7 +435,7 @@
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{row.device.id}"}
|
navigate={~p"/devices/#{row.device.id}"}
|
||||||
class={[
|
class={[
|
||||||
"block px-3 text-inherit no-underline font-mono text-xs text-gray-600 dark:text-gray-300",
|
"block px-3 text-inherit no-underline font-mono text-xs text-cool-steel-600 dark:text-cool-steel-300",
|
||||||
"py-2"
|
"py-2"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -449,15 +454,16 @@
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-block h-2 w-2 rounded-full",
|
"inline-block h-2 w-2 rounded-full",
|
||||||
row.device.status == :up && "bg-green-500",
|
row.device.status == :up && "bg-green-500",
|
||||||
row.device.status == :down && "bg-red-500",
|
row.device.status == :down && "bg-sweet-salmon-500",
|
||||||
row.device.status == :unknown && "bg-gray-400"
|
row.device.status == :unknown && "bg-cool-steel-400"
|
||||||
]} />
|
]} />
|
||||||
<span class={[
|
<span class={[
|
||||||
"text-xs font-mono font-medium",
|
"text-xs font-mono font-medium",
|
||||||
row.device.status == :up && "text-green-700 dark:text-green-400",
|
row.device.status == :up && "text-green-700 dark:text-green-400",
|
||||||
row.device.status == :down && "text-red-700 dark:text-red-400",
|
row.device.status == :down &&
|
||||||
|
"text-sweet-salmon-700 dark:text-sweet-salmon-400",
|
||||||
row.device.status == :unknown &&
|
row.device.status == :unknown &&
|
||||||
"text-gray-500 dark:text-gray-400"
|
"text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
]}>
|
]}>
|
||||||
{row.device.status |> to_string() |> String.upcase()}
|
{row.device.status |> to_string() |> String.upcase()}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -490,7 +496,7 @@
|
||||||
<% bar_w = response_time_bar_width(row.response) %>
|
<% bar_w = response_time_bar_width(row.response) %>
|
||||||
<div
|
<div
|
||||||
:if={bar_w > 0}
|
:if={bar_w > 0}
|
||||||
class="w-16 h-1.5 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden"
|
class="w-16 h-1.5 bg-cool-steel-100 dark:bg-cool-steel-700 rounded-full overflow-hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class={"h-full rounded-full #{response_time_bar_color(row.response)}"}
|
class={"h-full rounded-full #{response_time_bar_color(row.response)}"}
|
||||||
|
|
@ -509,12 +515,14 @@
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<%= if sub_text = format_subscriber_count(row.subscribers) do %>
|
<%= if sub_text = format_subscriber_count(row.subscribers) do %>
|
||||||
<span class="inline-flex items-center gap-1 text-xs text-blue-700 dark:text-blue-300">
|
<span class="inline-flex items-center gap-1 text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
<.icon name="hero-users" class="h-3 w-3" />
|
<.icon name="hero-users" class="h-3 w-3" />
|
||||||
{sub_text}
|
{sub_text}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-xs text-gray-300 dark:text-gray-600">—</span>
|
<span class="text-xs text-cool-steel-300 dark:text-cool-steel-600">
|
||||||
|
—
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -532,17 +540,17 @@
|
||||||
<div class="text-center py-16">
|
<div class="text-center py-16">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-magnifying-glass"
|
name="hero-magnifying-glass"
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500 dark:text-gray-400"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No discovered devices")}
|
{t("No discovered devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Devices will appear here as they are discovered via LLDP/CDP.")}
|
{t("Devices will appear here as they are discovered via LLDP/CDP.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
<div class="mb-4 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Showing {(@pagination.page - 1) * @pagination.per_page + 1}-{min(
|
Showing {(@pagination.page - 1) * @pagination.per_page + 1}-{min(
|
||||||
@pagination.page * @pagination.per_page,
|
@pagination.page * @pagination.per_page,
|
||||||
@pagination.total_count
|
@pagination.total_count
|
||||||
|
|
@ -557,16 +565,16 @@
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
||||||
MAC
|
MAC
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2 font-mono text-sm text-gray-900 dark:text-white">
|
<span class="ml-2 font-mono text-sm text-cool-steel-900 dark:text-white">
|
||||||
{discovered.identifier.value}
|
{discovered.identifier.value}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% :ip -> %>
|
<% :ip -> %>
|
||||||
<div>
|
<div>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900 dark:text-cerulean-200">
|
||||||
IP
|
IP
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2 font-mono text-sm text-gray-900 dark:text-white">
|
<span class="ml-2 font-mono text-sm text-cool-steel-900 dark:text-white">
|
||||||
{discovered.identifier.value}
|
{discovered.identifier.value}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -575,52 +583,56 @@
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||||
HOST
|
HOST
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2 text-sm text-gray-900 dark:text-white">
|
<span class="ml-2 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{discovered.identifier.value}
|
{discovered.identifier.value}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% _ -> %>
|
<% _ -> %>
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">Unknown</span>
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">Unknown</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={discovered} label={t("Hostname")}>
|
<:col :let={discovered} label={t("Hostname")}>
|
||||||
<%= if discovered.hostname do %>
|
<%= if discovered.hostname do %>
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{discovered.hostname}
|
{discovered.hostname}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-sm text-gray-400">-</span>
|
<span class="text-sm text-cool-steel-400">-</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={discovered} label={t("Type")}>
|
<:col :let={discovered} label={t("Type")}>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-server"
|
name="hero-server"
|
||||||
class="h-4 w-4 text-gray-500 dark:text-gray-400"
|
class="h-4 w-4 text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-gray-900 dark:text-white">
|
<span class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{device_type_label(discovered.device_type)}
|
{device_type_label(discovered.device_type)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={discovered} label={t("Manufacturer")}>
|
<:col :let={discovered} label={t("Manufacturer")}>
|
||||||
<%= if discovered.manufacturer do %>
|
<%= if discovered.manufacturer do %>
|
||||||
<span class="text-sm text-gray-900 dark:text-white">{discovered.manufacturer}</span>
|
<span class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{discovered.manufacturer}
|
||||||
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-sm text-gray-400">-</span>
|
<span class="text-sm text-cool-steel-400">-</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={discovered} label={t("Discovered By")}>
|
<:col :let={discovered} label={t("Discovered By")}>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if length(discovered.discovered_by) == 1 do %>
|
<%= if length(discovered.discovered_by) == 1 do %>
|
||||||
<% [first] = discovered.discovered_by %>
|
<% [first] = discovered.discovered_by %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{first.device_id}"}
|
navigate={~p"/devices/#{first.device_id}"}
|
||||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{first.device_name}
|
{first.device_name}
|
||||||
</.link>
|
</.link>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400"> ({first.interface})</span>
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
({first.interface})
|
||||||
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span>{length(discovered.discovered_by)} devices</span>
|
<span>{length(discovered.discovered_by)} devices</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -629,7 +641,7 @@
|
||||||
<:col :let={discovered} label={t("Last Seen")}>
|
<:col :let={discovered} label={t("Last Seen")}>
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={discovered.last_seen}
|
datetime={discovered.last_seen}
|
||||||
class="text-sm text-gray-600 dark:text-gray-400"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={discovered} label={t("Actions")}>
|
<:col :let={discovered} label={t("Actions")}>
|
||||||
|
|
@ -644,25 +656,25 @@
|
||||||
</.table>
|
</.table>
|
||||||
|
|
||||||
<%= if @pagination.total_pages > 1 do %>
|
<%= if @pagination.total_pages > 1 do %>
|
||||||
<div class="mt-6 flex items-center justify-between border-t border-gray-200 dark:border-white/10 pt-6">
|
<div class="mt-6 flex items-center justify-between border-t border-cool-steel-200 dark:border-white/10 pt-6">
|
||||||
<div class="flex flex-1 justify-between sm:hidden">
|
<div class="flex flex-1 justify-between sm:hidden">
|
||||||
<.link
|
<.link
|
||||||
:if={@pagination.page > 1}
|
:if={@pagination.page > 1}
|
||||||
patch={~p"/devices?tab=discovered&page=#{@pagination.page - 1}"}
|
patch={~p"/devices?tab=discovered&page=#{@pagination.page - 1}"}
|
||||||
class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
class="relative inline-flex items-center rounded-md border border-cool-steel-300 bg-white px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
{t("Previous")}
|
{t("Previous")}
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
:if={@pagination.page < @pagination.total_pages}
|
:if={@pagination.page < @pagination.total_pages}
|
||||||
patch={~p"/devices?tab=discovered&page=#{@pagination.page + 1}"}
|
patch={~p"/devices?tab=discovered&page=#{@pagination.page + 1}"}
|
||||||
class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
class="relative ml-3 inline-flex items-center rounded-md border border-cool-steel-300 bg-white px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
{t("Next")}
|
{t("Next")}
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
|
||||||
<p class="text-sm text-gray-700 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-700 dark:text-cool-steel-400">
|
||||||
<span class="font-medium">{(@pagination.page - 1) * @pagination.per_page + 1}</span>–<span class="font-medium">{min(@pagination.page * @pagination.per_page, @pagination.total_count)}</span> of
|
<span class="font-medium">{(@pagination.page - 1) * @pagination.per_page + 1}</span>–<span class="font-medium">{min(@pagination.page * @pagination.per_page, @pagination.total_count)}</span> of
|
||||||
<span class="font-medium">{@pagination.total_count}</span>
|
<span class="font-medium">{@pagination.total_count}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -673,24 +685,24 @@
|
||||||
<.link
|
<.link
|
||||||
:if={@pagination.page > 1}
|
:if={@pagination.page > 1}
|
||||||
patch={~p"/devices?tab=discovered&page=#{@pagination.page - 1}"}
|
patch={~p"/devices?tab=discovered&page=#{@pagination.page - 1}"}
|
||||||
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:ring-white/10 dark:hover:bg-gray-800"
|
class="relative inline-flex items-center rounded-l-md px-2 py-2 text-cool-steel-400 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:ring-white/10 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-left" class="h-5 w-5" />
|
<.icon name="hero-chevron-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<%= for page_num <- pagination_range(@pagination.page, @pagination.total_pages) do %>
|
<%= for page_num <- pagination_range(@pagination.page, @pagination.total_pages) do %>
|
||||||
<%= if page_num == :ellipsis do %>
|
<%= if page_num == :ellipsis do %>
|
||||||
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300 dark:text-gray-400 dark:ring-white/10">
|
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-cool-steel-700 ring-1 ring-inset ring-cool-steel-300 dark:text-cool-steel-400 dark:ring-white/10">
|
||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/devices?tab=discovered&page=#{page_num}"}
|
patch={~p"/devices?tab=discovered&page=#{page_num}"}
|
||||||
class={[
|
class={[
|
||||||
"relative inline-flex items-center px-4 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 dark:ring-white/10",
|
"relative inline-flex items-center px-4 py-2 text-sm font-semibold ring-1 ring-inset ring-cool-steel-300 dark:ring-white/10",
|
||||||
if @pagination.page == page_num do
|
if @pagination.page == page_num do
|
||||||
"z-10 bg-blue-600 text-white"
|
"z-10 bg-cerulean-600 text-white"
|
||||||
else
|
else
|
||||||
"text-gray-900 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"text-cool-steel-900 hover:bg-cool-steel-50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -701,7 +713,7 @@
|
||||||
<.link
|
<.link
|
||||||
:if={@pagination.page < @pagination.total_pages}
|
:if={@pagination.page < @pagination.total_pages}
|
||||||
patch={~p"/devices?tab=discovered&page=#{@pagination.page + 1}"}
|
patch={~p"/devices?tab=discovered&page=#{@pagination.page + 1}"}
|
||||||
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:ring-white/10 dark:hover:bg-gray-800"
|
class="relative inline-flex items-center rounded-r-md px-2 py-2 text-cool-steel-400 ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:ring-white/10 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-right" class="h-5 w-5" />
|
<.icon name="hero-chevron-right" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -632,8 +632,8 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
|
|
||||||
# Additional template-used formatting functions (not yet in Formatters module)
|
# Additional template-used formatting functions (not yet in Formatters module)
|
||||||
@doc false
|
@doc false
|
||||||
def utilization_text_color(pct) when pct >= 90, do: "text-red-600 dark:text-red-400"
|
def utilization_text_color(pct) when pct >= 90, do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def utilization_text_color(pct) when pct >= 70, do: "text-yellow-600 dark:text-yellow-400"
|
def utilization_text_color(pct) when pct >= 70, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|
@ -1023,7 +1023,7 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400">
|
||||||
WARNING
|
WARNING
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1033,7 +1033,7 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400">
|
||||||
CRITICAL
|
CRITICAL
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1043,7 +1043,7 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-900/30 dark:text-cool-steel-400">
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1053,7 +1053,7 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-900/30 dark:text-cool-steel-400">
|
||||||
PENDING
|
PENDING
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
|
|
@ -1115,8 +1115,8 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
||||||
|
|
||||||
defp config_change_dot_color(event) do
|
defp config_change_dot_color(event) do
|
||||||
cond do
|
cond do
|
||||||
event.change_size > 50 -> "bg-red-500"
|
event.change_size > 50 -> "bg-sweet-salmon-500"
|
||||||
event.change_size > 20 -> "bg-yellow-500"
|
event.change_size > 20 -> "bg-wheat-500"
|
||||||
true -> "bg-green-500"
|
true -> "bg-green-500"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -7,11 +7,11 @@
|
||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-3 mb-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules?tab=escalation-policies"}
|
navigate={~p"/schedules?tab=escalation-policies"}
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{@page_title}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">{@page_title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
|
|
@ -34,13 +34,13 @@
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 p-4 flex items-center gap-3">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 flex items-center gap-3">
|
||||||
<.icon name="hero-arrow-path" class="h-5 w-5 text-gray-500" />
|
<.icon name="hero-arrow-path" class="h-5 w-5 text-cool-steel-500" />
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
<span class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("If no one acknowledges, repeat this policy")}
|
{t("If no one acknowledges, repeat this policy")}
|
||||||
</span>
|
</span>
|
||||||
<.input field={@form[:repeat_count]} type="number" min="1" max="10" class="w-20" />
|
<.input field={@form[:repeat_count]} type="number" min="1" max="10" class="w-20" />
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("time(s).")}</span>
|
<span class="text-sm text-cool-steel-700 dark:text-cool-steel-300">{t("time(s).")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<.button type="submit" phx-disable-with={t("Saving...")}>
|
<.button type="submit" phx-disable-with={t("Saving...")}>
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
</.button>
|
</.button>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules?tab=escalation-policies"}
|
navigate={~p"/schedules?tab=escalation-policies"}
|
||||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,17 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules"}
|
navigate={~p"/schedules"}
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Escalation Policies")}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("Escalation Policies")}
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/escalation-policies/new"}
|
navigate={~p"/schedules/escalation-policies/new"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("New Policy")}
|
{t("New Policy")}
|
||||||
|
|
@ -28,39 +30,39 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-trending-up"
|
name="hero-arrow-trending-up"
|
||||||
class="h-12 w-12 text-gray-300 dark:text-gray-600 mx-auto mb-4"
|
class="h-12 w-12 text-cool-steel-300 dark:text-cool-steel-600 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300">
|
<h3 class="text-lg font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("No escalation policies")}
|
{t("No escalation policies")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Create an escalation policy to define how alerts are escalated to on-call users.")}
|
{t("Create an escalation policy to define how alerts are escalated to on-call users.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Name")}
|
{t("Name")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Levels")}
|
{t("Levels")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-900">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-900">
|
||||||
<%= for policy <- @policies do %>
|
<%= for policy <- @policies do %>
|
||||||
<tr
|
<tr
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors cursor-pointer"
|
class="hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/30 transition-colors cursor-pointer"
|
||||||
phx-click={JS.navigate(~p"/schedules/escalation-policies/#{policy.id}")}
|
phx-click={JS.navigate(~p"/schedules/escalation-policies/#{policy.id}")}
|
||||||
>
|
>
|
||||||
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white">
|
<td class="px-4 py-3 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{policy.name}
|
{policy.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{length(policy.rules)}
|
{length(policy.rules)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,16 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules?tab=escalation-policies"}
|
navigate={~p"/schedules?tab=escalation-policies"}
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{@policy.name}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">{@policy.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/escalation-policies/#{@policy.id}/edit"}
|
navigate={~p"/schedules/escalation-policies/#{@policy.id}/edit"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 border border-cool-steel-300 dark:border-cool-steel-600 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-pencil" class="h-4 w-4" />
|
<.icon name="hero-pencil" class="h-4 w-4" />
|
||||||
{t("Edit")}
|
{t("Edit")}
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<button
|
<button
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
data-confirm={t("Are you sure you want to delete this escalation policy?")}
|
data-confirm={t("Are you sure you want to delete this escalation policy?")}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-gray-300 dark:border-gray-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm font-medium text-sweet-salmon-600 dark:text-sweet-salmon-400 border border-cool-steel-300 dark:border-cool-steel-600 hover:bg-sweet-salmon-50 dark:hover:bg-sweet-salmon-900/20 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
{t("Delete")}
|
{t("Delete")}
|
||||||
|
|
@ -33,23 +33,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-6">
|
||||||
{t("Send On-Call Handoff Notifications:")}
|
{t("Send On-Call Handoff Notifications:")}
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{handoff_mode_label(@policy.handoff_notifications_mode)}
|
{handoff_mode_label(@policy.handoff_notifications_mode)}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= if @policy.description do %>
|
<%= if @policy.description do %>
|
||||||
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">{@policy.description}</p>
|
<p class="mb-6 text-sm text-cool-steel-600 dark:text-cool-steel-400">{@policy.description}</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<%!-- LEFT: Levels timeline --%>
|
<%!-- LEFT: Levels timeline --%>
|
||||||
<div class="lg:col-span-2 space-y-3">
|
<div class="lg:col-span-2 space-y-3">
|
||||||
<%= if Enum.empty?(@policy.rules) do %>
|
<%= if Enum.empty?(@policy.rules) do %>
|
||||||
<div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-6 text-center">
|
<div class="rounded-lg border border-dashed border-cool-steel-300 dark:border-cool-steel-600 p-6 text-center">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No levels configured. Add a level to define escalation steps.")}
|
{t("No levels configured. Add a level to define escalation steps.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,12 +66,12 @@
|
||||||
<div
|
<div
|
||||||
data-id={rule.id}
|
data-id={rule.id}
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4 cursor-move"
|
class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4 cursor-move"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-bars-3" class="h-4 w-4 text-gray-400" />
|
<.icon name="hero-bars-3" class="h-4 w-4 text-cool-steel-400" />
|
||||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-base font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Level %{n}", n: idx + 1)}
|
{t("Level %{n}", n: idx + 1)}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
phx-click="move_rule"
|
phx-click="move_rule"
|
||||||
phx-value-id={rule.id}
|
phx-value-id={rule.id}
|
||||||
phx-value-direction="up"
|
phx-value-direction="up"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300 disabled:opacity-30 disabled:hover:text-cool-steel-400"
|
||||||
disabled={idx == 0}
|
disabled={idx == 0}
|
||||||
title={t("Move up")}
|
title={t("Move up")}
|
||||||
>
|
>
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
phx-click="move_rule"
|
phx-click="move_rule"
|
||||||
phx-value-id={rule.id}
|
phx-value-id={rule.id}
|
||||||
phx-value-direction="down"
|
phx-value-direction="down"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300 disabled:opacity-30 disabled:hover:text-cool-steel-400"
|
||||||
disabled={idx == rule_count - 1}
|
disabled={idx == rule_count - 1}
|
||||||
title={t("Move down")}
|
title={t("Move down")}
|
||||||
>
|
>
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
phx-click="delete_rule"
|
phx-click="delete_rule"
|
||||||
phx-value-id={rule.id}
|
phx-value-id={rule.id}
|
||||||
data-confirm={t("Delete this level?")}
|
data-confirm={t("Delete this level?")}
|
||||||
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors ml-1"
|
class="text-cool-steel-400 hover:text-sweet-salmon-500 dark:hover:text-sweet-salmon-400 transition-colors ml-1"
|
||||||
title={t("Delete")}
|
title={t("Delete")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
|
|
@ -108,9 +108,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-3">
|
||||||
{t("Notify the following users immediately and escalate after")}
|
{t("Notify the following users immediately and escalate after")}
|
||||||
<span class="font-semibold text-gray-900 dark:text-white">
|
<span class="font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{rule.timeout_minutes}
|
{rule.timeout_minutes}
|
||||||
</span>
|
</span>
|
||||||
{t("minutes.")}
|
{t("minutes.")}
|
||||||
|
|
@ -119,13 +119,13 @@
|
||||||
<%!-- Targets --%>
|
<%!-- Targets --%>
|
||||||
<div>
|
<div>
|
||||||
<%= if Enum.empty?(rule.targets) do %>
|
<%= if Enum.empty?(rule.targets) do %>
|
||||||
<p class="text-xs text-gray-400 dark:text-gray-500 mb-2">
|
<p class="text-xs text-cool-steel-400 dark:text-cool-steel-500 mb-2">
|
||||||
{t("No targets yet")}
|
{t("No targets yet")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="flex flex-wrap gap-2 mb-2">
|
<div class="flex flex-wrap gap-2 mb-2">
|
||||||
<%= for target <- rule.targets do %>
|
<%= for target <- rule.targets do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-800 pl-2.5 pr-1 py-0.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
<span class="inline-flex items-center gap-1 rounded-full bg-cool-steel-100 dark:bg-cool-steel-800 pl-2.5 pr-1 py-0.5 text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<%= case target.target_type do %>
|
<%= case target.target_type do %>
|
||||||
<% "user" -> %>
|
<% "user" -> %>
|
||||||
<.icon name="hero-user" class="h-3 w-3 mr-0.5" />
|
<.icon name="hero-user" class="h-3 w-3 mr-0.5" />
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
<button
|
<button
|
||||||
phx-click="delete_target"
|
phx-click="delete_target"
|
||||||
phx-value-id={target.id}
|
phx-value-id={target.id}
|
||||||
class="ml-0.5 rounded-full p-0.5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
class="ml-0.5 rounded-full p-0.5 hover:bg-cool-steel-200 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-3 w-3" />
|
<.icon name="hero-x-mark" class="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -154,14 +154,14 @@
|
||||||
<select
|
<select
|
||||||
name="target_type"
|
name="target_type"
|
||||||
id={"target-type-#{rule.id}"}
|
id={"target-type-#{rule.id}"}
|
||||||
class="block rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1.5"
|
class="block rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-xs py-1.5"
|
||||||
>
|
>
|
||||||
<option value="user">{t("User")}</option>
|
<option value="user">{t("User")}</option>
|
||||||
<option value="schedule">{t("Schedule")}</option>
|
<option value="schedule">{t("Schedule")}</option>
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
name="target_id"
|
name="target_id"
|
||||||
class="block w-full max-w-xs rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1.5"
|
class="block w-full max-w-xs rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-xs py-1.5"
|
||||||
>
|
>
|
||||||
<option value="">{t("Select...")}</option>
|
<option value="">{t("Select...")}</option>
|
||||||
<optgroup label={t("Users")}>
|
<optgroup label={t("Users")}>
|
||||||
|
|
@ -177,7 +177,7 @@
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="inline-flex items-center rounded-md bg-gray-100 dark:bg-gray-800 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
class="inline-flex items-center rounded-md bg-cool-steel-100 dark:bg-cool-steel-800 px-2.5 py-1.5 text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 hover:bg-cool-steel-200 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-3 w-3 mr-1" />
|
<.icon name="hero-plus" class="h-3 w-3 mr-1" />
|
||||||
{t("Add")}
|
{t("Add")}
|
||||||
|
|
@ -191,11 +191,11 @@
|
||||||
|
|
||||||
<%!-- Add Level button + form --%>
|
<%!-- Add Level button + form --%>
|
||||||
<%= if @show_add_rule do %>
|
<%= if @show_add_rule do %>
|
||||||
<div class="rounded-lg border border-blue-200 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-900/10 p-4">
|
<div class="rounded-lg border border-cerulean-200 dark:border-cerulean-800 bg-cerulean-50/50 dark:bg-cerulean-900/10 p-4">
|
||||||
<form phx-submit="save_rule">
|
<form phx-submit="save_rule">
|
||||||
<div class="flex items-end gap-4 mb-4">
|
<div class="flex items-end gap-4 mb-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Escalate after (minutes)")}
|
{t("Escalate after (minutes)")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
value="30"
|
value="30"
|
||||||
min="1"
|
min="1"
|
||||||
max="1440"
|
max="1440"
|
||||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm"
|
class="block w-full rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_add_rule"
|
phx-click="toggle_add_rule"
|
||||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -225,16 +225,16 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<button
|
<button
|
||||||
phx-click="toggle_add_rule"
|
phx-click="toggle_add_rule"
|
||||||
class="w-full rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-3 text-sm text-gray-500 dark:text-gray-400 hover:border-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
|
class="w-full rounded-lg border border-dashed border-cool-steel-300 dark:border-cool-steel-600 p-3 text-sm text-cool-steel-500 dark:text-cool-steel-400 hover:border-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4 inline mr-1" /> {t("Add Level")}
|
<.icon name="hero-plus" class="h-4 w-4 inline mr-1" /> {t("Add Level")}
|
||||||
</button>
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%!-- REPEAT footer --%>
|
<%!-- REPEAT footer --%>
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-gray-800/40 p-4 flex items-center gap-3">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-cool-steel-50 dark:bg-cool-steel-800/40 p-4 flex items-center gap-3">
|
||||||
<.icon name="hero-arrow-path" class="h-5 w-5 text-gray-500" />
|
<.icon name="hero-arrow-path" class="h-5 w-5 text-cool-steel-500" />
|
||||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
<p class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("If no one acknowledges, repeat this policy")}
|
{t("If no one acknowledges, repeat this policy")}
|
||||||
<span class="font-semibold">{@policy.repeat_count}</span>
|
<span class="font-semibold">{@policy.repeat_count}</span>
|
||||||
{t("time(s).")}
|
{t("time(s).")}
|
||||||
|
|
@ -244,12 +244,12 @@
|
||||||
|
|
||||||
<%!-- RIGHT: Services sidebar --%>
|
<%!-- RIGHT: Services sidebar --%>
|
||||||
<aside class="lg:col-span-1">
|
<aside class="lg:col-span-1">
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white mb-3">
|
||||||
{t("Services (%{count})", count: length(@services))}
|
{t("Services (%{count})", count: length(@services))}
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @services == [] do %>
|
<%= if @services == [] do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No devices use this policy yet.")}
|
{t("No devices use this policy yet.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -258,7 +258,7 @@
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{device.id}"}
|
navigate={~p"/devices/#{device.id}"}
|
||||||
class="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-sm text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
{device.name}
|
{device.name}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,21 @@
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@device_id}"}
|
navigate={~p"/devices/#{@device_id}"}
|
||||||
class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{@chart_title}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{@chart_title}</h1>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">{@device.name}</p>
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">{@device.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Date Range Selector -->
|
<!-- Date Range Selector -->
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Time Range:</span>
|
<span class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
|
Time Range:
|
||||||
|
</span>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<%= for {label, value} <- [{"Live", "live"}, {"1 Hour", "1h"}, {"6 Hours", "6h"}, {"12 Hours", "12h"}, {"24 Hours", "24h"}, {"7 Days", "7d"}, {"30 Days", "30d"}] do %>
|
<%= for {label, value} <- [{"Live", "live"}, {"1 Hour", "1h"}, {"6 Hours", "6h"}, {"12 Hours", "12h"}, {"24 Hours", "24h"}, {"7 Days", "7d"}, {"30 Days", "30d"}] do %>
|
||||||
<button
|
<button
|
||||||
|
|
@ -38,9 +40,9 @@
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if @range == value do
|
if @range == value do
|
||||||
"bg-blue-500 text-white border-blue-500"
|
"bg-cerulean-500 text-white border-cerulean-500"
|
||||||
else
|
else
|
||||||
"bg-white dark:bg-gray-800/50 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-white/10 hover:bg-gray-50 dark:hover:bg-gray-800"
|
"bg-white dark:bg-cool-steel-800/50 text-cool-steel-700 dark:text-cool-steel-300 border-cool-steel-300 dark:border-white/10 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
|
|
@ -52,7 +54,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Live Mode Indicator -->
|
<!-- Live Mode Indicator -->
|
||||||
<%= if @is_live_mode do %>
|
<%= if @is_live_mode do %>
|
||||||
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<div class="flex items-center gap-2 text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
<span class="relative flex h-3 w-3">
|
<span class="relative flex h-3 w-3">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -63,7 +65,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<!-- Large Chart -->
|
<!-- Large Chart -->
|
||||||
<%= if @chart_data do %>
|
<%= if @chart_data do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div
|
<div
|
||||||
id="detail-chart"
|
id="detail-chart"
|
||||||
|
|
@ -80,16 +82,16 @@
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
</div>
|
</div>
|
||||||
<%= if @max_value && @min_value do %>
|
<%= if @max_value && @min_value do %>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-white/10 flex justify-center gap-8 text-sm">
|
<div class="mt-4 pt-4 border-t border-cool-steel-200 dark:border-white/10 flex justify-center gap-8 text-sm">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium text-gray-700 dark:text-gray-300">Max:</span>
|
<span class="font-medium text-cool-steel-700 dark:text-cool-steel-300">Max:</span>
|
||||||
<span class="text-gray-900 dark:text-white font-semibold">
|
<span class="text-cool-steel-900 dark:text-white font-semibold">
|
||||||
{format_value(@max_value, @unit)}
|
{format_value(@max_value, @unit)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium text-gray-700 dark:text-gray-300">Min:</span>
|
<span class="font-medium text-cool-steel-700 dark:text-cool-steel-300">Min:</span>
|
||||||
<span class="text-gray-900 dark:text-white font-semibold">
|
<span class="text-cool-steel-900 dark:text-white font-semibold">
|
||||||
{format_value(@min_value, @unit)}
|
{format_value(@min_value, @unit)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -98,13 +100,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon name="hero-chart-bar" class="mx-auto h-12 w-12 text-gray-400" />
|
<.icon name="hero-chart-bar" class="mx-auto h-12 w-12 text-cool-steel-400" />
|
||||||
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No sensor data available")}
|
{t("No sensor data available")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("This device doesn't have any sensors of this type.")}
|
{t("This device doesn't have any sensors of this type.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -182,18 +182,23 @@ defmodule ToweropsWeb.InsightsLive.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def urgency_classes("critical"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
def urgency_classes("critical"),
|
||||||
def urgency_classes("warning"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
def urgency_classes("info"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
|
||||||
def urgency_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
def urgency_classes("warning"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
def urgency_classes("info"), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
def urgency_classes(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def source_classes("preseem"), do: "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-400"
|
def source_classes("preseem"), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
def source_classes("gaiia"), do: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400"
|
def source_classes("gaiia"), do: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||||
def source_classes("snmp"), do: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
def source_classes("snmp"), do: "bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
def source_classes("system"), do: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300"
|
|
||||||
|
def source_classes("system"),
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def source_classes("ai"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
def source_classes("ai"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
def source_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
def source_classes(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Display label for a source. Acronyms render uppercase; everything else
|
Display label for a source. Acronyms render uppercase; everything else
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
active_page="insights"
|
active_page="insights"
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between gap-4 border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="flex items-start justify-between gap-4 border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Network Insights")}
|
{t("Network Insights")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Proactive network health observations from all sources.")}
|
{t("Proactive network health observations from all sources.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
id="regenerate-insights-btn"
|
id="regenerate-insights-btn"
|
||||||
phx-click="regenerate_insights"
|
phx-click="regenerate_insights"
|
||||||
data-confirm={t("Queue insight regeneration jobs for all sources?")}
|
data-confirm={t("Queue insight regeneration jobs for all sources?")}
|
||||||
class="inline-flex items-center gap-1.5 rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="inline-flex items-center gap-1.5 rounded-md bg-cerulean-600 px-3 py-2 text-sm font-medium text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
||||||
{t("Regenerate insights")}
|
{t("Regenerate insights")}
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Filter bar --%>
|
<%!-- Filter bar --%>
|
||||||
<div class="mt-6 flex flex-wrap items-center gap-4 border-b border-gray-200 pb-4 dark:border-white/10">
|
<div class="mt-6 flex flex-wrap items-center gap-4 border-b border-cool-steel-200 pb-4 dark:border-white/10">
|
||||||
<%!-- Status filter tabs --%>
|
<%!-- Status filter tabs --%>
|
||||||
<nav class="flex space-x-4">
|
<nav class="flex space-x-4">
|
||||||
<.link
|
<.link
|
||||||
|
|
@ -40,9 +40,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium",
|
"rounded-md px-3 py-1.5 text-sm font-medium",
|
||||||
if(@filter_status == "active",
|
if(@filter_status == "active",
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
|
do:
|
||||||
|
"bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-300"
|
"text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -56,9 +57,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium",
|
"rounded-md px-3 py-1.5 text-sm font-medium",
|
||||||
if(@filter_status == "dismissed",
|
if(@filter_status == "dismissed",
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
|
do:
|
||||||
|
"bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-300"
|
"text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -67,8 +69,10 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<%!-- Source filter pills --%>
|
<%!-- Source filter pills --%>
|
||||||
<div class="flex items-center gap-2 border-l border-gray-200 pl-4 dark:border-white/10">
|
<div class="flex items-center gap-2 border-l border-cool-steel-200 pl-4 dark:border-white/10">
|
||||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400">Source:</span>
|
<span class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Source:
|
||||||
|
</span>
|
||||||
<.link
|
<.link
|
||||||
id="filter-source-all"
|
id="filter-source-all"
|
||||||
patch={
|
patch={
|
||||||
|
|
@ -77,8 +81,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(is_nil(@filter_source),
|
if(is_nil(@filter_source),
|
||||||
do: "bg-gray-200 text-gray-800 dark:bg-white/20 dark:text-white",
|
do: "bg-cool-steel-200 text-cool-steel-800 dark:bg-white/20 dark:text-white",
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -93,8 +98,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(@filter_source == source,
|
if(@filter_source == source,
|
||||||
do: "bg-gray-200 text-gray-800 dark:bg-white/20 dark:text-white",
|
do: "bg-cool-steel-200 text-cool-steel-800 dark:bg-white/20 dark:text-white",
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -103,8 +109,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Urgency filter --%>
|
<%!-- Urgency filter --%>
|
||||||
<div class="flex items-center gap-2 border-l border-gray-200 pl-4 dark:border-white/10">
|
<div class="flex items-center gap-2 border-l border-cool-steel-200 pl-4 dark:border-white/10">
|
||||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400">Urgency:</span>
|
<span class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Urgency:
|
||||||
|
</span>
|
||||||
<.link
|
<.link
|
||||||
id="filter-urgency-all"
|
id="filter-urgency-all"
|
||||||
patch={
|
patch={
|
||||||
|
|
@ -113,8 +121,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(is_nil(@filter_urgency),
|
if(is_nil(@filter_urgency),
|
||||||
do: "bg-gray-200 text-gray-800 dark:bg-white/20 dark:text-white",
|
do: "bg-cool-steel-200 text-cool-steel-800 dark:bg-white/20 dark:text-white",
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -123,10 +132,11 @@
|
||||||
<.link
|
<.link
|
||||||
:for={
|
:for={
|
||||||
{urgency, classes} <- [
|
{urgency, classes} <- [
|
||||||
{"critical", "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"},
|
{"critical",
|
||||||
{"warning",
|
"bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"},
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"},
|
{"warning", "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"},
|
||||||
{"info", "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"}
|
{"info",
|
||||||
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
id={"filter-urgency-#{urgency}"}
|
id={"filter-urgency-#{urgency}"}
|
||||||
|
|
@ -137,7 +147,8 @@
|
||||||
"rounded-md px-2 py-1 text-xs font-medium capitalize",
|
"rounded-md px-2 py-1 text-xs font-medium capitalize",
|
||||||
if(@filter_urgency == urgency,
|
if(@filter_urgency == urgency,
|
||||||
do: classes,
|
do: classes,
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -150,16 +161,16 @@
|
||||||
<%= if any_selected?(@selected_ids) do %>
|
<%= if any_selected?(@selected_ids) do %>
|
||||||
<div
|
<div
|
||||||
id="bulk-actions"
|
id="bulk-actions"
|
||||||
class="mt-4 flex items-center gap-3 rounded-lg bg-indigo-50 px-4 py-3 dark:bg-indigo-900/20"
|
class="mt-4 flex items-center gap-3 rounded-lg bg-cerulean-50 px-4 py-3 dark:bg-cerulean-900/20"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-medium text-indigo-700 dark:text-indigo-200">
|
<span class="text-sm font-medium text-cerulean-700 dark:text-cerulean-200">
|
||||||
{MapSet.size(@selected_ids)} selected
|
{MapSet.size(@selected_ids)} selected
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="deselect-all-btn"
|
id="deselect-all-btn"
|
||||||
phx-click="deselect_all"
|
phx-click="deselect_all"
|
||||||
class="text-sm text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Deselect All")}
|
{t("Deselect All")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -167,7 +178,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="bulk-dismiss-btn"
|
id="bulk-dismiss-btn"
|
||||||
phx-click="bulk_dismiss"
|
phx-click="bulk_dismiss"
|
||||||
class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-3 py-1.5 text-sm font-medium text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Dismiss Selected")}
|
{t("Dismiss Selected")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -180,16 +191,16 @@
|
||||||
<div id="empty-state" class="flex items-center justify-center py-8">
|
<div id="empty-state" class="flex items-center justify-center py-8">
|
||||||
<div class="card shadow-md border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 max-w-md w-full">
|
<div class="card shadow-md border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 max-w-md w-full">
|
||||||
<div class="items-center text-center">
|
<div class="items-center text-center">
|
||||||
<div class="rounded-full bg-amber-100 dark:bg-amber-900/40 p-4 mb-2">
|
<div class="rounded-full bg-wheat-100 dark:bg-wheat-900/40 p-4 mb-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-light-bulb-solid"
|
name="hero-light-bulb-solid"
|
||||||
class="h-12 w-12 text-amber-500 dark:text-amber-400"
|
class="h-12 w-12 text-wheat-500 dark:text-wheat-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No Insights Yet")}
|
{t("No Insights Yet")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= cond do %>
|
<%= cond do %>
|
||||||
<% @filter_status == "dismissed" -> %>
|
<% @filter_status == "dismissed" -> %>
|
||||||
{t("No dismissed insights.")}
|
{t("No dismissed insights.")}
|
||||||
|
|
@ -216,7 +227,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="select-all-btn"
|
id="select-all-btn"
|
||||||
phx-click="select_all"
|
phx-click="select_all"
|
||||||
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Select All")}
|
{t("Select All")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -227,11 +238,11 @@
|
||||||
:for={insight <- @insights}
|
:for={insight <- @insights}
|
||||||
id={"insight-#{insight.id}"}
|
id={"insight-#{insight.id}"}
|
||||||
class={[
|
class={[
|
||||||
"rounded-lg border bg-white p-4 transition-shadow hover:shadow-md dark:bg-gray-800/50",
|
"rounded-lg border bg-white p-4 transition-shadow hover:shadow-md dark:bg-cool-steel-800/50",
|
||||||
if(selected?(@selected_ids, insight.id),
|
if(selected?(@selected_ids, insight.id),
|
||||||
do:
|
do:
|
||||||
"border-indigo-300 ring-1 ring-indigo-200 dark:border-indigo-600 dark:ring-indigo-800",
|
"border-cerulean-300 ring-1 ring-cerulean-200 dark:border-cerulean-600 dark:ring-cerulean-800",
|
||||||
else: "border-gray-200 dark:border-white/10"
|
else: "border-cool-steel-200 dark:border-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -248,8 +259,9 @@
|
||||||
"flex h-5 w-5 items-center justify-center rounded border",
|
"flex h-5 w-5 items-center justify-center rounded border",
|
||||||
if(selected?(@selected_ids, insight.id),
|
if(selected?(@selected_ids, insight.id),
|
||||||
do:
|
do:
|
||||||
"border-indigo-600 bg-indigo-600 dark:border-indigo-500 dark:bg-indigo-500",
|
"border-cerulean-600 bg-cerulean-600 dark:border-cerulean-500 dark:bg-cerulean-500",
|
||||||
else: "border-gray-300 bg-white dark:border-gray-600 dark:bg-gray-700"
|
else:
|
||||||
|
"border-cool-steel-300 bg-white dark:border-cool-steel-600 dark:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<%= if selected?(@selected_ids, insight.id) do %>
|
<%= if selected?(@selected_ids, insight.id) do %>
|
||||||
|
|
@ -278,29 +290,29 @@
|
||||||
|
|
||||||
<%!-- AI badge — present on any LLM-enriched insight --%>
|
<%!-- AI badge — present on any LLM-enriched insight --%>
|
||||||
<%= if insight.llm_enriched_at do %>
|
<%= if insight.llm_enriched_at do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-600 px-2 py-0.5 text-xs font-semibold text-white dark:bg-blue-500">
|
<span class="inline-flex items-center rounded-full bg-cerulean-600 px-2 py-0.5 text-xs font-semibold text-white dark:bg-cerulean-500">
|
||||||
AI
|
AI
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%!-- Type badge --%>
|
<%!-- Type badge --%>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2 py-0.5 text-xs font-medium text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{String.replace(insight.type, "_", " ")}
|
{String.replace(insight.type, "_", " ")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-1 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-1 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{insight.title}
|
{insight.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<%= if insight.description do %>
|
<%= if insight.description do %>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{insight.description}
|
{insight.description}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.llm_summary do %>
|
<%= if insight.llm_summary do %>
|
||||||
<div class="mt-2 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-sm text-blue-900 dark:border-blue-900/40 dark:bg-blue-950/40 dark:text-blue-100">
|
<div class="mt-2 rounded-md border border-cerulean-200 bg-cerulean-50 px-3 py-2 text-sm text-cerulean-900 dark:border-cerulean-900/40 dark:bg-cerulean-950/40 dark:text-cerulean-100">
|
||||||
<p class="font-medium">{t("Summary")}</p>
|
<p class="font-medium">{t("Summary")}</p>
|
||||||
<p>{insight.llm_summary}</p>
|
<p>{insight.llm_summary}</p>
|
||||||
<%= if insight.recommended_action do %>
|
<%= if insight.recommended_action do %>
|
||||||
|
|
@ -309,7 +321,7 @@
|
||||||
{insight.recommended_action}
|
{insight.recommended_action}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p class="mt-1 text-xs text-blue-700 dark:text-blue-300">
|
<p class="mt-1 text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
AI-generated
|
AI-generated
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -317,8 +329,8 @@
|
||||||
|
|
||||||
<%!-- Affected devices list (from metadata) --%>
|
<%!-- Affected devices list (from metadata) --%>
|
||||||
<%= if insight.metadata["device_names"] && length(insight.metadata["device_names"]) > 0 do %>
|
<%= if insight.metadata["device_names"] && length(insight.metadata["device_names"]) > 0 do %>
|
||||||
<div class="mt-2 rounded-md bg-gray-50 p-3 dark:bg-white/5">
|
<div class="mt-2 rounded-md bg-cool-steel-50 p-3 dark:bg-white/5">
|
||||||
<p class="text-xs font-medium text-gray-700 dark:text-gray-300">
|
<p class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Affected devices:")}
|
{t("Affected devices:")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-1.5 flex flex-wrap gap-1.5">
|
<div class="mt-1.5 flex flex-wrap gap-1.5">
|
||||||
|
|
@ -328,18 +340,18 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/devices/#{Enum.at(insight.metadata["device_ids"], idx)}"
|
~p"/devices/#{Enum.at(insight.metadata["device_ids"], idx)}"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center rounded-md bg-white px-2 py-0.5 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10 hover:bg-indigo-50 dark:bg-indigo-900/20 dark:text-indigo-400 dark:ring-indigo-400/20 dark:hover:bg-indigo-900/40"
|
class="inline-flex items-center rounded-md bg-white px-2 py-0.5 text-xs font-medium text-cerulean-700 ring-1 ring-inset ring-cerulean-700/10 hover:bg-cerulean-50 dark:bg-cerulean-900/20 dark:text-cerulean-400 dark:ring-cerulean-400/20 dark:hover:bg-cerulean-900/40"
|
||||||
>
|
>
|
||||||
{name || "Unnamed"}
|
{name || "Unnamed"}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center rounded-md bg-white px-2 py-0.5 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-white/10 dark:text-gray-300 dark:ring-white/10">
|
<span class="inline-flex items-center rounded-md bg-white px-2 py-0.5 text-xs font-medium text-cool-steel-600 ring-1 ring-inset ring-cool-steel-500/10 dark:bg-white/10 dark:text-cool-steel-300 dark:ring-white/10">
|
||||||
{name || "Unnamed"}
|
{name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if insight.metadata["count"] && insight.metadata["count"] > length(insight.metadata["device_names"]) do %>
|
<%= if insight.metadata["count"] && insight.metadata["count"] > length(insight.metadata["device_names"]) do %>
|
||||||
<span class="inline-flex items-center rounded-md bg-gray-100 px-2 py-0.5 text-xs text-gray-500 dark:bg-white/10 dark:text-gray-400">
|
<span class="inline-flex items-center rounded-md bg-cool-steel-100 px-2 py-0.5 text-xs text-cool-steel-500 dark:bg-white/10 dark:text-cool-steel-400">
|
||||||
+{insight.metadata["count"] - length(insight.metadata["device_names"])} more
|
+{insight.metadata["count"] - length(insight.metadata["device_names"])} more
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -349,8 +361,8 @@
|
||||||
|
|
||||||
<%!-- Actionable recommendations --%>
|
<%!-- Actionable recommendations --%>
|
||||||
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "untracked" do %>
|
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "untracked" do %>
|
||||||
<div class="mt-3 rounded-md border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
<div class="mt-3 rounded-md border border-cerulean-200 bg-cerulean-50 p-3 dark:border-cerulean-800 dark:bg-cerulean-900/20">
|
||||||
<p class="text-xs font-medium text-blue-800 dark:text-blue-300 mb-2">
|
<p class="text-xs font-medium text-cerulean-800 dark:text-cerulean-300 mb-2">
|
||||||
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
||||||
{t("To fix this:")}
|
{t("To fix this:")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -359,7 +371,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-blue-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-blue-700"
|
class="inline-flex items-center gap-1 rounded-md bg-cerulean-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-cerulean-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrows-right-left" class="h-3.5 w-3.5" />
|
<.icon name="hero-arrows-right-left" class="h-3.5 w-3.5" />
|
||||||
{t("Link devices to Gaiia")}
|
{t("Link devices to Gaiia")}
|
||||||
|
|
@ -368,7 +380,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-gray-300 dark:ring-white/10 dark:hover:bg-white/20"
|
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-cool-steel-700 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-cool-steel-300 dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<.icon name="hero-table-cells" class="h-3.5 w-3.5" />
|
<.icon name="hero-table-cells" class="h-3.5 w-3.5" />
|
||||||
{t("View comparison report")}
|
{t("View comparison report")}
|
||||||
|
|
@ -378,8 +390,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "ghost" do %>
|
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "ghost" do %>
|
||||||
<div class="mt-3 rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20">
|
<div class="mt-3 rounded-md border border-wheat-200 bg-wheat-50 p-3 dark:border-wheat-800 dark:bg-wheat-900/20">
|
||||||
<p class="text-xs font-medium text-amber-800 dark:text-amber-200 mb-2">
|
<p class="text-xs font-medium text-wheat-800 dark:text-wheat-200 mb-2">
|
||||||
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
||||||
{t("To fix this:")}
|
{t("To fix this:")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -388,7 +400,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-amber-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-amber-700"
|
class="inline-flex items-center gap-1 rounded-md bg-wheat-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-wheat-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-3.5 w-3.5" />
|
<.icon name="hero-trash" class="h-3.5 w-3.5" />
|
||||||
{t("Remove stale device links")}
|
{t("Remove stale device links")}
|
||||||
|
|
@ -398,8 +410,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "mismatch" do %>
|
<%= if insight.source == "gaiia" && insight.metadata["finding_type"] == "mismatch" do %>
|
||||||
<div class="mt-3 rounded-md border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
<div class="mt-3 rounded-md border border-cerulean-200 bg-cerulean-50 p-3 dark:border-cerulean-800 dark:bg-cerulean-900/20">
|
||||||
<p class="text-xs font-medium text-blue-800 dark:text-blue-300 mb-2">
|
<p class="text-xs font-medium text-cerulean-800 dark:text-cerulean-300 mb-2">
|
||||||
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
<.icon name="hero-light-bulb" class="h-3.5 w-3.5 inline" />
|
||||||
{t("To fix this:")}
|
{t("To fix this:")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -408,7 +420,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-blue-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-blue-700"
|
class="inline-flex items-center gap-1 rounded-md bg-cerulean-600 px-2.5 py-1.5 text-xs font-medium text-white shadow-xs hover:bg-cerulean-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-table-cells" class="h-3.5 w-3.5" />
|
<.icon name="hero-table-cells" class="h-3.5 w-3.5" />
|
||||||
{t("Review conflicting data")}
|
{t("Review conflicting data")}
|
||||||
|
|
@ -547,24 +559,24 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.type == "upstream_degradation" do %>
|
<%= if insight.type == "upstream_degradation" do %>
|
||||||
<div class="mt-3 rounded-md border border-indigo-200 bg-indigo-50 p-3 dark:border-indigo-700 dark:bg-indigo-950/40">
|
<div class="mt-3 rounded-md border border-cerulean-200 bg-cerulean-50 p-3 dark:border-cerulean-700 dark:bg-cerulean-950/40">
|
||||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs font-medium text-indigo-700 dark:text-indigo-200">
|
<span class="text-xs font-medium text-cerulean-700 dark:text-cerulean-200">
|
||||||
{t("Degraded APs")}
|
{t("Degraded APs")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-base font-semibold text-indigo-900 dark:text-white">
|
<span class="font-mono text-base font-semibold text-cerulean-900 dark:text-white">
|
||||||
{insight.metadata["degraded_ap_count"]} / {insight.metadata[
|
{insight.metadata["degraded_ap_count"]} / {insight.metadata[
|
||||||
"total_ap_count"
|
"total_ap_count"
|
||||||
]}
|
]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="ml-auto rounded-md bg-indigo-600 px-2 py-1 text-xs font-semibold text-white dark:bg-indigo-500">
|
<span class="ml-auto rounded-md bg-cerulean-600 px-2 py-1 text-xs font-semibold text-white dark:bg-cerulean-500">
|
||||||
{t("Investigate upstream first")}
|
{t("Investigate upstream first")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<%= if is_list(insight.metadata["degraded_aps"]) do %>
|
<%= if is_list(insight.metadata["degraded_aps"]) do %>
|
||||||
<ul class="mt-2 space-y-1 text-xs text-indigo-900 dark:text-indigo-50">
|
<ul class="mt-2 space-y-1 text-xs text-cerulean-900 dark:text-cerulean-50">
|
||||||
<li
|
<li
|
||||||
:for={ap <- insight.metadata["degraded_aps"]}
|
:for={ap <- insight.metadata["degraded_aps"]}
|
||||||
class="flex items-center justify-between gap-2"
|
class="flex items-center justify-between gap-2"
|
||||||
|
|
@ -573,7 +585,7 @@
|
||||||
<%= if ap["device_id"] do %>
|
<%= if ap["device_id"] do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{ap["device_id"]}"}
|
navigate={~p"/devices/#{ap["device_id"]}"}
|
||||||
class="text-indigo-700 underline hover:text-indigo-900 dark:text-indigo-200 dark:hover:text-white"
|
class="text-cerulean-700 underline hover:text-cerulean-900 dark:text-cerulean-200 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{ap["name"] || "Unnamed"}
|
{ap["name"] || "Unnamed"}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -581,7 +593,7 @@
|
||||||
{ap["name"] || "Unnamed"}
|
{ap["name"] || "Unnamed"}
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-indigo-800 dark:text-indigo-100">
|
<span class="font-mono text-cerulean-800 dark:text-cerulean-100">
|
||||||
QoE {Float.round(ap["qoe_score"] / 1.0, 1)}
|
QoE {Float.round(ap["qoe_score"] / 1.0, 1)}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -591,33 +603,33 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.type == "device_overheating" do %>
|
<%= if insight.type == "device_overheating" do %>
|
||||||
<div class="mt-3 rounded-md border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">
|
<div class="mt-3 rounded-md border border-sweet-salmon-200 bg-sweet-salmon-50 p-3 dark:border-sweet-salmon-800 dark:bg-sweet-salmon-900/20">
|
||||||
<div class="flex flex-wrap items-center gap-4 text-sm">
|
<div class="flex flex-wrap items-center gap-4 text-sm">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs text-red-700 dark:text-red-200">
|
<span class="text-xs text-sweet-salmon-700 dark:text-sweet-salmon-200">
|
||||||
{t("Hottest sensor")}
|
{t("Hottest sensor")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-base font-semibold text-red-900 dark:text-red-100">
|
<span class="font-mono text-base font-semibold text-sweet-salmon-900 dark:text-sweet-salmon-100">
|
||||||
{insight.metadata["temperature_c"]} °C
|
{insight.metadata["temperature_c"]} °C
|
||||||
</span>
|
</span>
|
||||||
<%= if insight.metadata["sensor_descr"] do %>
|
<%= if insight.metadata["sensor_descr"] do %>
|
||||||
<span class="text-xs text-red-700 dark:text-red-200">
|
<span class="text-xs text-sweet-salmon-700 dark:text-sweet-salmon-200">
|
||||||
{insight.metadata["sensor_descr"]}
|
{insight.metadata["sensor_descr"]}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= if insight.metadata["qoe_score"] do %>
|
<%= if insight.metadata["qoe_score"] do %>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs text-red-700 dark:text-red-200">
|
<span class="text-xs text-sweet-salmon-700 dark:text-sweet-salmon-200">
|
||||||
{t("Linked QoE")}
|
{t("Linked QoE")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-base font-semibold text-red-900 dark:text-red-100">
|
<span class="font-mono text-base font-semibold text-sweet-salmon-900 dark:text-sweet-salmon-100">
|
||||||
{Float.round(insight.metadata["qoe_score"] / 1.0, 1)}
|
{Float.round(insight.metadata["qoe_score"] / 1.0, 1)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if is_number(insight.metadata["temperature_c"]) and insight.metadata["temperature_c"] >= 75 do %>
|
<%= if is_number(insight.metadata["temperature_c"]) and insight.metadata["temperature_c"] >= 75 do %>
|
||||||
<span class="ml-auto rounded-md bg-red-600 px-2 py-1 text-xs font-semibold text-white dark:bg-red-500">
|
<span class="ml-auto rounded-md bg-sweet-salmon-600 px-2 py-1 text-xs font-semibold text-white dark:bg-sweet-salmon-500">
|
||||||
{t("Above manufacturer spec")}
|
{t("Above manufacturer spec")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -626,42 +638,42 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.type == "own_fleet_channel_conflict" do %>
|
<%= if insight.type == "own_fleet_channel_conflict" do %>
|
||||||
<div class="mt-3 rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20">
|
<div class="mt-3 rounded-md border border-wheat-200 bg-wheat-50 p-3 dark:border-wheat-800 dark:bg-wheat-900/20">
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs text-amber-700 dark:text-amber-200">
|
<span class="text-xs text-wheat-700 dark:text-wheat-200">
|
||||||
{t("Channel")}
|
{t("Channel")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-base font-semibold text-amber-900 dark:text-amber-100">
|
<span class="font-mono text-base font-semibold text-wheat-900 dark:text-wheat-100">
|
||||||
{insight.metadata["channel"]}
|
{insight.metadata["channel"]}
|
||||||
<%= if insight.metadata["frequency_mhz"] do %>
|
<%= if insight.metadata["frequency_mhz"] do %>
|
||||||
<span class="text-xs font-normal text-amber-700 dark:text-amber-200">
|
<span class="text-xs font-normal text-wheat-700 dark:text-wheat-200">
|
||||||
({insight.metadata["frequency_mhz"]} MHz)
|
({insight.metadata["frequency_mhz"]} MHz)
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs text-amber-700 dark:text-amber-200">
|
<span class="text-xs text-wheat-700 dark:text-wheat-200">
|
||||||
{t("APs sharing")}
|
{t("APs sharing")}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-base font-semibold text-amber-900 dark:text-amber-100">
|
<span class="font-mono text-base font-semibold text-wheat-900 dark:text-wheat-100">
|
||||||
{insight.metadata["ap_count"]}
|
{insight.metadata["ap_count"]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<%= if insight.metadata["has_critical_rf"] do %>
|
<%= if insight.metadata["has_critical_rf"] do %>
|
||||||
<span class="ml-auto rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-800 dark:bg-red-900/40 dark:text-red-200">
|
<span class="ml-auto rounded-md bg-sweet-salmon-100 px-2 py-1 text-xs font-medium text-sweet-salmon-800 dark:bg-sweet-salmon-900/40 dark:text-sweet-salmon-200">
|
||||||
{t("Critical RF environment detected")}
|
{t("Critical RF environment detected")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= if is_list(insight.metadata["aps"]) and insight.metadata["aps"] != [] do %>
|
<%= if is_list(insight.metadata["aps"]) and insight.metadata["aps"] != [] do %>
|
||||||
<div class="mt-3 overflow-x-auto">
|
<div class="mt-3 overflow-x-auto">
|
||||||
<p class="text-xs font-medium text-amber-800 dark:text-amber-200">
|
<p class="text-xs font-medium text-wheat-800 dark:text-wheat-200">
|
||||||
{t("Conflicting APs")}
|
{t("Conflicting APs")}
|
||||||
</p>
|
</p>
|
||||||
<table class="mt-1 w-full text-left text-xs text-amber-900 dark:text-amber-100">
|
<table class="mt-1 w-full text-left text-xs text-wheat-900 dark:text-wheat-100">
|
||||||
<thead class="text-[11px] font-medium text-amber-700 dark:text-amber-200">
|
<thead class="text-[11px] font-medium text-wheat-700 dark:text-wheat-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="py-1 pr-3">{t("AP")}</th>
|
<th class="py-1 pr-3">{t("AP")}</th>
|
||||||
<th class="py-1 pr-3">{t("Width")}</th>
|
<th class="py-1 pr-3">{t("Width")}</th>
|
||||||
|
|
@ -674,7 +686,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
:for={ap <- insight.metadata["aps"]}
|
:for={ap <- insight.metadata["aps"]}
|
||||||
class="border-t border-amber-200/50 dark:border-amber-800/40"
|
class="border-t border-wheat-200/50 dark:border-wheat-800/40"
|
||||||
>
|
>
|
||||||
<td class="py-1 pr-3">
|
<td class="py-1 pr-3">
|
||||||
<%= if ap["device_id"] do %>
|
<%= if ap["device_id"] do %>
|
||||||
|
|
@ -688,7 +700,7 @@
|
||||||
{ap["name"] || "Unnamed"}
|
{ap["name"] || "Unnamed"}
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if ap["rf_severity"] == "critical" do %>
|
<%= if ap["rf_severity"] == "critical" do %>
|
||||||
<span class="ml-1 rounded bg-red-600 px-1 py-0.5 text-[10px] font-semibold text-white dark:bg-red-500">
|
<span class="ml-1 rounded bg-sweet-salmon-600 px-1 py-0.5 text-[10px] font-semibold text-white dark:bg-sweet-salmon-500">
|
||||||
crit
|
crit
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -778,7 +790,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="mt-2 flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
|
<div class="mt-2 flex flex-wrap items-center gap-3 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%!-- Access point --%>
|
<%!-- Access point --%>
|
||||||
<%= if insight.preseem_access_point do %>
|
<%= if insight.preseem_access_point do %>
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
|
|
@ -791,7 +803,7 @@
|
||||||
<%= if insight.device do %>
|
<%= if insight.device do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{insight.device.id}"}
|
navigate={~p"/devices/#{insight.device.id}"}
|
||||||
class="flex items-center gap-1 text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="flex items-center gap-1 text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
||||||
{insight.device.name || "Device"}
|
{insight.device.name || "Device"}
|
||||||
|
|
@ -817,7 +829,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="dismiss"
|
phx-click="dismiss"
|
||||||
phx-value-id={insight.id}
|
phx-value-id={insight.id}
|
||||||
class="flex-shrink-0 rounded-md px-2.5 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-white"
|
class="flex-shrink-0 rounded-md px-2.5 py-1.5 text-xs font-medium text-cool-steel-600 hover:bg-cool-steel-100 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Dismiss")}
|
{t("Dismiss")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,16 @@
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/maintenance"}
|
navigate={~p"/maintenance"}
|
||||||
class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
class="inline-flex items-center gap-1 text-sm text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
||||||
{t("Back to Maintenance Windows")}
|
{t("Back to Maintenance Windows")}
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white mb-6">{@page_title}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white mb-6">{@page_title}</h1>
|
||||||
|
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-6">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-6">
|
||||||
<.form
|
<.form
|
||||||
for={@form}
|
for={@form}
|
||||||
phx-change="validate"
|
phx-change="validate"
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
<%!-- Scope --%>
|
<%!-- Scope --%>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2">
|
||||||
{t("Scope")}
|
{t("Scope")}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-4 mb-3">
|
<div class="flex items-center gap-4 mb-3">
|
||||||
|
|
@ -56,9 +56,11 @@
|
||||||
checked={@scope_type == "org"}
|
checked={@scope_type == "org"}
|
||||||
phx-click="change_scope"
|
phx-click="change_scope"
|
||||||
phx-value-scope_type="org"
|
phx-value-scope_type="org"
|
||||||
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
class="text-cerulean-600 focus:ring-cerulean-500 dark:bg-cool-steel-800 dark:border-white/10"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Org-wide")}</span>
|
<span class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
|
{t("Org-wide")}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
|
|
@ -68,9 +70,11 @@
|
||||||
checked={@scope_type == "site"}
|
checked={@scope_type == "site"}
|
||||||
phx-click="change_scope"
|
phx-click="change_scope"
|
||||||
phx-value-scope_type="site"
|
phx-value-scope_type="site"
|
||||||
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
class="text-cerulean-600 focus:ring-cerulean-500 dark:bg-cool-steel-800 dark:border-white/10"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Site")}</span>
|
<span class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
|
{t("Site")}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="inline-flex items-center gap-2 cursor-pointer">
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
|
|
@ -80,9 +84,11 @@
|
||||||
checked={@scope_type == "device"}
|
checked={@scope_type == "device"}
|
||||||
phx-click="change_scope"
|
phx-click="change_scope"
|
||||||
phx-value-scope_type="device"
|
phx-value-scope_type="device"
|
||||||
class="text-blue-600 focus:ring-blue-500 dark:bg-gray-800 dark:border-white/10"
|
class="text-cerulean-600 focus:ring-cerulean-500 dark:bg-cool-steel-800 dark:border-white/10"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">{t("Device")}</span>
|
<span class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
|
{t("Device")}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -148,16 +154,16 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%!-- Submit --%>
|
<%!-- Submit --%>
|
||||||
<div class="flex items-center justify-end gap-3 pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-end gap-3 pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/maintenance"}
|
navigate={~p"/maintenance"}
|
||||||
class="rounded-lg px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors"
|
class="rounded-lg px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-100 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800 transition-colors"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</.link>
|
</.link>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
class="rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 transition-colors"
|
||||||
>
|
>
|
||||||
{t("Save")}
|
{t("Save")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,18 @@
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Maintenance Windows")}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("Maintenance Windows")}
|
||||||
|
</h1>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/maintenance/new"}
|
navigate={~p"/maintenance/new"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("New Window")}
|
{t("New Window")}
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Schedule planned downtime to suppress alerts during maintenance work.")}
|
{t("Schedule planned downtime to suppress alerts during maintenance work.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -24,9 +26,9 @@
|
||||||
patch={~p"/maintenance?filter=all"}
|
patch={~p"/maintenance?filter=all"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "all" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "all" && "bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "all" &&
|
@filter != "all" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("All")}
|
{t("All")}
|
||||||
|
|
@ -35,9 +37,10 @@
|
||||||
patch={~p"/maintenance?filter=active"}
|
patch={~p"/maintenance?filter=active"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "active" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "active" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "active" &&
|
@filter != "active" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Active")}
|
{t("Active")}
|
||||||
|
|
@ -46,9 +49,10 @@
|
||||||
patch={~p"/maintenance?filter=upcoming"}
|
patch={~p"/maintenance?filter=upcoming"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "upcoming" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "upcoming" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "upcoming" &&
|
@filter != "upcoming" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Upcoming")}
|
{t("Upcoming")}
|
||||||
|
|
@ -57,9 +61,9 @@
|
||||||
patch={~p"/maintenance?filter=past"}
|
patch={~p"/maintenance?filter=past"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@filter == "past" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@filter == "past" && "bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@filter != "past" &&
|
@filter != "past" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Past")}
|
{t("Past")}
|
||||||
|
|
@ -71,51 +75,51 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-wrench-screwdriver"
|
name="hero-wrench-screwdriver"
|
||||||
class="h-12 w-12 text-gray-300 dark:text-gray-600 mx-auto mb-4"
|
class="h-12 w-12 text-cool-steel-300 dark:text-cool-steel-600 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300">
|
<h3 class="text-lg font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("No maintenance windows")}
|
{t("No maintenance windows")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Schedule maintenance to suppress alerts during planned work.")}
|
{t("Schedule maintenance to suppress alerts during planned work.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Name")}
|
{t("Name")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Scope")}
|
{t("Scope")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Time Range")}
|
{t("Time Range")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Created By")}
|
{t("Created By")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-900">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-900">
|
||||||
<%= for window <- @windows do %>
|
<%= for window <- @windows do %>
|
||||||
<tr
|
<tr
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors cursor-pointer"
|
class="hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/30 transition-colors cursor-pointer"
|
||||||
phx-click={JS.navigate(~p"/maintenance/#{window.id}")}
|
phx-click={JS.navigate(~p"/maintenance/#{window.id}")}
|
||||||
>
|
>
|
||||||
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white">
|
<td class="px-4 py-3 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{window.name}
|
{window.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{scope_label(window)}
|
{scope_label(window)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400 whitespace-nowrap">
|
||||||
{format_datetime(window.starts_at, @timezone)} — {format_datetime(
|
{format_datetime(window.starts_at, @timezone)} — {format_datetime(
|
||||||
window.ends_at,
|
window.ends_at,
|
||||||
@timezone
|
@timezone
|
||||||
|
|
@ -128,14 +132,14 @@
|
||||||
status == :active &&
|
status == :active &&
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
|
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
|
||||||
status == :upcoming &&
|
status == :upcoming &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/40 dark:text-cerulean-300",
|
||||||
status == :past &&
|
status == :past &&
|
||||||
"bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
]}>
|
]}>
|
||||||
{status_label(status)}
|
{status_label(status)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{if window.created_by, do: window.created_by.email, else: "—"}
|
{if window.created_by, do: window.created_by.email, else: "—"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/maintenance"}
|
navigate={~p"/maintenance"}
|
||||||
class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
class="inline-flex items-center gap-1 text-sm text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
||||||
{t("Back to Maintenance Windows")}
|
{t("Back to Maintenance Windows")}
|
||||||
|
|
@ -35,13 +35,15 @@
|
||||||
|
|
||||||
<div class="flex items-start justify-between mb-6">
|
<div class="flex items-start justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{@window.name}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{@window.name}</h1>
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium mt-2",
|
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium mt-2",
|
||||||
status == :active &&
|
status == :active &&
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
|
"bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
|
||||||
status == :upcoming && "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300",
|
status == :upcoming &&
|
||||||
status == :past && "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/40 dark:text-cerulean-300",
|
||||||
|
status == :past &&
|
||||||
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
]}>
|
]}>
|
||||||
{status_label(status)}
|
{status_label(status)}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -49,7 +51,7 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/maintenance/#{@window.id}/edit"}
|
navigate={~p"/maintenance/#{@window.id}/edit"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-cool-steel-100 px-4 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-pencil" class="h-4 w-4" />
|
<.icon name="hero-pencil" class="h-4 w-4" />
|
||||||
{t("Edit")}
|
{t("Edit")}
|
||||||
|
|
@ -58,7 +60,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
data-confirm={t("Are you sure you want to delete this maintenance window?")}
|
data-confirm={t("Are you sure you want to delete this maintenance window?")}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-red-100 px-4 py-2 text-sm font-medium text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-300 dark:hover:bg-red-900/50 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-sweet-salmon-100 px-4 py-2 text-sm font-medium text-sweet-salmon-700 hover:bg-sweet-salmon-200 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-300 dark:hover:bg-sweet-salmon-900/50 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
{t("Delete")}
|
{t("Delete")}
|
||||||
|
|
@ -66,14 +68,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-white/10">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%!-- Reason --%>
|
<%!-- Reason --%>
|
||||||
<%= if @window.reason do %>
|
<%= if @window.reason do %>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Reason")}
|
{t("Reason")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white whitespace-pre-wrap">
|
<dd class="text-sm text-cool-steel-900 dark:text-white whitespace-pre-wrap">
|
||||||
{@window.reason}
|
{@window.reason}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -81,18 +83,18 @@
|
||||||
|
|
||||||
<%!-- Scope --%>
|
<%!-- Scope --%>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Scope")}
|
{t("Scope")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white">{scope_label(@window)}</dd>
|
<dd class="text-sm text-cool-steel-900 dark:text-white">{scope_label(@window)}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Time Range --%>
|
<%!-- Time Range --%>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Time Range")}
|
{t("Time Range")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white">
|
<dd class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{format_datetime(@window.starts_at, @timezone)} — {format_datetime(
|
{format_datetime(@window.starts_at, @timezone)} — {format_datetime(
|
||||||
@window.ends_at,
|
@window.ends_at,
|
||||||
@timezone
|
@timezone
|
||||||
|
|
@ -102,10 +104,10 @@
|
||||||
|
|
||||||
<%!-- Recurring --%>
|
<%!-- Recurring --%>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Recurring")}
|
{t("Recurring")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white">
|
<dd class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<%= if @window.recurring do %>
|
<%= if @window.recurring do %>
|
||||||
{t("Yes")} — {@window.recurrence_rule || t("No rule specified")}
|
{t("Yes")} — {@window.recurrence_rule || t("No rule specified")}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -116,16 +118,16 @@
|
||||||
|
|
||||||
<%!-- Suppress Alerts --%>
|
<%!-- Suppress Alerts --%>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Suppress Alerts")}
|
{t("Suppress Alerts")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white">
|
<dd class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
<%= if @window.suppress_alerts do %>
|
<%= if @window.suppress_alerts do %>
|
||||||
<span class="inline-flex items-center gap-1 text-green-700 dark:text-green-400">
|
<span class="inline-flex items-center gap-1 text-green-700 dark:text-green-400">
|
||||||
<.icon name="hero-check-circle" class="h-4 w-4" /> {t("Yes")}
|
<.icon name="hero-check-circle" class="h-4 w-4" /> {t("Yes")}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center gap-1 text-gray-500 dark:text-gray-400">
|
<span class="inline-flex items-center gap-1 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-x-circle" class="h-4 w-4" /> {t("No")}
|
<.icon name="hero-x-circle" class="h-4 w-4" /> {t("No")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -134,10 +136,10 @@
|
||||||
|
|
||||||
<%!-- Created By --%>
|
<%!-- Created By --%>
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
<dt class="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">
|
<dt class="text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-1">
|
||||||
{t("Created By")}
|
{t("Created By")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm text-gray-900 dark:text-white">
|
<dd class="text-sm text-cool-steel-900 dark:text-white">
|
||||||
{if @window.created_by, do: @window.created_by.email, else: "—"}
|
{if @window.created_by, do: @window.created_by.email, else: "—"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<.header>
|
<.header>
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
{t("Sites Map")}
|
{t("Sites Map")}
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 px-2 py-0.5 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">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 px-2 py-0.5 text-xs font-medium text-cerulean-700 ring-1 ring-inset ring-cerulean-700/10 dark:bg-cerulean-400/10 dark:text-cerulean-400 dark:ring-cerulean-400/30">
|
||||||
{t("Geographic")}
|
{t("Geographic")}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -23,19 +23,19 @@
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-white/10 overflow-hidden">
|
||||||
<div class="p-4 border-b border-gray-200 dark:border-white/10">
|
<div class="p-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Geographic Site Locations")}
|
{t("Geographic Site Locations")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Click markers to view site details")}
|
{t("Click markers to view site details")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Showing {length(@sites)} sites with coordinates
|
Showing {length(@sites)} sites with coordinates
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -54,8 +54,13 @@
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div class="flex items-center justify-center h-full">
|
<div class="flex items-center justify-center h-full">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon name="hero-arrow-path" class="h-8 w-8 text-gray-400 animate-spin mx-auto" />
|
<.icon
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Loading map...</p>
|
name="hero-arrow-path"
|
||||||
|
class="h-8 w-8 text-cool-steel-400 animate-spin mx-auto"
|
||||||
|
/>
|
||||||
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Loading map...
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,24 +70,32 @@
|
||||||
<!-- Stats and Site List -->
|
<!-- Stats and Site List -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- Site Statistics -->
|
<!-- Site Statistics -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">{t("Statistics")}</h3>
|
<h3 class="text-lg font-medium text-cool-steel-900 dark:text-white mb-4">
|
||||||
|
{t("Statistics")}
|
||||||
|
</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Sites</div>
|
<div class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<div class="text-2xl font-semibold text-gray-900 dark:text-white">
|
Total Sites
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{Sites.count_organization_sites(@organization.id)}
|
{Sites.count_organization_sites(@organization.id)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">Geocoded Sites</div>
|
<div class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<div class="text-2xl font-semibold text-gray-900 dark:text-white">
|
Geocoded Sites
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{length(@sites)}
|
{length(@sites)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">Coverage</div>
|
<div class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
Coverage
|
||||||
|
</div>
|
||||||
|
<div class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<%= if Sites.count_organization_sites(@organization.id) > 0 do %>
|
<%= if Sites.count_organization_sites(@organization.id) > 0 do %>
|
||||||
{round(length(@sites) / Sites.count_organization_sites(@organization.id) * 100)}%
|
{round(length(@sites) / Sites.count_organization_sites(@organization.id) * 100)}%
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -94,47 +107,47 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sites with Coordinates -->
|
<!-- Sites with Coordinates -->
|
||||||
<div class="lg:col-span-2 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="lg:col-span-2 bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-white/10">
|
<div class="px-6 py-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
|
<h3 class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Sites with Geographic Data")}
|
{t("Sites with Geographic Data")}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-h-96 overflow-y-auto">
|
<div class="max-h-96 overflow-y-auto">
|
||||||
<%= if length(@sites) > 0 do %>
|
<%= if length(@sites) > 0 do %>
|
||||||
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for site <- @sites do %>
|
<%= for site <- @sites do %>
|
||||||
<div class="px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
<div class="px-6 py-4 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700/50">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
<h4 class="text-sm font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
{site.name}
|
{site.name}
|
||||||
</h4>
|
</h4>
|
||||||
<%= if site.device_count > 0 do %>
|
<%= if site.device_count > 0 do %>
|
||||||
<span class="ml-2 inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
<span class="ml-2 inline-flex items-center rounded-full bg-cerulean-100 px-2 py-0.5 text-xs font-medium text-cerulean-700 dark:bg-cerulean-900 dark:text-cerulean-300">
|
||||||
{site.device_count} devices
|
{site.device_count} devices
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= if site.address do %>
|
<%= if site.address do %>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{site.address}
|
{site.address}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if site.description do %>
|
<%= if site.description do %>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{site.description}
|
{site.description}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p class="mt-1 text-xs text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-400">
|
||||||
{Float.round(site.latitude, 4)}, {Float.round(site.longitude, 4)}
|
{Float.round(site.latitude, 4)}, {Float.round(site.longitude, 4)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{site.id}"}
|
navigate={~p"/sites/#{site.id}"}
|
||||||
class="text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium"
|
class="text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400 dark:hover:text-cerulean-300 text-sm font-medium"
|
||||||
>
|
>
|
||||||
View →
|
View →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -145,11 +158,11 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="px-6 py-8 text-center">
|
<div class="px-6 py-8 text-center">
|
||||||
<.icon name="hero-map-pin" class="mx-auto h-12 w-12 text-gray-400" />
|
<.icon name="hero-map-pin" class="mx-auto h-12 w-12 text-cool-steel-400" />
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("No geocoded sites")}
|
{t("No geocoded sites")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Add addresses and coordinates to your sites to see them on the map.")}
|
{t("Add addresses and coordinates to your sites to see them on the map.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,38 @@
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<nav class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-4">
|
<nav class="flex items-center gap-2 text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-4">
|
||||||
<.link navigate={~p"/devices"} class="hover:text-gray-700 dark:hover:text-gray-300">
|
<.link
|
||||||
|
navigate={~p"/devices"}
|
||||||
|
class="hover:text-cool-steel-700 dark:hover:text-cool-steel-300"
|
||||||
|
>
|
||||||
{t("Devices")}
|
{t("Devices")}
|
||||||
</.link>
|
</.link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@device.id}"}
|
navigate={~p"/devices/#{@device.id}"}
|
||||||
class="hover:text-gray-700 dark:hover:text-gray-300"
|
class="hover:text-cool-steel-700 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{@device.name}
|
{@device.name}
|
||||||
</.link>
|
</.link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@device.id}?tab=backups"}
|
navigate={~p"/devices/#{@device.id}?tab=backups"}
|
||||||
class="hover:text-gray-700 dark:hover:text-gray-300"
|
class="hover:text-cool-steel-700 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t("Backups")}
|
{t("Backups")}
|
||||||
</.link>
|
</.link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span class="text-gray-900 dark:text-white">Compare</span>
|
<span class="text-cool-steel-900 dark:text-white">Compare</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{t("Compare Configuration Backups")}
|
{t("Compare Configuration Backups")}
|
||||||
</h1>
|
</h1>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@device.id}?tab=backups"}
|
navigate={~p"/devices/#{@device.id}?tab=backups"}
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700"
|
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-cool-steel-700 bg-white border border-cool-steel-300 rounded-lg hover:bg-cool-steel-50 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:border-cool-steel-600 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" /> Back to Backups
|
<.icon name="hero-arrow-left" class="h-4 w-4" /> Back to Backups
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -39,21 +42,23 @@
|
||||||
<!-- Backup Info Cards -->
|
<!-- Backup Info Cards -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||||
<!-- Backup A -->
|
<!-- Backup A -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Backup A (Older)</h3>
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
|
Backup A (Older)
|
||||||
|
</h3>
|
||||||
<button
|
<button
|
||||||
phx-click="download_config"
|
phx-click="download_config"
|
||||||
phx-value-backup="a"
|
phx-value-backup="a"
|
||||||
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download
|
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<dl class="space-y-1 text-sm">
|
<dl class="space-y-1 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Date:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Date:</dt>
|
||||||
<dd class="text-gray-900 dark:text-white font-mono">
|
<dd class="text-cool-steel-900 dark:text-white font-mono">
|
||||||
{ToweropsWeb.TimeHelpers.format_iso8601(
|
{ToweropsWeb.TimeHelpers.format_iso8601(
|
||||||
@backup_a.backed_up_at,
|
@backup_a.backed_up_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -62,18 +67,18 @@
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Size:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Size:</dt>
|
||||||
<dd class="text-gray-900 dark:text-white font-mono">
|
<dd class="text-cool-steel-900 dark:text-white font-mono">
|
||||||
{format_bytes(@backup_a.config_size_bytes)}
|
{format_bytes(@backup_a.config_size_bytes)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Source:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Source:</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
||||||
@backup_a.trigger_source == "daily_cron" &&
|
@backup_a.trigger_source == "daily_cron" &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900 dark:text-cerulean-200",
|
||||||
@backup_a.trigger_source == "manual" &&
|
@backup_a.trigger_source == "manual" &&
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||||
]}>
|
]}>
|
||||||
|
|
@ -84,21 +89,23 @@
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<!-- Backup B -->
|
<!-- Backup B -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Backup B (Newer)</h3>
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
|
Backup B (Newer)
|
||||||
|
</h3>
|
||||||
<button
|
<button
|
||||||
phx-click="download_config"
|
phx-click="download_config"
|
||||||
phx-value-backup="b"
|
phx-value-backup="b"
|
||||||
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download
|
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Download
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<dl class="space-y-1 text-sm">
|
<dl class="space-y-1 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Date:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Date:</dt>
|
||||||
<dd class="text-gray-900 dark:text-white font-mono">
|
<dd class="text-cool-steel-900 dark:text-white font-mono">
|
||||||
{ToweropsWeb.TimeHelpers.format_iso8601(
|
{ToweropsWeb.TimeHelpers.format_iso8601(
|
||||||
@backup_b.backed_up_at,
|
@backup_b.backed_up_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -107,18 +114,18 @@
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Size:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Size:</dt>
|
||||||
<dd class="text-gray-900 dark:text-white font-mono">
|
<dd class="text-cool-steel-900 dark:text-white font-mono">
|
||||||
{format_bytes(@backup_b.config_size_bytes)}
|
{format_bytes(@backup_b.config_size_bytes)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">Source:</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">Source:</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
||||||
@backup_b.trigger_source == "daily_cron" &&
|
@backup_b.trigger_source == "daily_cron" &&
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900 dark:text-cerulean-200",
|
||||||
@backup_b.trigger_source == "manual" &&
|
@backup_b.trigger_source == "manual" &&
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
||||||
]}>
|
]}>
|
||||||
|
|
@ -140,37 +147,37 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 mb-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 mb-6">
|
||||||
<div class="flex items-center gap-6 text-sm">
|
<div class="flex items-center gap-6 text-sm">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block w-3 h-3 bg-green-500 rounded-sm"></span>
|
<span class="inline-block w-3 h-3 bg-green-500 rounded-sm"></span>
|
||||||
<span class="text-gray-600 dark:text-gray-400">
|
<span class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{@stats.additions} addition{if @stats.additions != 1, do: "s"}
|
{@stats.additions} addition{if @stats.additions != 1, do: "s"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block w-3 h-3 bg-red-500 rounded-sm"></span>
|
<span class="inline-block w-3 h-3 bg-sweet-salmon-500 rounded-sm"></span>
|
||||||
<span class="text-gray-600 dark:text-gray-400">
|
<span class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{@stats.deletions} deletion{if @stats.deletions != 1, do: "s"}
|
{@stats.deletions} deletion{if @stats.deletions != 1, do: "s"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="inline-block w-3 h-3 bg-yellow-500 rounded-sm"></span>
|
<span class="inline-block w-3 h-3 bg-wheat-500 rounded-sm"></span>
|
||||||
<span class="text-gray-600 dark:text-gray-400">
|
<span class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{@stats.changes} change{if @stats.changes != 1, do: "s"}
|
{@stats.changes} change{if @stats.changes != 1, do: "s"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Unified Diff Display -->
|
<!-- Unified Diff Display -->
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 overflow-hidden">
|
||||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-gray-800/75">
|
<div class="px-4 py-3 border-b border-cool-steel-200 dark:border-white/10 bg-cool-steel-50 dark:bg-cool-steel-800/75">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Configuration Differences")}
|
{t("Configuration Differences")}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<pre class="p-4 text-xs font-mono leading-relaxed"><code><%= for line <- String.split(@diff_output, "\n") do %><%= cond do %><% String.starts_with?(line, "+") && !String.starts_with?(line, "+++") -> %><span class="block bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200">{line}</span><% String.starts_with?(line, "-") && !String.starts_with?(line, "---") -> %><span class="block bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-200">{line}</span><% String.starts_with?(line, "@@") -> %><span class="block bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 font-semibold">{line}</span><% String.starts_with?(line, "---") || String.starts_with?(line, "+++") -> %><span class="block text-gray-500 dark:text-gray-500 italic">{line}</span><% true -> %><span class="block text-gray-700 dark:text-gray-300">{line}</span><% end %><% end %></code></pre>
|
<pre class="p-4 text-xs font-mono leading-relaxed"><code><%= for line <- String.split(@diff_output, "\n") do %><%= cond do %><% String.starts_with?(line, "+") && !String.starts_with?(line, "+++") -> %><span class="block bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200">{line}</span><% String.starts_with?(line, "-") && !String.starts_with?(line, "---") -> %><span class="block bg-sweet-salmon-100 dark:bg-sweet-salmon-900/30 text-sweet-salmon-800 dark:text-sweet-salmon-200">{line}</span><% String.starts_with?(line, "@@") -> %><span class="block bg-cerulean-50 dark:bg-cerulean-900/20 text-cerulean-700 dark:text-cerulean-300 font-semibold">{line}</span><% String.starts_with?(line, "---") || String.starts_with?(line, "+++") -> %><span class="block text-cool-steel-500 dark:text-cool-steel-500 italic">{line}</span><% true -> %><span class="block text-cool-steel-700 dark:text-cool-steel-300">{line}</span><% end %><% end %></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ defmodule ToweropsWeb.MobileQRLive do
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<div :if={!@completed} class="mt-8">
|
<div :if={!@completed} class="mt-8">
|
||||||
<div class="rounded-lg bg-white p-8 shadow-sm ring-1 ring-gray-200 dark:bg-gray-800/50 dark:ring-white/10">
|
<div class="rounded-lg bg-white p-8 shadow-sm ring-1 ring-cool-steel-200 dark:bg-cool-steel-800/50 dark:ring-white/10">
|
||||||
<div class="flex flex-col items-center space-y-6">
|
<div class="flex flex-col items-center space-y-6">
|
||||||
<div class="rounded-lg bg-white p-4">
|
<div class="rounded-lg bg-white p-4">
|
||||||
<img
|
<img
|
||||||
|
|
@ -143,64 +143,66 @@ defmodule ToweropsWeb.MobileQRLive do
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
This QR code expires in 5 minutes
|
This QR code expires in 5 minutes
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="h-2 w-2 animate-pulse rounded-full bg-blue-500"></div>
|
<div class="h-2 w-2 animate-pulse rounded-full bg-cerulean-500"></div>
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Waiting for mobile app to scan...
|
Waiting for mobile app to scan...
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 rounded-lg bg-white p-6 shadow-sm ring-1 ring-gray-200 dark:bg-gray-800/50 dark:ring-white/10">
|
<div class="mt-6 rounded-lg bg-white p-6 shadow-sm ring-1 ring-cool-steel-200 dark:bg-cool-steel-800/50 dark:ring-white/10">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
How to link your phone
|
How to link your phone
|
||||||
</h3>
|
</h3>
|
||||||
<ol class="mt-4 space-y-3">
|
<ol class="mt-4 space-y-3">
|
||||||
<li class="flex items-start gap-3">
|
<li class="flex items-start gap-3">
|
||||||
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-indigo-600 text-xs font-bold text-white">
|
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-cerulean-600 text-xs font-bold text-white">
|
||||||
1
|
1
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Open the <span class="font-medium text-gray-900 dark:text-white">Towerops</span>
|
Open the
|
||||||
|
<span class="font-medium text-cool-steel-900 dark:text-white">Towerops</span>
|
||||||
app on your iPhone
|
app on your iPhone
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-start gap-3">
|
<li class="flex items-start gap-3">
|
||||||
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-indigo-600 text-xs font-bold text-white">
|
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-cerulean-600 text-xs font-bold text-white">
|
||||||
2
|
2
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Tap <span class="font-medium text-gray-900 dark:text-white">"Scan QR Code"</span>
|
Tap
|
||||||
|
<span class="font-medium text-cool-steel-900 dark:text-white">"Scan QR Code"</span>
|
||||||
on the login screen
|
on the login screen
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-start gap-3">
|
<li class="flex items-start gap-3">
|
||||||
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-indigo-600 text-xs font-bold text-white">
|
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-cerulean-600 text-xs font-bold text-white">
|
||||||
3
|
3
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Point your camera at the QR code above
|
Point your camera at the QR code above
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/20">
|
<div class="mt-4 rounded-lg bg-cerulean-50 p-4 dark:bg-cerulean-900/20">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<.icon name="hero-information-circle" class="h-5 w-5 text-blue-400" />
|
<.icon name="hero-information-circle" class="h-5 w-5 text-cerulean-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-300">
|
<h3 class="text-sm font-medium text-cerulean-800 dark:text-cerulean-300">
|
||||||
Don't have the app yet?
|
Don't have the app yet?
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-blue-700 dark:text-blue-400">
|
<p class="mt-1 text-sm text-cerulean-700 dark:text-cerulean-400">
|
||||||
Download Towerops from the App Store on your iPhone.
|
Download Towerops from the App Store on your iPhone.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -224,23 +226,29 @@ defmodule ToweropsWeb.MobileQRLive do
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 w-full max-w-md rounded-lg bg-white p-4 dark:bg-gray-800/50">
|
<div class="mt-4 w-full max-w-md rounded-lg bg-white p-4 dark:bg-cool-steel-800/50">
|
||||||
<dl class="space-y-2 text-sm">
|
<dl class="space-y-2 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<dt class="font-medium text-gray-700 dark:text-gray-300">Device Name</dt>
|
<dt class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white">
|
Device Name
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-cool-steel-900 dark:text-white">
|
||||||
{@mobile_session.device_name || "Unknown"}
|
{@mobile_session.device_name || "Unknown"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="font-medium text-gray-700 dark:text-gray-300">Device OS</dt>
|
<dt class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white">
|
Device OS
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-cool-steel-900 dark:text-white">
|
||||||
{@mobile_session.device_os || "Unknown"}
|
{@mobile_session.device_os || "Unknown"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="font-medium text-gray-700 dark:text-gray-300">App Version</dt>
|
<dt class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<dd class="mt-1 text-gray-900 dark:text-white">
|
App Version
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-cool-steel-900 dark:text-white">
|
||||||
{@mobile_session.app_version || "Unknown"}
|
{@mobile_session.app_version || "Unknown"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -249,7 +257,7 @@ defmodule ToweropsWeb.MobileQRLive do
|
||||||
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/settings"}
|
navigate={~p"/users/settings"}
|
||||||
class="text-sm font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400"
|
class="text-sm font-medium text-cerulean-600 hover:text-cerulean-500 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
Manage mobile sessions →
|
Manage mobile sessions →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -260,7 +268,7 @@ defmodule ToweropsWeb.MobileQRLive do
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs"}
|
navigate={~p"/orgs"}
|
||||||
class="text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="text-sm font-medium text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
← Back to Organizations
|
← Back to Organizations
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@
|
||||||
<.header>
|
<.header>
|
||||||
<span class="flex items-center gap-2">
|
<span class="flex items-center gap-2">
|
||||||
{t("Network Map")}
|
{t("Network Map")}
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-50 px-2 py-0.5 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">
|
<span class="inline-flex items-center rounded-full bg-cerulean-50 px-2 py-0.5 text-xs font-medium text-cerulean-700 ring-1 ring-inset ring-cerulean-700/10 dark:bg-cerulean-400/10 dark:text-cerulean-400 dark:ring-cerulean-400/30">
|
||||||
{t("Experimental")}
|
{t("Experimental")}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<:subtitle>{t("Visual topology of your network infrastructure")}</:subtitle>
|
<:subtitle>{t("Visual topology of your network infrastructure")}</:subtitle>
|
||||||
<:actions>
|
<:actions>
|
||||||
<div class="flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-500 font-mono">
|
<div class="flex items-center gap-1.5 text-xs text-cool-steel-400 dark:text-cool-steel-500 font-mono">
|
||||||
<span class="relative flex h-2 w-2">
|
<span class="relative flex h-2 w-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75">
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -25,16 +25,16 @@
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
<div class="border-b border-gray-200 dark:border-white/10 mb-6">
|
<div class="border-b border-cool-steel-200 dark:border-white/10 mb-6">
|
||||||
<nav class="-mb-px flex space-x-8">
|
<nav class="-mb-px flex space-x-8">
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/network-map?tab=added"}
|
patch={~p"/network-map?tab=added"}
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
||||||
if @active_tab == "added" do
|
if @active_tab == "added" do
|
||||||
"border-blue-500 text-blue-600 dark:text-blue-400"
|
"border-cerulean-500 text-cerulean-600 dark:text-cerulean-400"
|
||||||
else
|
else
|
||||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:text-cool-steel-700 hover:border-cool-steel-300 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -46,14 +46,14 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
||||||
if @active_tab == "all" do
|
if @active_tab == "all" do
|
||||||
"border-blue-500 text-blue-600 dark:text-blue-400"
|
"border-cerulean-500 text-cerulean-600 dark:text-cerulean-400"
|
||||||
else
|
else
|
||||||
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:text-cool-steel-700 hover:border-cool-steel-300 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
end
|
end
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("All Devices")}
|
{t("All Devices")}
|
||||||
<span class="ml-2 inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
<span class="ml-2 inline-flex items-center rounded-full bg-cool-steel-100 px-2 py-0.5 text-xs font-medium text-cool-steel-700 dark:bg-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@topology.stats.discovered_devices}
|
{@topology.stats.discovered_devices}
|
||||||
</span>
|
</span>
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -63,25 +63,27 @@
|
||||||
<%= if @loading do %>
|
<%= if @loading do %>
|
||||||
<div class="flex items-center justify-center h-96">
|
<div class="flex items-center justify-center h-96">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon name="hero-arrow-path" class="h-12 w-12 text-gray-400 animate-spin mx-auto" />
|
<.icon name="hero-arrow-path" class="h-12 w-12 text-cool-steel-400 animate-spin mx-auto" />
|
||||||
<p class="mt-4 text-sm text-gray-600 dark:text-gray-400">Loading network topology...</p>
|
<p class="mt-4 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Loading network topology...
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<!-- Stats Bar -->
|
<!-- Stats Bar -->
|
||||||
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
||||||
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800 overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<.icon name="hero-server" class="h-6 w-6 text-gray-400" />
|
<.icon name="hero-server" class="h-6 w-6 text-cool-steel-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{t("Total Devices")}
|
{t("Total Devices")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-medium text-gray-900 dark:text-white">
|
<dd class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@topology.stats.total_devices}
|
{@topology.stats.total_devices}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
@ -90,7 +92,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800 overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
|
|
@ -98,10 +100,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{t("Added Devices")}
|
{t("Added Devices")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-medium text-gray-900 dark:text-white">
|
<dd class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@topology.stats.added_devices}
|
{@topology.stats.added_devices}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
@ -110,18 +112,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800 overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<.icon name="hero-magnifying-glass" class="h-6 w-6 text-blue-400" />
|
<.icon name="hero-magnifying-glass" class="h-6 w-6 text-cerulean-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{t("Discovered")}
|
{t("Discovered")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-medium text-gray-900 dark:text-white">
|
<dd class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@topology.stats.discovered_devices}
|
{@topology.stats.discovered_devices}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
@ -130,7 +132,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800 overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
|
|
@ -138,10 +140,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
<dt class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{t("Connections")}
|
{t("Connections")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-medium text-gray-900 dark:text-white">
|
<dd class="text-lg font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@topology.stats.total_links}
|
{@topology.stats.total_links}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
@ -152,7 +154,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter Bar -->
|
<!-- Filter Bar -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 mb-4 p-3">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-white/10 mb-4 p-3">
|
||||||
<div class="flex items-center justify-between gap-4 flex-wrap">
|
<div class="flex items-center justify-between gap-4 flex-wrap">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
|
@ -161,8 +163,10 @@
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
||||||
if(@filter == "all",
|
if(@filter == "all",
|
||||||
do: "bg-blue-100 text-blue-700 dark:bg-blue-400/20 dark:text-blue-400",
|
do:
|
||||||
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
"bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-400/20 dark:text-cerulean-400",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-600 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -174,14 +178,15 @@
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
||||||
if(@filter == "degraded",
|
if(@filter == "degraded",
|
||||||
do: "bg-yellow-100 text-yellow-700 dark:bg-yellow-400/20 dark:text-yellow-400",
|
do: "bg-wheat-100 text-wheat-700 dark:bg-wheat-400/20 dark:text-wheat-400",
|
||||||
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
else:
|
||||||
|
"text-cool-steel-600 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Degraded Only
|
Degraded Only
|
||||||
<%= if @topology.stats[:degraded_links] && @topology.stats.degraded_links > 0 do %>
|
<%= if @topology.stats[:degraded_links] && @topology.stats.degraded_links > 0 do %>
|
||||||
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-yellow-800 bg-yellow-200 rounded-full dark:bg-yellow-900 dark:text-yellow-300">
|
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-wheat-800 bg-wheat-200 rounded-full dark:bg-wheat-900 dark:text-wheat-300">
|
||||||
{@topology.stats.degraded_links}
|
{@topology.stats.degraded_links}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -192,14 +197,16 @@
|
||||||
class={[
|
class={[
|
||||||
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
"px-3 py-1.5 text-xs font-medium rounded-md transition-colors",
|
||||||
if(@filter == "alerts",
|
if(@filter == "alerts",
|
||||||
do: "bg-red-100 text-red-700 dark:bg-red-400/20 dark:text-red-400",
|
do:
|
||||||
else: "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
"bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-400/20 dark:text-sweet-salmon-400",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-600 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Sites with Alerts
|
Sites with Alerts
|
||||||
<%= if @topology.stats[:sites_with_alerts] && @topology.stats.sites_with_alerts > 0 do %>
|
<%= if @topology.stats[:sites_with_alerts] && @topology.stats.sites_with_alerts > 0 do %>
|
||||||
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-red-800 bg-red-200 rounded-full dark:bg-red-900 dark:text-red-300">
|
<span class="ml-1 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-sweet-salmon-800 bg-sweet-salmon-200 rounded-full dark:bg-sweet-salmon-900 dark:text-sweet-salmon-300">
|
||||||
{@topology.stats.sites_with_alerts}
|
{@topology.stats.sites_with_alerts}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -209,7 +216,7 @@
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-magnifying-glass"
|
name="hero-magnifying-glass"
|
||||||
class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400"
|
class="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -218,19 +225,21 @@
|
||||||
phx-key="Enter"
|
phx-key="Enter"
|
||||||
phx-debounce="300"
|
phx-debounce="300"
|
||||||
value={@search_query}
|
value={@search_query}
|
||||||
class="pl-8 pr-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 w-48"
|
class="pl-8 pr-3 py-1.5 text-xs border border-cool-steel-300 dark:border-cool-steel-600 rounded-md bg-white dark:bg-cool-steel-700 text-cool-steel-900 dark:text-white placeholder-cool-steel-400 focus:ring-1 focus:ring-cerulean-500 focus:border-cerulean-500 w-48"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<%= if @topology[:has_geo] do %>
|
<%= if @topology[:has_geo] do %>
|
||||||
<div class="flex items-center gap-1 border-l border-gray-200 dark:border-gray-600 pl-3">
|
<div class="flex items-center gap-1 border-l border-cool-steel-200 dark:border-cool-steel-600 pl-3">
|
||||||
<button
|
<button
|
||||||
phx-click="toggle_layout"
|
phx-click="toggle_layout"
|
||||||
phx-value-mode="force"
|
phx-value-mode="force"
|
||||||
class={[
|
class={[
|
||||||
"px-2 py-1 text-xs rounded transition-colors",
|
"px-2 py-1 text-xs rounded transition-colors",
|
||||||
if(@layout_mode == "force",
|
if(@layout_mode == "force",
|
||||||
do: "bg-gray-200 dark:bg-gray-600 text-gray-900 dark:text-white font-medium",
|
do:
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700"
|
"bg-cool-steel-200 dark:bg-cool-steel-600 text-cool-steel-900 dark:text-white font-medium",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
title="Force-directed layout"
|
title="Force-directed layout"
|
||||||
|
|
@ -243,8 +252,10 @@
|
||||||
class={[
|
class={[
|
||||||
"px-2 py-1 text-xs rounded transition-colors",
|
"px-2 py-1 text-xs rounded transition-colors",
|
||||||
if(@layout_mode == "geo",
|
if(@layout_mode == "geo",
|
||||||
do: "bg-gray-200 dark:bg-gray-600 text-gray-900 dark:text-white font-medium",
|
do:
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700"
|
"bg-cool-steel-200 dark:bg-cool-steel-600 text-cool-steel-900 dark:text-white font-medium",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:hover:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
title="Geographic layout"
|
title="Geographic layout"
|
||||||
|
|
@ -258,19 +269,19 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Network Map Container -->
|
<!-- Network Map Container -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-white/10 overflow-hidden">
|
||||||
<div class="p-4 border-b border-gray-200 dark:border-white/10">
|
<div class="p-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{t("Topology Visualization")}
|
{t("Topology Visualization")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Click and drag to pan, scroll to zoom, click nodes for details")}
|
{t("Click and drag to pan, scroll to zoom, click nodes for details")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Last updated: <.timestamp datetime={@topology.last_updated} timezone={@timezone} />
|
Last updated: <.timestamp datetime={@topology.last_updated} timezone={@timezone} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -295,7 +306,7 @@
|
||||||
id="cy-zoom-in"
|
id="cy-zoom-in"
|
||||||
type="button"
|
type="button"
|
||||||
title="Zoom in"
|
title="Zoom in"
|
||||||
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
|
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-cool-steel-700 border border-cool-steel-200 dark:border-white/10 rounded shadow-sm text-cool-steel-600 dark:text-cool-steel-300 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-600 hover:text-cool-steel-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -303,7 +314,7 @@
|
||||||
id="cy-zoom-out"
|
id="cy-zoom-out"
|
||||||
type="button"
|
type="button"
|
||||||
title="Zoom out"
|
title="Zoom out"
|
||||||
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
|
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-cool-steel-700 border border-cool-steel-200 dark:border-white/10 rounded shadow-sm text-cool-steel-600 dark:text-cool-steel-300 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-600 hover:text-cool-steel-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-minus" class="h-4 w-4" />
|
<.icon name="hero-minus" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -311,7 +322,7 @@
|
||||||
id="cy-fit"
|
id="cy-fit"
|
||||||
type="button"
|
type="button"
|
||||||
title="Fit to screen"
|
title="Fit to screen"
|
||||||
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-gray-700 border border-gray-200 dark:border-white/10 rounded shadow-sm text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors"
|
class="flex items-center justify-center w-8 h-8 bg-white dark:bg-cool-steel-700 border border-cool-steel-200 dark:border-white/10 rounded shadow-sm text-cool-steel-600 dark:text-cool-steel-300 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-600 hover:text-cool-steel-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrows-pointing-out" class="h-4 w-4" />
|
<.icon name="hero-arrows-pointing-out" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -321,18 +332,18 @@
|
||||||
<%= if @selected_node_detail do %>
|
<%= if @selected_node_detail do %>
|
||||||
<div
|
<div
|
||||||
id="node-detail-panel"
|
id="node-detail-panel"
|
||||||
class="absolute top-0 right-0 h-full w-80 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-white/10 shadow-lg overflow-y-auto transition-transform duration-200"
|
class="absolute top-0 right-0 h-full w-80 bg-white dark:bg-cool-steel-800 border-l border-cool-steel-200 dark:border-white/10 shadow-lg overflow-y-auto transition-transform duration-200"
|
||||||
>
|
>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Node Details")}
|
{t("Node Details")}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
id="close-detail-panel"
|
id="close-detail-panel"
|
||||||
phx-click="close_detail_panel"
|
phx-click="close_detail_panel"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-5 w-5" />
|
<.icon name="hero-x-mark" class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -341,11 +352,11 @@
|
||||||
<!-- Device Info -->
|
<!-- Device Info -->
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-base font-medium text-gray-900 dark:text-white">
|
<h4 class="text-base font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@selected_node_detail.name}
|
{@selected_node_detail.name}
|
||||||
</h4>
|
</h4>
|
||||||
<%= if @selected_node_detail.type == :discovered do %>
|
<%= if @selected_node_detail.type == :discovered do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300 mt-1">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2 py-0.5 text-xs font-medium text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300 mt-1">
|
||||||
{t("Discovered")}
|
{t("Discovered")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -353,8 +364,11 @@
|
||||||
|
|
||||||
<%= if @selected_node_detail.ip_address do %>
|
<%= if @selected_node_detail.ip_address do %>
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<.icon name="hero-globe-alt" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
|
<.icon
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
name="hero-globe-alt"
|
||||||
|
class="h-4 w-4 text-cool-steel-400 mr-2 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@selected_node_detail.ip_address}
|
{@selected_node_detail.ip_address}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -362,8 +376,11 @@
|
||||||
|
|
||||||
<%= if @selected_node_detail.site_name do %>
|
<%= if @selected_node_detail.site_name do %>
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<.icon name="hero-map-pin" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
|
<.icon
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
name="hero-map-pin"
|
||||||
|
class="h-4 w-4 text-cool-steel-400 mr-2 flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@selected_node_detail.site_name}
|
{@selected_node_detail.site_name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -373,17 +390,17 @@
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-building-office"
|
name="hero-building-office"
|
||||||
class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0"
|
class="h-4 w-4 text-cool-steel-400 mr-2 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-700 dark:text-gray-300">
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@selected_node_detail.manufacturer}
|
{@selected_node_detail.manufacturer}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<.icon name="hero-tag" class="h-4 w-4 text-gray-400 mr-2 flex-shrink-0" />
|
<.icon name="hero-tag" class="h-4 w-4 text-cool-steel-400 mr-2 flex-shrink-0" />
|
||||||
<span class="text-gray-700 dark:text-gray-300 capitalize">
|
<span class="text-cool-steel-700 dark:text-cool-steel-300 capitalize">
|
||||||
{@selected_node_detail.role}
|
{@selected_node_detail.role}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -394,11 +411,11 @@
|
||||||
"inline-block h-2 w-2 rounded-full mr-2",
|
"inline-block h-2 w-2 rounded-full mr-2",
|
||||||
if(@selected_node_detail.status == :up,
|
if(@selected_node_detail.status == :up,
|
||||||
do: "bg-green-400",
|
do: "bg-green-400",
|
||||||
else: "bg-red-400"
|
else: "bg-sweet-salmon-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-gray-700 dark:text-gray-300 capitalize">
|
<span class="text-cool-steel-700 dark:text-cool-steel-300 capitalize">
|
||||||
{@selected_node_detail.status}
|
{@selected_node_detail.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -407,29 +424,33 @@
|
||||||
|
|
||||||
<!-- Wireless Stats (for APs, CPEs, backhaul radios) -->
|
<!-- Wireless Stats (for APs, CPEs, backhaul radios) -->
|
||||||
<%= if @selected_node_detail[:is_wireless] do %>
|
<%= if @selected_node_detail[:is_wireless] do %>
|
||||||
<div class="mt-4 pt-3 border-t border-gray-200 dark:border-white/10">
|
<div class="mt-4 pt-3 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<h4 class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<h4 class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-2">
|
||||||
RF Stats
|
RF Stats
|
||||||
</h4>
|
</h4>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<!-- Client count -->
|
<!-- Client count -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-700/50 rounded-md p-2">
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400">Clients</div>
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<div class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-1">
|
Clients
|
||||||
|
</div>
|
||||||
|
<div class="text-lg font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1">
|
||||||
<.icon name="hero-wifi" class="h-4 w-4 text-purple-500" />
|
<.icon name="hero-wifi" class="h-4 w-4 text-purple-500" />
|
||||||
{@selected_node_detail[:client_count] || 0}
|
{@selected_node_detail[:client_count] || 0}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Signal health -->
|
<!-- Signal health -->
|
||||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-700/50 rounded-md p-2">
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400">Signal</div>
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Signal
|
||||||
|
</div>
|
||||||
<div class={[
|
<div class={[
|
||||||
"text-sm font-semibold capitalize",
|
"text-sm font-semibold capitalize",
|
||||||
case @selected_node_detail[:signal_health] do
|
case @selected_node_detail[:signal_health] do
|
||||||
"good" -> "text-green-600 dark:text-green-400"
|
"good" -> "text-green-600 dark:text-green-400"
|
||||||
"degraded" -> "text-yellow-600 dark:text-yellow-400"
|
"degraded" -> "text-wheat-600 dark:text-wheat-400"
|
||||||
"critical" -> "text-red-600 dark:text-red-400"
|
"critical" -> "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
_ -> "text-gray-500 dark:text-gray-400"
|
_ -> "text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{@selected_node_detail[:signal_health] || "N/A"}
|
{@selected_node_detail[:signal_health] || "N/A"}
|
||||||
|
|
@ -437,8 +458,10 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- SNR -->
|
<!-- SNR -->
|
||||||
<%= if @selected_node_detail[:snr] do %>
|
<%= if @selected_node_detail[:snr] do %>
|
||||||
<div class="bg-gray-50 dark:bg-gray-700/50 rounded-md p-2 col-span-2">
|
<div class="bg-cool-steel-50 dark:bg-cool-steel-700/50 rounded-md p-2 col-span-2">
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400">SNR</div>
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
SNR
|
||||||
|
</div>
|
||||||
<div class={[
|
<div class={[
|
||||||
"text-sm font-semibold",
|
"text-sm font-semibold",
|
||||||
cond do
|
cond do
|
||||||
|
|
@ -446,10 +469,10 @@
|
||||||
"text-green-600 dark:text-green-400"
|
"text-green-600 dark:text-green-400"
|
||||||
|
|
||||||
@selected_node_detail.snr >= 15 ->
|
@selected_node_detail.snr >= 15 ->
|
||||||
"text-yellow-600 dark:text-yellow-400"
|
"text-wheat-600 dark:text-wheat-400"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
"text-red-600 dark:text-red-400"
|
"text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{@selected_node_detail.snr} dB
|
{@selected_node_detail.snr} dB
|
||||||
|
|
@ -462,33 +485,33 @@
|
||||||
|
|
||||||
<!-- Connections -->
|
<!-- Connections -->
|
||||||
<%= if length(@selected_node_detail.connections) > 0 do %>
|
<%= if length(@selected_node_detail.connections) > 0 do %>
|
||||||
<div class="mt-5 pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="mt-5 pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<h4 class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
|
<h4 class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-3">
|
||||||
{t("Connections")} ({length(@selected_node_detail.connections)})
|
{t("Connections")} ({length(@selected_node_detail.connections)})
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= for conn <- @selected_node_detail.connections do %>
|
<%= for conn <- @selected_node_detail.connections do %>
|
||||||
<div class="flex items-center justify-between py-1.5 px-2 rounded bg-gray-50 dark:bg-gray-700/50 text-sm">
|
<div class="flex items-center justify-between py-1.5 px-2 rounded bg-cool-steel-50 dark:bg-cool-steel-700/50 text-sm">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<p class="text-gray-900 dark:text-white truncate font-medium text-xs">
|
<p class="text-cool-steel-900 dark:text-white truncate font-medium text-xs">
|
||||||
{conn.device_name}
|
{conn.device_name}
|
||||||
</p>
|
</p>
|
||||||
<%= if conn.interface do %>
|
<%= if conn.interface do %>
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-xs">
|
<p class="text-cool-steel-500 dark:text-cool-steel-400 text-xs">
|
||||||
{conn.interface}
|
{conn.interface}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2 ml-2 flex-shrink-0">
|
<div class="flex items-center space-x-2 ml-2 flex-shrink-0">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 uppercase">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400 uppercase">
|
||||||
{conn.link_type}
|
{conn.link_type}
|
||||||
</span>
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"text-xs font-medium",
|
"text-xs font-medium",
|
||||||
cond do
|
cond do
|
||||||
conn.confidence > 0.9 -> "text-green-600 dark:text-green-400"
|
conn.confidence > 0.9 -> "text-green-600 dark:text-green-400"
|
||||||
conn.confidence >= 0.5 -> "text-yellow-600 dark:text-yellow-400"
|
conn.confidence >= 0.5 -> "text-wheat-600 dark:text-wheat-400"
|
||||||
true -> "text-gray-500 dark:text-gray-400"
|
true -> "text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{trunc(conn.confidence * 100)}%
|
{trunc(conn.confidence * 100)}%
|
||||||
|
|
@ -505,7 +528,7 @@
|
||||||
<div class="mt-4 space-y-2">
|
<div class="mt-4 space-y-2">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@selected_node_detail.id}"}
|
navigate={~p"/devices/#{@selected_node_detail.id}"}
|
||||||
class="flex items-center justify-center w-full px-3 py-2 text-sm font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-400/10 rounded-md hover:bg-blue-100 dark:hover:bg-blue-400/20"
|
class="flex items-center justify-center w-full px-3 py-2 text-sm font-medium text-cerulean-600 dark:text-cerulean-400 bg-cerulean-50 dark:bg-cerulean-400/10 rounded-md hover:bg-cerulean-100 dark:hover:bg-cerulean-400/20"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-top-right-on-square" class="h-4 w-4 mr-1.5" />
|
<.icon name="hero-arrow-top-right-on-square" class="h-4 w-4 mr-1.5" />
|
||||||
{t("View Device")}
|
{t("View Device")}
|
||||||
|
|
@ -529,18 +552,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Legend -->
|
<!-- Legend -->
|
||||||
<div class="p-4 border-t border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-gray-900">
|
<div class="p-4 border-t border-cool-steel-200 dark:border-white/10 bg-cool-steel-50 dark:bg-cool-steel-900">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<%!-- Device roles --%>
|
<%!-- Device roles --%>
|
||||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
||||||
<span class="font-medium text-gray-600 dark:text-gray-400">Roles:</span>
|
<span class="font-medium text-cool-steel-600 dark:text-cool-steel-400">Roles:</span>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3.5 h-3 rounded-sm" style="background-color: #3B82F6;"></div>
|
<div class="w-3.5 h-3 rounded-sm" style="background-color: #3B82F6;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Router</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Router</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3 h-3 rotate-45" style="background-color: #10B981;"></div>
|
<div class="w-3 h-3 rotate-45" style="background-color: #10B981;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Switch</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Switch</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div
|
<div
|
||||||
|
|
@ -548,57 +571,59 @@
|
||||||
style="border-bottom-color: #8B5CF6;"
|
style="border-bottom-color: #8B5CF6;"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">AP / Wireless</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">AP / Wireless</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3 h-3 rounded-full" style="background-color: #EF4444;"></div>
|
<div class="w-3 h-3 rounded-full" style="background-color: #EF4444;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Firewall</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Firewall</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3 h-3" style="background-color: #F59E0B;"></div>
|
<div class="w-3 h-3" style="background-color: #F59E0B;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Server</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Server</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3 h-3 rounded-full bg-gray-400"></div>
|
<div class="w-3 h-3 rounded-full bg-cool-steel-400"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Unknown</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Unknown</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-3 h-3 rounded-full border-2 border-dashed border-gray-400 opacity-60">
|
<div class="w-3 h-3 rounded-full border-2 border-dashed border-cool-steel-400 opacity-60">
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Discovered</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Discovered</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%!-- RF Link health legend hidden (TOW-44) — keep code intact
|
<%!-- RF Link health legend hidden (TOW-44) — keep code intact
|
||||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
||||||
<span class="font-medium text-gray-600 dark:text-gray-400">RF Links:</span>
|
<span class="font-medium text-cool-steel-600 dark:text-cool-steel-400">RF Links:</span>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-6 h-1 rounded" style="background-color: #10B981;"></div>
|
<div class="w-6 h-1 rounded" style="background-color: #10B981;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Good signal</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Good signal</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-6 h-1 rounded" style="background-color: #F59E0B;"></div>
|
<div class="w-6 h-1 rounded" style="background-color: #F59E0B;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Degraded</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Degraded</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-6 h-1 rounded" style="background-color: #EF4444;"></div>
|
<div class="w-6 h-1 rounded" style="background-color: #EF4444;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">Critical</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">Critical</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
--%>
|
--%>
|
||||||
<%!-- Link types --%>
|
<%!-- Link types --%>
|
||||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
<div class="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs">
|
||||||
<span class="font-medium text-gray-600 dark:text-gray-400">Link types:</span>
|
<span class="font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Link types:
|
||||||
|
</span>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-6 h-0.5" style="background-color: #6B7280;"></div>
|
<div class="w-6 h-0.5" style="background-color: #6B7280;"></div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">LLDP/CDP</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">LLDP/CDP</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<div class="w-6 h-0.5 border-t-2 border-dashed" style="border-color: #6B7280;">
|
<div class="w-6 h-0.5 border-t-2 border-dashed" style="border-color: #6B7280;">
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 dark:text-gray-300">MAC/ARP inferred</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300">MAC/ARP inferred</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-1.5">
|
<div class="flex items-center space-x-1.5">
|
||||||
<span class="text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Line thickness = bandwidth capacity
|
Line thickness = bandwidth capacity
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -610,14 +635,17 @@
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<%= if @topology.stats.total_devices == 0 do %>
|
<%= if @topology.stats.total_devices == 0 do %>
|
||||||
<div class="text-center py-16">
|
<div class="text-center py-16">
|
||||||
<.icon name="hero-map" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
name="hero-map"
|
||||||
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No network topology available")}
|
{t("No network topology available")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Add devices with SNMP enabled to start building your network map.")}
|
{t("Add devices with SNMP enabled to start building your network map.")}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("The topology is automatically discovered via LLDP and CDP protocols.")}
|
{t("The topology is automatically discovered via LLDP and CDP protocols.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@
|
||||||
class={[
|
class={[
|
||||||
"flex items-center gap-1.5 text-sm font-medium px-2 py-1 rounded transition",
|
"flex items-center gap-1.5 text-sm font-medium px-2 py-1 rounded transition",
|
||||||
if(step == @step,
|
if(step == @step,
|
||||||
do: "text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/30",
|
do:
|
||||||
|
"text-cerulean-600 dark:text-cerulean-400 bg-cerulean-50 dark:bg-cerulean-900/30",
|
||||||
else:
|
else:
|
||||||
if(step_index(step) < step_index(@step),
|
if(step_index(step) < step_index(@step),
|
||||||
do:
|
do:
|
||||||
"text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white",
|
"text-cool-steel-600 dark:text-cool-steel-300 hover:text-cool-steel-900 dark:hover:text-white",
|
||||||
else: "text-gray-400 dark:text-gray-500"
|
else: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
|
|
@ -28,11 +29,12 @@
|
||||||
<span class={[
|
<span class={[
|
||||||
"flex items-center justify-center w-5 h-5 rounded-full text-xs font-bold",
|
"flex items-center justify-center w-5 h-5 rounded-full text-xs font-bold",
|
||||||
if(step == @step,
|
if(step == @step,
|
||||||
do: "bg-indigo-600 text-white dark:bg-indigo-500",
|
do: "bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
else:
|
else:
|
||||||
if(step_index(step) < step_index(@step),
|
if(step_index(step) < step_index(@step),
|
||||||
do: "bg-gray-400 dark:bg-gray-500 text-white",
|
do: "bg-cool-steel-400 dark:bg-cool-steel-500 text-white",
|
||||||
else: "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
|
else:
|
||||||
|
"bg-cool-steel-200 dark:bg-cool-steel-700 text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
|
|
@ -47,7 +49,7 @@
|
||||||
<%= if step != List.last(@steps) do %>
|
<%= if step != List.last(@steps) do %>
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-chevron-right-mini"
|
name="hero-chevron-right-mini"
|
||||||
class="h-4 w-4 text-gray-300 dark:text-gray-600"
|
class="h-4 w-4 text-cool-steel-300 dark:text-cool-steel-600"
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -56,13 +58,13 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<%!-- Step content --%>
|
<%!-- Step content --%>
|
||||||
<div class="bg-white dark:bg-white/5 rounded-lg border border-gray-200 dark:border-white/10 p-6">
|
<div class="bg-white dark:bg-white/5 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6">
|
||||||
<%= case @step do %>
|
<%= case @step do %>
|
||||||
<% :snmp -> %>
|
<% :snmp -> %>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-1">
|
||||||
{t("SNMP Configuration")}
|
{t("SNMP Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-6">
|
||||||
{t("Set organization-wide SNMP defaults. Can be overridden per-site or per-device.")}
|
{t("Set organization-wide SNMP defaults. Can be overridden per-site or per-device.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -116,17 +118,17 @@
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="skip"
|
phx-click="skip"
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t("Skip")}
|
{t("Skip")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Save & Continue")}
|
{t("Save & Continue")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -134,10 +136,10 @@
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
<% :site -> %>
|
<% :site -> %>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-1">
|
||||||
{t("Add Your First Site")}
|
{t("Add Your First Site")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-6">
|
||||||
{t("Sites represent physical locations — tower sites, POPs, data centers, etc.")}
|
{t("Sites represent physical locations — tower sites, POPs, data centers, etc.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -173,17 +175,17 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="skip"
|
phx-click="skip"
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t("Skip")}
|
{t("Skip")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Create Site & Continue")}
|
{t("Create Site & Continue")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -191,10 +193,10 @@
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
<% :billing -> %>
|
<% :billing -> %>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-1">
|
||||||
{t("Connect a Billing Platform")}
|
{t("Connect a Billing Platform")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-6">
|
||||||
{t(
|
{t(
|
||||||
"Optional. Sync subscriber data for outage impact analysis and inventory reconciliation."
|
"Optional. Sync subscriber data for outage impact analysis and inventory reconciliation."
|
||||||
)}
|
)}
|
||||||
|
|
@ -207,10 +209,13 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="select_provider"
|
phx-click="select_provider"
|
||||||
phx-value-provider={provider.id}
|
phx-value-provider={provider.id}
|
||||||
class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-white/10 hover:border-indigo-400 dark:hover:border-indigo-500 hover:bg-gray-50 dark:hover:bg-white/5 transition text-left"
|
class="flex items-center gap-3 p-4 rounded-lg border border-cool-steel-200 dark:border-white/10 hover:border-cerulean-400 dark:hover:border-cerulean-500 hover:bg-cool-steel-50 dark:hover:bg-white/5 transition text-left"
|
||||||
>
|
>
|
||||||
<.icon name={provider.icon} class="h-6 w-6 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
name={provider.icon}
|
||||||
|
class="h-6 w-6 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{provider.name}
|
{provider.name}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -222,7 +227,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="select_provider"
|
phx-click="select_provider"
|
||||||
phx-value-provider=""
|
phx-value-provider=""
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
← {t("Back to providers")}
|
← {t("Back to providers")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -273,17 +278,17 @@
|
||||||
/>
|
/>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="skip"
|
phx-click="skip"
|
||||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t("Skip")}
|
{t("Skip")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Save & Continue")}
|
{t("Save & Continue")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -293,21 +298,21 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @selected_provider == nil do %>
|
<%= if @selected_provider == nil do %>
|
||||||
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="flex items-center justify-between pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="skip"
|
phx-click="skip"
|
||||||
class="text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
|
class="text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400 hover:text-cool-steel-700 dark:hover:text-cool-steel-200"
|
||||||
>
|
>
|
||||||
{t("Skip — I'll set this up later")}
|
{t("Skip — I'll set this up later")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% :agent -> %>
|
<% :agent -> %>
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-1">
|
||||||
{t("Deploy Agent")}
|
{t("Deploy Agent")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-6">
|
||||||
{t(
|
{t(
|
||||||
"The TowerOps agent polls your network devices via SNMP. Deploy it on a host with network access to your infrastructure."
|
"The TowerOps agent polls your network devices via SNMP. Deploy it on a host with network access to your infrastructure."
|
||||||
)}
|
)}
|
||||||
|
|
@ -316,20 +321,20 @@
|
||||||
<%= if @agent_token do %>
|
<%= if @agent_token do %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= if @agent_token_value do %>
|
<%= if @agent_token_value do %>
|
||||||
<div class="rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-3">
|
<div class="rounded-md bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 p-3">
|
||||||
<p class="text-sm text-amber-800 dark:text-amber-200">
|
<p class="text-sm text-wheat-800 dark:text-wheat-200">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline -mt-0.5" />
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline -mt-0.5" />
|
||||||
{t("Save this token — it won't be shown again.")}
|
{t("Save this token — it won't be shown again.")}
|
||||||
</p>
|
</p>
|
||||||
<code class="mt-2 block text-xs font-mono bg-amber-100 dark:bg-amber-900/40 rounded p-2 break-all select-all text-amber-900 dark:text-amber-100">
|
<code class="mt-2 block text-xs font-mono bg-wheat-100 dark:bg-wheat-900/40 rounded p-2 break-all select-all text-wheat-900 dark:text-wheat-100">
|
||||||
{@agent_token_value}
|
{@agent_token_value}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="rounded-md bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 p-3">
|
<div class="rounded-md bg-cool-steel-50 dark:bg-white/5 border border-cool-steel-200 dark:border-white/10 p-3">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Agent:")}
|
{t("Agent:")}
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@agent_token.name}
|
{@agent_token.name}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -337,10 +342,10 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Docker")}
|
{t("Docker")}
|
||||||
</h3>
|
</h3>
|
||||||
<pre class="text-xs font-mono bg-gray-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>docker run -d \
|
<pre class="text-xs font-mono bg-cool-steel-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>docker run -d \
|
||||||
--name towerops-agent \
|
--name towerops-agent \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
-e TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
|
-e TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
|
||||||
|
|
@ -350,26 +355,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Systemd")}
|
{t("Systemd")}
|
||||||
</h3>
|
</h3>
|
||||||
<pre class="text-xs font-mono bg-gray-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>curl -sSL https://install.towerops.net | \
|
<pre class="text-xs font-mono bg-cool-steel-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>curl -sSL https://install.towerops.net | \
|
||||||
TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
|
TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
|
||||||
TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
|
TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
|
||||||
bash</code></pre>
|
bash</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Failed to create agent token. You can create one later in Settings → Agents.")}
|
{t("Failed to create agent token. You can create one later in Settings → Agents.")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center justify-end pt-6 border-t border-gray-200 dark:border-white/10 mt-6">
|
<div class="flex items-center justify-end pt-6 border-t border-cool-steel-200 dark:border-white/10 mt-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="finish"
|
phx-click="finish"
|
||||||
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Done — Go to Dashboard")}
|
{t("Done — Go to Dashboard")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@
|
||||||
current_scope={@current_scope}
|
current_scope={@current_scope}
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<.breadcrumb items={[
|
<.breadcrumb items={[
|
||||||
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
||||||
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
||||||
%{label: "Integrations", navigate: ~p"/orgs/#{@organization.slug}/settings/integrations"},
|
%{label: "Integrations", navigate: ~p"/orgs/#{@organization.slug}/settings/integrations"},
|
||||||
%{label: "Gaiia"}
|
%{label: "Gaiia"}
|
||||||
]} />
|
]} />
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Link Devices & Sites to Gaiia")}
|
{t("Link Devices & Sites to Gaiia")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"For each of your monitored devices and sites, find the matching Gaiia inventory item. This enables subscriber impact data when devices go down."
|
"For each of your monitored devices and sites, find the matching Gaiia inventory item. This enables subscriber impact data when devices go down."
|
||||||
)}
|
)}
|
||||||
|
|
@ -21,17 +21,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Sub-navigation tabs --%>
|
<%!-- Sub-navigation tabs --%>
|
||||||
<div class="mt-4 border-b border-gray-200 dark:border-white/10">
|
<div class="mt-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="flex space-x-6">
|
<nav class="flex space-x-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
||||||
class="border-b-2 border-indigo-500 px-1 pb-3 text-sm font-medium text-indigo-600 dark:text-indigo-400"
|
class="border-b-2 border-cerulean-500 px-1 pb-3 text-sm font-medium text-cerulean-600 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Device & Site Linking")}
|
{t("Device & Site Linking")}
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"}
|
||||||
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-white"
|
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Comparison Report")}
|
{t("Comparison Report")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Tab navigation --%>
|
<%!-- Tab navigation --%>
|
||||||
<div class="mt-6 border-b border-gray-200 dark:border-white/10">
|
<div class="mt-6 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="-mb-px flex space-x-8">
|
<nav class="-mb-px flex space-x-8">
|
||||||
<.link
|
<.link
|
||||||
id="tab-devices"
|
id="tab-devices"
|
||||||
|
|
@ -49,9 +49,10 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||||
if(@tab == "devices",
|
if(@tab == "devices",
|
||||||
do: "border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-400",
|
do:
|
||||||
|
"border-cerulean-500 text-cerulean-600 dark:border-cerulean-400 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -65,9 +66,10 @@
|
||||||
class={[
|
class={[
|
||||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||||
if(@tab == "sites",
|
if(@tab == "sites",
|
||||||
do: "border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-400",
|
do:
|
||||||
|
"border-cerulean-500 text-cerulean-600 dark:border-cerulean-400 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-300"
|
"border-transparent text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -86,9 +88,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-full px-3 py-1 text-xs font-medium",
|
"rounded-full px-3 py-1 text-xs font-medium",
|
||||||
if(@filter == f,
|
if(@filter == f,
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300",
|
do: "bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/40 dark:text-cerulean-300",
|
||||||
else:
|
else:
|
||||||
"bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-white/5 dark:text-gray-400 dark:hover:bg-white/10"
|
"bg-cool-steel-100 text-cool-steel-600 hover:bg-cool-steel-200 dark:bg-white/5 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -101,13 +103,13 @@
|
||||||
<%= if @items == [] do %>
|
<%= if @items == [] do %>
|
||||||
<div
|
<div
|
||||||
id="empty-state"
|
id="empty-state"
|
||||||
class="rounded-lg border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-700"
|
class="rounded-lg border-2 border-dashed border-cool-steel-300 p-12 text-center dark:border-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon
|
<.icon
|
||||||
name={if(@tab == "sites", do: "hero-map-pin", else: "hero-cpu-chip")}
|
name={if(@tab == "sites", do: "hero-map-pin", else: "hero-cpu-chip")}
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<%= case {@tab, @filter} do %>
|
<%= case {@tab, @filter} do %>
|
||||||
<% {"devices", "linked"} -> %>
|
<% {"devices", "linked"} -> %>
|
||||||
{t("No linked devices yet")}
|
{t("No linked devices yet")}
|
||||||
|
|
@ -123,7 +125,7 @@
|
||||||
{t("No sites in your organization")}
|
{t("No sites in your organization")}
|
||||||
<% end %>
|
<% end %>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= case {@tab, @filter} do %>
|
<%= case {@tab, @filter} do %>
|
||||||
<% {_, "unlinked"} -> %>
|
<% {_, "unlinked"} -> %>
|
||||||
{t("Nothing left to do here.")}
|
{t("Nothing left to do here.")}
|
||||||
|
|
@ -135,27 +137,27 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-white/5">
|
<thead class="bg-cool-steel-50 dark:bg-white/5">
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{if @tab == "devices", do: t("Your Device"), else: t("Your Site")}
|
{if @tab == "devices", do: t("Your Device"), else: t("Your Site")}
|
||||||
</th>
|
</th>
|
||||||
<%= if @tab == "devices" do %>
|
<%= if @tab == "devices" do %>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 sm:table-cell"
|
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 sm:table-cell"
|
||||||
>
|
>
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<% end %>
|
<% end %>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 md:table-cell"
|
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 md:table-cell"
|
||||||
>
|
>
|
||||||
{if @tab == "devices",
|
{if @tab == "devices",
|
||||||
do: t("Gaiia Inventory Item"),
|
do: t("Gaiia Inventory Item"),
|
||||||
|
|
@ -163,13 +165,13 @@
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for row <- @items do %>
|
<%= for row <- @items do %>
|
||||||
<% row_id = if @tab == "devices", do: row.device.id, else: row.site.id
|
<% row_id = if @tab == "devices", do: row.device.id, else: row.site.id
|
||||||
|
|
||||||
|
|
@ -180,7 +182,7 @@
|
||||||
<tr
|
<tr
|
||||||
id={"row-#{row_id}"}
|
id={"row-#{row_id}"}
|
||||||
class={[
|
class={[
|
||||||
"hover:bg-gray-50 dark:hover:bg-white/5",
|
"hover:bg-cool-steel-50 dark:hover:bg-white/5",
|
||||||
if(not is_linked, do: "cursor-pointer")
|
if(not is_linked, do: "cursor-pointer")
|
||||||
]}
|
]}
|
||||||
phx-click={if(not is_linked, do: "start_link")}
|
phx-click={if(not is_linked, do: "start_link")}
|
||||||
|
|
@ -194,19 +196,19 @@
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{row.device.id}"}
|
navigate={~p"/devices/#{row.device.id}"}
|
||||||
class="font-medium text-gray-900 hover:text-indigo-600 dark:text-white dark:hover:text-indigo-400"
|
class="font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{row.device.name || row.device.ip_address || "Unnamed"}
|
{row.device.name || row.device.ip_address || "Unnamed"}
|
||||||
</.link>
|
</.link>
|
||||||
<%= if Map.has_key?(@suggestions, row.device.id) do %>
|
<%= if Map.has_key?(@suggestions, row.device.id) do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-400">
|
<span class="inline-flex items-center rounded-full bg-wheat-100 px-2 py-0.5 text-xs font-medium text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400">
|
||||||
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" />
|
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" />
|
||||||
Match found
|
Match found
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= if row.device.site do %>
|
<%= if row.device.site do %>
|
||||||
<div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
<div class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{row.device.site.name}
|
{row.device.site.name}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -214,11 +216,11 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{row.site.name || "Unnamed"}
|
{row.site.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<%= if Map.has_key?(@suggestions, row.site.id) do %>
|
<%= if Map.has_key?(@suggestions, row.site.id) do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-400">
|
<span class="inline-flex items-center rounded-full bg-wheat-100 px-2 py-0.5 text-xs font-medium text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400">
|
||||||
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" />
|
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" />
|
||||||
Match found
|
Match found
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -231,13 +233,13 @@
|
||||||
|
|
||||||
<%!-- IP Address (devices only) --%>
|
<%!-- IP Address (devices only) --%>
|
||||||
<%= if @tab == "devices" do %>
|
<%= if @tab == "devices" do %>
|
||||||
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400 sm:table-cell">
|
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400 sm:table-cell">
|
||||||
<%= if row.device.ip_address do %>
|
<%= if row.device.ip_address do %>
|
||||||
<a
|
<a
|
||||||
href={"http://#{row.device.ip_address}"}
|
href={"http://#{row.device.ip_address}"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="font-mono hover:text-indigo-600 dark:hover:text-indigo-400"
|
class="font-mono hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{row.device.ip_address}
|
{row.device.ip_address}
|
||||||
|
|
@ -258,7 +260,7 @@
|
||||||
href={"#{@gaiia_app_url}/inventory/items/#{row.inventory_item.gaiia_id}"}
|
href={"#{@gaiia_app_url}/inventory/items/#{row.inventory_item.gaiia_id}"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="font-medium text-gray-900 hover:text-indigo-600 dark:text-white dark:hover:text-indigo-400"
|
class="font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{row.inventory_item.name || "Unnamed"}
|
{row.inventory_item.name || "Unnamed"}
|
||||||
<.icon
|
<.icon
|
||||||
|
|
@ -267,18 +269,18 @@
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{row.inventory_item.name || "Unnamed"}
|
{row.inventory_item.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if row.inventory_item.model_name do %>
|
<%= if row.inventory_item.model_name do %>
|
||||||
<div class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
<div class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{row.inventory_item.model_name}
|
{row.inventory_item.model_name}
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500 dark:bg-white/10 dark:text-gray-400">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2.5 py-0.5 text-xs font-medium text-cool-steel-500 dark:bg-white/10 dark:text-cool-steel-400">
|
||||||
Not linked
|
Not linked
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -289,7 +291,7 @@
|
||||||
href={"#{@gaiia_app_url}/network-sites/#{row.network_site.gaiia_id}"}
|
href={"#{@gaiia_app_url}/network-sites/#{row.network_site.gaiia_id}"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="font-medium text-gray-900 hover:text-indigo-600 dark:text-white dark:hover:text-indigo-400"
|
class="font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{row.network_site.name || "Unnamed"}
|
{row.network_site.name || "Unnamed"}
|
||||||
|
|
@ -299,12 +301,12 @@
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{row.network_site.name || "Unnamed"}
|
{row.network_site.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500 dark:bg-white/10 dark:text-gray-400">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2.5 py-0.5 text-xs font-medium text-cool-steel-500 dark:bg-white/10 dark:text-cool-steel-400">
|
||||||
Not linked
|
Not linked
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -323,7 +325,7 @@
|
||||||
data-confirm={
|
data-confirm={
|
||||||
t("Remove the link between this device and its Gaiia item?")
|
t("Remove the link between this device and its Gaiia item?")
|
||||||
}
|
}
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
{t("Unlink")}
|
{t("Unlink")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -333,7 +335,7 @@
|
||||||
id={"link-#{row.device.id}"}
|
id={"link-#{row.device.id}"}
|
||||||
phx-click="start_link"
|
phx-click="start_link"
|
||||||
phx-value-id={row.device.id}
|
phx-value-id={row.device.id}
|
||||||
class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Link")}
|
{t("Link")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -348,7 +350,7 @@
|
||||||
data-confirm={
|
data-confirm={
|
||||||
t("Remove the link between this site and its Gaiia network site?")
|
t("Remove the link between this site and its Gaiia network site?")
|
||||||
}
|
}
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
{t("Unlink")}
|
{t("Unlink")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -358,7 +360,7 @@
|
||||||
id={"link-#{row.site.id}"}
|
id={"link-#{row.site.id}"}
|
||||||
phx-click="start_link"
|
phx-click="start_link"
|
||||||
phx-value-id={row.site.id}
|
phx-value-id={row.site.id}
|
||||||
class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Link")}
|
{t("Link")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -369,19 +371,19 @@
|
||||||
|
|
||||||
<%!-- Inline linking row --%>
|
<%!-- Inline linking row --%>
|
||||||
<%= if row_id == @linking_id do %>
|
<%= if row_id == @linking_id do %>
|
||||||
<tr id={"linking-row-#{row_id}"} class="bg-indigo-50 dark:bg-indigo-900/20">
|
<tr id={"linking-row-#{row_id}"} class="bg-cerulean-50 dark:bg-cerulean-900/20">
|
||||||
<td colspan={if(@tab == "devices", do: "4", else: "3")} class="px-4 py-4">
|
<td colspan={if(@tab == "devices", do: "4", else: "3")} class="px-4 py-4">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
<p class="mb-2 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
<%= if @tab == "devices" do %>
|
<%= if @tab == "devices" do %>
|
||||||
Find a Gaiia item to link to
|
Find a Gaiia item to link to
|
||||||
<span class="text-indigo-600 dark:text-indigo-400">
|
<span class="text-cerulean-600 dark:text-cerulean-400">
|
||||||
{row.device.name || row.device.ip_address}
|
{row.device.name || row.device.ip_address}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
Find a Gaiia network site to link to
|
Find a Gaiia network site to link to
|
||||||
<span class="text-indigo-600 dark:text-indigo-400">
|
<span class="text-cerulean-600 dark:text-cerulean-400">
|
||||||
{row.site.name}
|
{row.site.name}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -390,7 +392,7 @@
|
||||||
<%!-- Suggestions --%>
|
<%!-- Suggestions --%>
|
||||||
<%= if suggestions = @suggestions[row_id] do %>
|
<%= if suggestions = @suggestions[row_id] do %>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<p class="mb-1 text-xs font-medium text-amber-700 dark:text-amber-400">
|
<p class="mb-1 text-xs font-medium text-wheat-700 dark:text-wheat-400">
|
||||||
<.icon name="hero-light-bulb-mini" class="inline h-3 w-3 mr-0.5" />
|
<.icon name="hero-light-bulb-mini" class="inline h-3 w-3 mr-0.5" />
|
||||||
{t("Suggested match (same IP):")}
|
{t("Suggested match (same IP):")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -404,15 +406,15 @@
|
||||||
phx-value-device-id={if(@tab == "devices", do: row.device.id)}
|
phx-value-device-id={if(@tab == "devices", do: row.device.id)}
|
||||||
phx-value-site-id={if(@tab == "sites", do: row.site.id)}
|
phx-value-site-id={if(@tab == "sites", do: row.site.id)}
|
||||||
phx-value-gaiia-id={suggestion.entity.gaiia_id}
|
phx-value-gaiia-id={suggestion.entity.gaiia_id}
|
||||||
class="inline-flex items-center gap-1.5 rounded-md border border-amber-300 bg-amber-50 px-2.5 py-1.5 text-xs font-medium text-amber-800 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-300 dark:hover:bg-amber-900/40"
|
class="inline-flex items-center gap-1.5 rounded-md border border-wheat-300 bg-wheat-50 px-2.5 py-1.5 text-xs font-medium text-wheat-800 hover:bg-wheat-100 dark:border-wheat-700 dark:bg-wheat-900/20 dark:text-wheat-300 dark:hover:bg-wheat-900/40"
|
||||||
>
|
>
|
||||||
{suggestion.entity.name}
|
{suggestion.entity.name}
|
||||||
<%= if @tab == "devices" && suggestion.entity.ip_address do %>
|
<%= if @tab == "devices" && suggestion.entity.ip_address do %>
|
||||||
<span class="text-amber-600 dark:text-amber-400">
|
<span class="text-wheat-600 dark:text-wheat-400">
|
||||||
{suggestion.entity.ip_address}
|
{suggestion.entity.ip_address}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="rounded bg-amber-200 px-1 text-amber-700 dark:bg-amber-800 dark:text-amber-300">
|
<span class="rounded bg-wheat-200 px-1 text-wheat-700 dark:bg-wheat-800 dark:text-wheat-300">
|
||||||
{suggestion.match_type}
|
{suggestion.match_type}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -436,13 +438,13 @@
|
||||||
}
|
}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
phx-debounce="300"
|
phx-debounce="300"
|
||||||
class="block w-full rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
class="block w-full rounded-md border-cool-steel-300 text-sm shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<%= if @search_results != [] do %>
|
<%= if @search_results != [] do %>
|
||||||
<div class="mt-2 max-h-48 overflow-y-auto rounded-md border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800">
|
<div class="mt-2 max-h-48 overflow-y-auto rounded-md border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800">
|
||||||
<ul class="divide-y divide-gray-200 dark:divide-white/5">
|
<ul class="divide-y divide-cool-steel-200 dark:divide-white/5">
|
||||||
<li :for={result <- @search_results}>
|
<li :for={result <- @search_results}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -453,13 +455,13 @@
|
||||||
phx-value-device-id={if(@tab == "devices", do: @linking_id)}
|
phx-value-device-id={if(@tab == "devices", do: @linking_id)}
|
||||||
phx-value-site-id={if(@tab == "sites", do: @linking_id)}
|
phx-value-site-id={if(@tab == "sites", do: @linking_id)}
|
||||||
phx-value-gaiia-id={result.gaiia_id}
|
phx-value-gaiia-id={result.gaiia_id}
|
||||||
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-gray-50 dark:hover:bg-white/5"
|
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-cool-steel-50 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{result.name || "Unnamed"}
|
{result.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<%= if @tab == "devices" do %>
|
<%= if @tab == "devices" do %>
|
||||||
<span class="flex items-center gap-2 text-gray-500 dark:text-gray-400">
|
<span class="flex items-center gap-2 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if result.model_name do %>
|
<%= if result.model_name do %>
|
||||||
<span class="text-xs">{result.model_name}</span>
|
<span class="text-xs">{result.model_name}</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -473,7 +475,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @search_query != "" and String.length(@search_query) >= 2 and @search_results == [] do %>
|
<%= if @search_query != "" and String.length(@search_query) >= 2 and @search_results == [] do %>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No Gaiia {if(@tab == "sites",
|
No Gaiia {if(@tab == "sites",
|
||||||
do: "network sites",
|
do: "network sites",
|
||||||
else: "inventory items"
|
else: "inventory items"
|
||||||
|
|
@ -485,7 +487,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="cancel-link"
|
id="cancel-link"
|
||||||
phx-click="cancel_link"
|
phx-click="cancel_link"
|
||||||
class="flex-shrink-0 self-start text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
class="flex-shrink-0 self-start text-sm text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,17 @@
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<%!-- Sub-navigation tabs --%>
|
<%!-- Sub-navigation tabs --%>
|
||||||
<div class="mt-4 border-b border-gray-200 dark:border-white/10">
|
<div class="mt-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="flex space-x-6">
|
<nav class="flex space-x-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
||||||
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-white"
|
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Entity Mapping")}
|
{t("Entity Mapping")}
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"}
|
||||||
class="border-b-2 border-indigo-500 px-1 pb-3 text-sm font-medium text-indigo-600 dark:text-indigo-400"
|
class="border-b-2 border-cerulean-500 px-1 pb-3 text-sm font-medium text-cerulean-600 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Reconciliation")}
|
{t("Reconciliation")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -39,29 +39,29 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-5">
|
<div class="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-5">
|
||||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white p-4 dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="text-2xl font-bold text-gray-900 dark:text-white">
|
<div class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@report.summary.total_mapped}
|
{@report.summary.total_mapped}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-600 dark:text-gray-400">Mapped</div>
|
<div class="text-sm text-cool-steel-600 dark:text-cool-steel-400">Mapped</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-yellow-200 bg-yellow-50 p-4 dark:border-yellow-600/30 dark:bg-yellow-900/20">
|
<div class="rounded-lg border border-wheat-200 bg-wheat-50 p-4 dark:border-wheat-600/30 dark:bg-wheat-900/20">
|
||||||
<div class="text-2xl font-bold text-yellow-800 dark:text-yellow-400">
|
<div class="text-2xl font-bold text-wheat-800 dark:text-wheat-400">
|
||||||
{@report.summary.missing_mapping_count}
|
{@report.summary.missing_mapping_count}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-yellow-700 dark:text-yellow-500">Unmapped Items</div>
|
<div class="text-sm text-wheat-700 dark:text-wheat-500">Unmapped Items</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-600/30 dark:bg-blue-900/20">
|
<div class="rounded-lg border border-cerulean-200 bg-cerulean-50 p-4 dark:border-cerulean-600/30 dark:bg-cerulean-900/20">
|
||||||
<div class="text-2xl font-bold text-blue-800 dark:text-blue-400">
|
<div class="text-2xl font-bold text-cerulean-800 dark:text-cerulean-400">
|
||||||
{@report.summary.untracked_count}
|
{@report.summary.untracked_count}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-blue-700 dark:text-blue-500">Untracked Devices</div>
|
<div class="text-sm text-cerulean-700 dark:text-cerulean-500">Untracked Devices</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-600/30 dark:bg-red-900/20">
|
<div class="rounded-lg border border-sweet-salmon-200 bg-sweet-salmon-50 p-4 dark:border-sweet-salmon-600/30 dark:bg-sweet-salmon-900/20">
|
||||||
<div class="text-2xl font-bold text-red-800 dark:text-red-400">
|
<div class="text-2xl font-bold text-sweet-salmon-800 dark:text-sweet-salmon-400">
|
||||||
{@report.summary.mismatch_count}
|
{@report.summary.mismatch_count}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-red-700 dark:text-red-500">Data Mismatches</div>
|
<div class="text-sm text-sweet-salmon-700 dark:text-sweet-salmon-500">Data Mismatches</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-600/30 dark:bg-purple-900/20">
|
<div class="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-600/30 dark:bg-purple-900/20">
|
||||||
<div class="text-2xl font-bold text-purple-800 dark:text-purple-400">
|
<div class="text-2xl font-bold text-purple-800 dark:text-purple-400">
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 mb-6 flex items-center justify-between">
|
<div class="mt-6 mb-6 flex items-center justify-between">
|
||||||
<div class="inline-flex rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="inline-flex rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<.link
|
<.link
|
||||||
patch={
|
patch={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=summary"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=summary"
|
||||||
|
|
@ -80,9 +80,9 @@
|
||||||
class={[
|
class={[
|
||||||
"px-4 py-2 text-sm font-medium rounded-l-lg",
|
"px-4 py-2 text-sm font-medium rounded-l-lg",
|
||||||
@active_tab == "summary" &&
|
@active_tab == "summary" &&
|
||||||
"bg-blue-600 text-white dark:bg-blue-500",
|
"bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
@active_tab != "summary" &&
|
@active_tab != "summary" &&
|
||||||
"bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Summary")}
|
{t("Summary")}
|
||||||
|
|
@ -92,11 +92,11 @@
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=untracked"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=untracked"
|
||||||
}
|
}
|
||||||
class={[
|
class={[
|
||||||
"px-4 py-2 text-sm font-medium border-l border-gray-200 dark:border-white/10",
|
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
|
||||||
@active_tab == "untracked" &&
|
@active_tab == "untracked" &&
|
||||||
"bg-blue-600 text-white dark:bg-blue-500",
|
"bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
@active_tab != "untracked" &&
|
@active_tab != "untracked" &&
|
||||||
"bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Untracked ({@report.summary.untracked_count})
|
Untracked ({@report.summary.untracked_count})
|
||||||
|
|
@ -106,11 +106,11 @@
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=mismatches"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=mismatches"
|
||||||
}
|
}
|
||||||
class={[
|
class={[
|
||||||
"px-4 py-2 text-sm font-medium border-l border-gray-200 dark:border-white/10",
|
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
|
||||||
@active_tab == "mismatches" &&
|
@active_tab == "mismatches" &&
|
||||||
"bg-blue-600 text-white dark:bg-blue-500",
|
"bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
@active_tab != "mismatches" &&
|
@active_tab != "mismatches" &&
|
||||||
"bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Mismatches ({@report.summary.mismatch_count})
|
Mismatches ({@report.summary.mismatch_count})
|
||||||
|
|
@ -120,11 +120,11 @@
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=unmapped"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=unmapped"
|
||||||
}
|
}
|
||||||
class={[
|
class={[
|
||||||
"px-4 py-2 text-sm font-medium border-l border-gray-200 dark:border-white/10",
|
"px-4 py-2 text-sm font-medium border-l border-cool-steel-200 dark:border-white/10",
|
||||||
@active_tab == "unmapped" &&
|
@active_tab == "unmapped" &&
|
||||||
"bg-blue-600 text-white dark:bg-blue-500",
|
"bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
@active_tab != "unmapped" &&
|
@active_tab != "unmapped" &&
|
||||||
"bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Unmapped ({@report.summary.missing_mapping_count})
|
Unmapped ({@report.summary.missing_mapping_count})
|
||||||
|
|
@ -134,11 +134,11 @@
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=ghosts"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation?tab=ghosts"
|
||||||
}
|
}
|
||||||
class={[
|
class={[
|
||||||
"px-4 py-2 text-sm font-medium rounded-r-lg border-l border-gray-200 dark:border-white/10",
|
"px-4 py-2 text-sm font-medium rounded-r-lg border-l border-cool-steel-200 dark:border-white/10",
|
||||||
@active_tab == "ghosts" &&
|
@active_tab == "ghosts" &&
|
||||||
"bg-blue-600 text-white dark:bg-blue-500",
|
"bg-cerulean-600 text-white dark:bg-cerulean-500",
|
||||||
@active_tab != "ghosts" &&
|
@active_tab != "ghosts" &&
|
||||||
"bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
"bg-white text-cool-steel-700 hover:bg-cool-steel-50 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
Ghosts ({@report.summary.ghost_count})
|
Ghosts ({@report.summary.ghost_count})
|
||||||
|
|
@ -147,7 +147,7 @@
|
||||||
|
|
||||||
<button
|
<button
|
||||||
phx-click="refresh"
|
phx-click="refresh"
|
||||||
class="inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800/50 dark:text-gray-300 dark:hover:bg-gray-800"
|
class="inline-flex items-center gap-1.5 rounded-lg border border-cool-steel-200 bg-white px-3 py-2 text-sm font-medium text-cool-steel-700 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800/50 dark:text-cool-steel-300 dark:hover:bg-cool-steel-800"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
<.icon name="hero-arrow-path" class="h-4 w-4" />
|
||||||
{t("Refresh")}
|
{t("Refresh")}
|
||||||
|
|
@ -158,61 +158,61 @@
|
||||||
<% "untracked" -> %>
|
<% "untracked" -> %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= if @report.untracked_devices == [] do %>
|
<%= if @report.untracked_devices == [] do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("All Towerops devices have matching Gaiia inventory items.")}
|
{t("All Towerops devices have matching Gaiia inventory items.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mb-4 flex items-center gap-3">
|
<div class="mb-4 flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
phx-click="auto_match"
|
phx-click="auto_match"
|
||||||
class="inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700"
|
class="inline-flex items-center gap-1.5 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-medium text-white hover:bg-cerulean-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-bolt" class="h-4 w-4" />
|
<.icon name="hero-bolt" class="h-4 w-4" />
|
||||||
{t("Auto-Match All")}
|
{t("Auto-Match All")}
|
||||||
</button>
|
</button>
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Links by MAC, IP, or site assignment.")}
|
{t("Links by MAC, IP, or site assignment.")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Device")}
|
{t("Device")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Site")}
|
{t("Site")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for entry <- @report.untracked_devices do %>
|
<%= for entry <- @report.untracked_devices do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{entry.device.id}"}
|
navigate={~p"/devices/#{entry.device.id}"}
|
||||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{entry.device.name}
|
{entry.device.name}
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<a
|
<a
|
||||||
href={"http://#{entry.device.ip_address}"}
|
href={"http://#{entry.device.ip_address}"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{entry.device.ip_address}
|
{entry.device.ip_address}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if entry.device.site do %>
|
<%= if entry.device.site do %>
|
||||||
{entry.device.site.name}
|
{entry.device.site.name}
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -224,7 +224,7 @@
|
||||||
<form phx-change="select_manufacturer">
|
<form phx-change="select_manufacturer">
|
||||||
<select
|
<select
|
||||||
name="manufacturer_id"
|
name="manufacturer_id"
|
||||||
class="rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300"
|
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<option value="">{t("Select manufacturer")}</option>
|
<option value="">{t("Select manufacturer")}</option>
|
||||||
<%= for mfr <- @manufacturers do %>
|
<%= for mfr <- @manufacturers do %>
|
||||||
|
|
@ -245,21 +245,21 @@
|
||||||
<input type="hidden" name="device_id" value={entry.device.id} />
|
<input type="hidden" name="device_id" value={entry.device.id} />
|
||||||
<select
|
<select
|
||||||
name="model_id"
|
name="model_id"
|
||||||
class="rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300"
|
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<option value="">{t("Select model")}</option>
|
<option value="">{t("Select model")}</option>
|
||||||
<%= for model <- Map.get(@models, @selected_manufacturer, []) do %>
|
<%= for model <- Map.get(@models, @selected_manufacturer, []) do %>
|
||||||
<option value={model["id"]}>{model["name"]}</option>
|
<option value={model["id"]}>{model["name"]}</option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
<button class="rounded bg-indigo-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-indigo-700">
|
<button class="rounded bg-cerulean-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-cerulean-700">
|
||||||
{t("Create")}
|
{t("Create")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
<button
|
<button
|
||||||
phx-click="cancel_create"
|
phx-click="cancel_create"
|
||||||
class="rounded px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400"
|
class="rounded px-2 py-1 text-xs text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -273,13 +273,13 @@
|
||||||
name="query"
|
name="query"
|
||||||
value={@link_search_query}
|
value={@link_search_query}
|
||||||
placeholder={t("Search by name or IP...")}
|
placeholder={t("Search by name or IP...")}
|
||||||
class="rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300"
|
class="rounded border border-cool-steel-300 bg-white px-2 py-1 text-xs text-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
phx-click="cancel_link_search"
|
phx-click="cancel_link_search"
|
||||||
class="rounded px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400"
|
class="rounded px-2 py-1 text-xs text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -289,14 +289,14 @@
|
||||||
<button
|
<button
|
||||||
phx-click="start_link_search"
|
phx-click="start_link_search"
|
||||||
phx-value-device_id={entry.device.id}
|
phx-value-device_id={entry.device.id}
|
||||||
class="rounded bg-white px-2.5 py-1 text-xs font-medium text-gray-700 border border-gray-300 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
class="rounded bg-white px-2.5 py-1 text-xs font-medium text-cool-steel-700 border border-cool-steel-300 hover:bg-cool-steel-50 dark:border-white/10 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
{t("Link")}
|
{t("Link")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
phx-click="open_create_form"
|
phx-click="open_create_form"
|
||||||
phx-value-device_id={entry.device.id}
|
phx-value-device_id={entry.device.id}
|
||||||
class="inline-flex items-center gap-1 rounded bg-indigo-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-indigo-700"
|
class="inline-flex items-center gap-1 rounded bg-cerulean-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-cerulean-700"
|
||||||
>
|
>
|
||||||
{t("Create in Gaiia")}
|
{t("Create in Gaiia")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -307,15 +307,15 @@
|
||||||
<%!-- Search results row for link search --%>
|
<%!-- Search results row for link search --%>
|
||||||
<%= if @open_link_for == entry.device.id and @link_search_results != [] do %>
|
<%= if @open_link_for == entry.device.id and @link_search_results != [] do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="px-4 py-2 bg-gray-50 dark:bg-gray-800/50">
|
<td colspan="4" class="px-4 py-2 bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<%= for item <- @link_search_results do %>
|
<%= for item <- @link_search_results do %>
|
||||||
<div class="flex items-center justify-between rounded border border-gray-200 bg-white px-3 py-2 dark:border-white/10 dark:bg-gray-800">
|
<div class="flex items-center justify-between rounded border border-cool-steel-200 bg-white px-3 py-2 dark:border-white/10 dark:bg-cool-steel-800">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
<span class="ml-2 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{item.ip_address || "—"}
|
{item.ip_address || "—"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -323,7 +323,7 @@
|
||||||
phx-click="link_to_gaiia"
|
phx-click="link_to_gaiia"
|
||||||
phx-value-device_id={entry.device.id}
|
phx-value-device_id={entry.device.id}
|
||||||
phx-value-gaiia_id={item.gaiia_id}
|
phx-value-gaiia_id={item.gaiia_id}
|
||||||
class="rounded bg-indigo-600 px-2 py-1 text-xs font-medium text-white hover:bg-indigo-700"
|
class="rounded bg-cerulean-600 px-2 py-1 text-xs font-medium text-white hover:bg-cerulean-700"
|
||||||
>
|
>
|
||||||
{t("Link")}
|
{t("Link")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -342,46 +342,46 @@
|
||||||
<% "mismatches" -> %>
|
<% "mismatches" -> %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= if @report.data_mismatches == [] do %>
|
<%= if @report.data_mismatches == [] do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No data mismatches found between mapped devices.")}
|
{t("No data mismatches found between mapped devices.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Device")}
|
{t("Device")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Field")}
|
{t("Field")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Towerops")}
|
{t("Towerops")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Gaiia")}
|
{t("Gaiia")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for mismatch <- @report.data_mismatches do %>
|
<%= for mismatch <- @report.data_mismatches do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{mismatch.device.id}"}
|
navigate={~p"/devices/#{mismatch.device.id}"}
|
||||||
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{mismatch.device.name}
|
{mismatch.device.name}
|
||||||
</.link>
|
</.link>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{mismatch.field}
|
{mismatch.field}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-900 dark:text-white">
|
||||||
{mismatch.towerops_value}
|
{mismatch.towerops_value}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-red-600 dark:text-red-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{mismatch.gaiia_value}
|
{mismatch.gaiia_value}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -394,41 +394,41 @@
|
||||||
<% "unmapped" -> %>
|
<% "unmapped" -> %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= if @report.missing_mappings == [] do %>
|
<%= if @report.missing_mappings == [] do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("All Gaiia inventory items are mapped to Towerops devices.")}
|
{t("All Gaiia inventory items are mapped to Towerops devices.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Gaiia Item")}
|
{t("Gaiia Item")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Category")}
|
{t("Category")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for entry <- @report.missing_mappings do %>
|
<%= for entry <- @report.missing_mappings do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{entry.inventory_item.name}
|
{entry.inventory_item.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{entry.inventory_item.ip_address || "—"}
|
{entry.inventory_item.ip_address || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{entry.inventory_item.category || "—"}
|
{entry.inventory_item.category || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{entry.inventory_item.status || "—"}
|
{entry.inventory_item.status || "—"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -441,43 +441,43 @@
|
||||||
<% "ghosts" -> %>
|
<% "ghosts" -> %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= if @report.ghost_devices == [] do %>
|
<%= if @report.ghost_devices == [] do %>
|
||||||
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
<p class="py-8 text-center text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No stale mappings — all linked Gaiia items reference existing Towerops devices.")}
|
{t("No stale mappings — all linked Gaiia items reference existing Towerops devices.")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"These Gaiia inventory items are linked to Towerops devices that no longer exist. Unlink them to restore them to the unmapped pool."
|
"These Gaiia inventory items are linked to Towerops devices that no longer exist. Unlink them to restore them to the unmapped pool."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Gaiia Item")}
|
{t("Gaiia Item")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for entry <- @report.ghost_devices do %>
|
<%= for entry <- @report.ghost_devices do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{entry.inventory_item.name}
|
{entry.inventory_item.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm font-mono text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{entry.inventory_item.ip_address || "—"}
|
{entry.inventory_item.ip_address || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{entry.inventory_item.status || "—"}
|
{entry.inventory_item.status || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
<td class="whitespace-nowrap px-4 py-3 text-right text-sm">
|
||||||
|
|
@ -498,7 +498,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% _ -> %>
|
<% _ -> %>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Last reconciled: {ToweropsWeb.TimeHelpers.format_datetime(
|
Last reconciled: {ToweropsWeb.TimeHelpers.format_datetime(
|
||||||
@report.reconciled_at,
|
@report.reconciled_at,
|
||||||
@current_scope.timezone,
|
@current_scope.timezone,
|
||||||
|
|
@ -510,59 +510,61 @@
|
||||||
<div class="py-8 text-center">
|
<div class="py-8 text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-check-circle"
|
name="hero-check-circle"
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No inventory data")}
|
{t("No inventory data")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Sync Gaiia data and map inventory items to see reconciliation results.")}
|
{t("Sync Gaiia data and map inventory items to see reconciliation results.")}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"}
|
||||||
class="mt-4 inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="mt-4 inline-flex items-center gap-1 text-sm font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Go to Entity Mapping <.icon name="hero-arrow-right" class="h-4 w-4" />
|
Go to Entity Mapping <.icon name="hero-arrow-right" class="h-4 w-4" />
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white p-6 dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">{t("Overview")}</h3>
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("Overview")}
|
||||||
|
</h3>
|
||||||
<dl class="mt-4 space-y-3">
|
<dl class="mt-4 space-y-3">
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
Mapped devices (Gaiia ↔ Towerops)
|
Mapped devices (Gaiia ↔ Towerops)
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{@report.summary.total_mapped}
|
{@report.summary.total_mapped}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Unmapped Gaiia items (no Towerops link)")}
|
{t("Unmapped Gaiia items (no Towerops link)")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="font-medium text-yellow-600 dark:text-yellow-400">
|
<dd class="font-medium text-wheat-600 dark:text-wheat-400">
|
||||||
{@report.summary.missing_mapping_count}
|
{@report.summary.missing_mapping_count}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Untracked Towerops devices (not in Gaiia)")}
|
{t("Untracked Towerops devices (not in Gaiia)")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="font-medium text-blue-600 dark:text-blue-400">
|
<dd class="font-medium text-cerulean-600 dark:text-cerulean-400">
|
||||||
{@report.summary.untracked_count}
|
{@report.summary.untracked_count}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Data mismatches (IP, serial, etc.)")}
|
{t("Data mismatches (IP, serial, etc.)")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="font-medium text-red-600 dark:text-red-400">
|
<dd class="font-medium text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{@report.summary.mismatch_count}
|
{@report.summary.mismatch_count}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between text-sm">
|
<div class="flex justify-between text-sm">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Ghost mappings (stale device links)")}
|
{t("Ghost mappings (stale device links)")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="font-medium text-purple-600 dark:text-purple-400">
|
<dd class="font-medium text-purple-600 dark:text-purple-400">
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@
|
||||||
current_scope={@current_scope}
|
current_scope={@current_scope}
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings"}
|
navigate={~p"/orgs/#{@organization.slug}/settings"}
|
||||||
class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="inline-flex items-center gap-1 text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" /> Back to Settings
|
<.icon name="hero-arrow-left" class="h-4 w-4" /> Back to Settings
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Integrations")}
|
{t("Integrations")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Connect third-party services to enhance your monitoring capabilities.")}
|
{t("Connect third-party services to enhance your monitoring capabilities.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,10 +23,10 @@
|
||||||
<div class="mt-8 space-y-10">
|
<div class="mt-8 space-y-10">
|
||||||
<div :for={category <- @provider_categories}>
|
<div :for={category <- @provider_categories}>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{category.name}
|
{category.name}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{category.description}
|
{category.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -38,41 +38,41 @@
|
||||||
@integrations[p.id] && @integrations[p.id].enabled
|
@integrations[p.id] && @integrations[p.id].enabled
|
||||||
end) %>
|
end) %>
|
||||||
<div class="mb-4 flex items-center gap-2">
|
<div class="mb-4 flex items-center gap-2">
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300">
|
<span class="inline-flex items-center gap-1 rounded-full bg-cerulean-100 px-3 py-1 text-xs font-semibold text-cerulean-700 dark:bg-cerulean-900/40 dark:text-cerulean-300">
|
||||||
<.icon name="hero-arrow-path" class="h-3.5 w-3.5" /> Choose one
|
<.icon name="hero-arrow-path" class="h-3.5 w-3.5" /> Choose one
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
— only one billing platform can be active at a time
|
— only one billing platform can be active at a time
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg border-2 border-indigo-200 bg-white p-6 dark:border-indigo-800/50 dark:bg-white/5">
|
<div class="rounded-lg border-2 border-cerulean-200 bg-white p-6 dark:border-cerulean-800/50 dark:bg-white/5">
|
||||||
<%= if active_billing do %>
|
<%= if active_billing do %>
|
||||||
<%!-- Active billing provider --%>
|
<%!-- Active billing provider --%>
|
||||||
<% integration = @integrations[active_billing.id] %>
|
<% integration = @integrations[active_billing.id] %>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-50 dark:bg-indigo-900/30">
|
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-cerulean-50 dark:bg-cerulean-900/30">
|
||||||
<.icon
|
<.icon
|
||||||
name={active_billing.icon}
|
name={active_billing.icon}
|
||||||
class="h-6 w-6 text-indigo-600 dark:text-indigo-400"
|
class="h-6 w-6 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{active_billing.name}
|
{active_billing.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900/30 dark:text-green-400">
|
<span class="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900/30 dark:text-green-400">
|
||||||
Connected
|
Connected
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{active_billing.description}
|
{active_billing.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-3 flex flex-wrap items-center gap-4">
|
<div class="mt-3 flex flex-wrap items-center gap-4">
|
||||||
<%= if integration.last_synced_at do %>
|
<%= if integration.last_synced_at do %>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Last synced:")}
|
{t("Last synced:")}
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={integration.last_synced_at}
|
datetime={integration.last_synced_at}
|
||||||
|
|
@ -90,20 +90,20 @@
|
||||||
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
||||||
|
|
||||||
"partial" ->
|
"partial" ->
|
||||||
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-700 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
"failed" ->
|
"failed" ->
|
||||||
"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
"bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{String.capitalize(integration.last_sync_status)}
|
{String.capitalize(integration.last_sync_status)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
· Syncs every {humanize_interval(integration.sync_interval_minutes)}
|
· Syncs every {humanize_interval(integration.sync_interval_minutes)}
|
||||||
<%= if integration.last_synced_at do %>
|
<%= if integration.last_synced_at do %>
|
||||||
· Next in ~{next_sync_minutes(
|
· Next in ~{next_sync_minutes(
|
||||||
|
|
@ -118,7 +118,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping"
|
||||||
}
|
}
|
||||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Device & Site Linking →
|
Device & Site Linking →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation"
|
||||||
}
|
}
|
||||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Comparison Report →
|
Comparison Report →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -140,7 +140,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_enabled"
|
phx-click="toggle_enabled"
|
||||||
phx-value-provider={active_billing.id}
|
phx-value-provider={active_billing.id}
|
||||||
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-indigo-600 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
|
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-cerulean-600 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-cerulean-600 focus:ring-offset-2"
|
||||||
>
|
>
|
||||||
<span class="pointer-events-none inline-block h-5 w-5 translate-x-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" />
|
<span class="pointer-events-none inline-block h-5 w-5 translate-x-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
id={"configure-#{active_billing.id}"}
|
id={"configure-#{active_billing.id}"}
|
||||||
phx-click="configure"
|
phx-click="configure"
|
||||||
phx-value-provider={active_billing.id}
|
phx-value-provider={active_billing.id}
|
||||||
class="rounded-md bg-white px-3 py-2 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"
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-cool-steel-900 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
{t("Configure")}
|
{t("Configure")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -157,14 +157,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Switch provider link --%>
|
<%!-- Switch provider link --%>
|
||||||
<div class="mt-4 border-t border-gray-100 pt-3 dark:border-white/5">
|
<div class="mt-4 border-t border-cool-steel-100 pt-3 dark:border-white/5">
|
||||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
<p class="text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
Using {active_billing.name}. To switch providers, disable this integration first.
|
Using {active_billing.name}. To switch providers, disable this integration first.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%!-- No active billing — show provider picker --%>
|
<%!-- No active billing — show provider picker --%>
|
||||||
<p class="mb-4 text-sm text-gray-600 dark:text-gray-300 font-medium">
|
<p class="mb-4 text-sm text-cool-steel-600 dark:text-cool-steel-300 font-medium">
|
||||||
{t("Which billing platform does your organization use?")}
|
{t("Which billing platform does your organization use?")}
|
||||||
</p>
|
</p>
|
||||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||||
|
|
@ -175,24 +175,24 @@
|
||||||
phx-click="configure"
|
phx-click="configure"
|
||||||
phx-value-provider={provider.id}
|
phx-value-provider={provider.id}
|
||||||
class={[
|
class={[
|
||||||
"group relative flex flex-col items-center rounded-lg border-2 p-4 text-center transition-all hover:border-indigo-300 hover:bg-indigo-50/50 dark:hover:border-indigo-700 dark:hover:bg-indigo-900/10",
|
"group relative flex flex-col items-center rounded-lg border-2 p-4 text-center transition-all hover:border-cerulean-300 hover:bg-cerulean-50/50 dark:hover:border-cerulean-700 dark:hover:bg-cerulean-900/10",
|
||||||
if(@configuring == provider.id,
|
if(@configuring == provider.id,
|
||||||
do:
|
do:
|
||||||
"border-indigo-500 bg-indigo-50/50 dark:border-indigo-600 dark:bg-indigo-900/20",
|
"border-cerulean-500 bg-cerulean-50/50 dark:border-cerulean-600 dark:bg-cerulean-900/20",
|
||||||
else: "border-gray-200 dark:border-white/10"
|
else: "border-cool-steel-200 dark:border-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-50 group-hover:bg-indigo-100 dark:bg-indigo-900/30">
|
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-cerulean-50 group-hover:bg-cerulean-100 dark:bg-cerulean-900/30">
|
||||||
<.icon
|
<.icon
|
||||||
name={provider.icon}
|
name={provider.icon}
|
||||||
class="h-5 w-5 text-indigo-600 dark:text-indigo-400"
|
class="h-5 w-5 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<span class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{provider.name}
|
{provider.name}
|
||||||
</span>
|
</span>
|
||||||
<span class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-2">
|
<span class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400 line-clamp-2">
|
||||||
{provider.description}
|
{provider.description}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -202,8 +202,8 @@
|
||||||
<%!-- Config form for whichever billing provider is being configured --%>
|
<%!-- Config form for whichever billing provider is being configured --%>
|
||||||
<%= if @configuring && find_category_id(@configuring) == "billing" do %>
|
<%= if @configuring && find_category_id(@configuring) == "billing" do %>
|
||||||
<% provider = Enum.find(category.providers, &(&1.id == @configuring)) %>
|
<% provider = Enum.find(category.providers, &(&1.id == @configuring)) %>
|
||||||
<div class="mt-6 border-t border-gray-200 pt-6 dark:border-white/10">
|
<div class="mt-6 border-t border-cool-steel-200 pt-6 dark:border-white/10">
|
||||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">
|
<h4 class="text-sm font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
Configure {provider.name}
|
Configure {provider.name}
|
||||||
</h4>
|
</h4>
|
||||||
<.form
|
<.form
|
||||||
|
|
@ -274,14 +274,14 @@
|
||||||
"rounded-md p-4",
|
"rounded-md p-4",
|
||||||
case @test_result do
|
case @test_result do
|
||||||
{:ok, _} -> "bg-green-50 dark:bg-green-900/20"
|
{:ok, _} -> "bg-green-50 dark:bg-green-900/20"
|
||||||
{:error, _} -> "bg-red-50 dark:bg-red-900/20"
|
{:error, _} -> "bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
<p class={[
|
<p class={[
|
||||||
"text-sm",
|
"text-sm",
|
||||||
case @test_result do
|
case @test_result do
|
||||||
{:ok, _} -> "text-green-800 dark:text-green-200"
|
{:ok, _} -> "text-green-800 dark:text-green-200"
|
||||||
{:error, _} -> "text-red-800 dark:text-red-200"
|
{:error, _} -> "text-sweet-salmon-800 dark:text-sweet-salmon-200"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{elem(@test_result, 1)}
|
{elem(@test_result, 1)}
|
||||||
|
|
@ -294,7 +294,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="test-connection"
|
id="test-connection"
|
||||||
phx-click="test_connection"
|
phx-click="test_connection"
|
||||||
class="rounded-md bg-white px-3 py-2 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"
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-cool-steel-900 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
{t("Test Connection")}
|
{t("Test Connection")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -302,14 +302,14 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close_config"
|
phx-click="close_config"
|
||||||
class="text-sm font-semibold text-gray-900 dark:text-white"
|
class="text-sm font-semibold text-cool-steel-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
phx-disable-with={t("Saving...")}
|
phx-disable-with={t("Saving...")}
|
||||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Save")}
|
{t("Save")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -321,11 +321,11 @@
|
||||||
|
|
||||||
<%!-- Gaiia webhook config --%>
|
<%!-- Gaiia webhook config --%>
|
||||||
<%= if @configuring == "gaiia" && @integrations["gaiia"] do %>
|
<%= if @configuring == "gaiia" && @integrations["gaiia"] do %>
|
||||||
<div class="mt-6 border-t border-gray-200 pt-6 dark:border-white/10">
|
<div class="mt-6 border-t border-cool-steel-200 pt-6 dark:border-white/10">
|
||||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h4 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Webhook Configuration")}
|
{t("Webhook Configuration")}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-300">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-300">
|
||||||
{t(
|
{t(
|
||||||
"Webhooks keep your data in sync in real-time. Without webhooks, data is only updated during scheduled syncs (every %{interval} minutes). With webhooks enabled, changes in Gaiia are reflected here within seconds.",
|
"Webhooks keep your data in sync in real-time. Without webhooks, data is only updated during scheduled syncs (every %{interval} minutes). With webhooks enabled, changes in Gaiia are reflected here within seconds.",
|
||||||
interval:
|
interval:
|
||||||
|
|
@ -336,18 +336,18 @@
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-4 rounded-md bg-gray-50 p-4 dark:bg-white/5">
|
<div class="mt-4 rounded-md bg-cool-steel-50 p-4 dark:bg-white/5">
|
||||||
<h5 class="text-xs font-semibold text-gray-700 dark:text-gray-300">
|
<h5 class="text-xs font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("What webhooks enable")}
|
{t("What webhooks enable")}
|
||||||
</h5>
|
</h5>
|
||||||
<ul class="mt-2 space-y-1.5 text-xs text-gray-600 dark:text-gray-400">
|
<ul class="mt-2 space-y-1.5 text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<li class="flex items-start gap-2">
|
<li class="flex items-start gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-user-group"
|
name="hero-user-group"
|
||||||
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400"
|
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
<span class="font-medium text-gray-700 dark:text-gray-300">
|
<span class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Account changes
|
Account changes
|
||||||
</span>
|
</span>
|
||||||
— new subscribers, status changes, and address updates are reflected instantly for accurate outage impact analysis.
|
— new subscribers, status changes, and address updates are reflected instantly for accurate outage impact analysis.
|
||||||
|
|
@ -356,10 +356,10 @@
|
||||||
<li class="flex items-start gap-2">
|
<li class="flex items-start gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-credit-card"
|
name="hero-credit-card"
|
||||||
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400"
|
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
<span class="font-medium text-gray-700 dark:text-gray-300">
|
<span class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Billing subscription changes
|
Billing subscription changes
|
||||||
</span>
|
</span>
|
||||||
— plan upgrades, cancellations, and MRR changes stay current so revenue impact estimates are always accurate.
|
— plan upgrades, cancellations, and MRR changes stay current so revenue impact estimates are always accurate.
|
||||||
|
|
@ -368,10 +368,10 @@
|
||||||
<li class="flex items-start gap-2">
|
<li class="flex items-start gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-wrench-screwdriver"
|
name="hero-wrench-screwdriver"
|
||||||
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400"
|
class="mt-0.5 h-3.5 w-3.5 shrink-0 text-cool-steel-400"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
<span class="font-medium text-gray-700 dark:text-gray-300">
|
<span class="font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
Inventory item changes
|
Inventory item changes
|
||||||
</span>
|
</span>
|
||||||
— equipment assignments, IP changes, and new installs are matched to your monitored devices automatically.
|
— equipment assignments, IP changes, and new installs are matched to your monitored devices automatically.
|
||||||
|
|
@ -382,7 +382,7 @@
|
||||||
|
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
<label class="block text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Webhook URL")}
|
{t("Webhook URL")}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-1 flex items-center gap-2">
|
<div class="mt-1 flex items-center gap-2">
|
||||||
|
|
@ -391,7 +391,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
value={webhook_url(@organization.id)}
|
value={webhook_url(@organization.id)}
|
||||||
class="block w-full rounded-md border-gray-300 bg-gray-50 py-1.5 text-sm text-gray-900 shadow-xs sm:leading-6 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
class="block w-full rounded-md border-cool-steel-300 bg-cool-steel-50 py-1.5 text-sm text-cool-steel-900 shadow-xs sm:leading-6 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -399,7 +399,7 @@
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
data-target="#gaiia-webhook-url"
|
data-target="#gaiia-webhook-url"
|
||||||
id="copy-webhook-url"
|
id="copy-webhook-url"
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-gray-300 dark:ring-white/10 dark:hover:bg-white/20"
|
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-cool-steel-700 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-cool-steel-300 dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<.icon name="hero-clipboard" class="h-3.5 w-3.5" /> Copy
|
<.icon name="hero-clipboard" class="h-3.5 w-3.5" /> Copy
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -407,7 +407,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-medium text-gray-700 dark:text-gray-300">
|
<label class="block text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Webhook Secret")}
|
{t("Webhook Secret")}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-1 flex items-center gap-2">
|
<div class="mt-1 flex items-center gap-2">
|
||||||
|
|
@ -416,7 +416,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
value={get_credential(@integrations["gaiia"], "webhook_secret")}
|
value={get_credential(@integrations["gaiia"], "webhook_secret")}
|
||||||
class="block w-full rounded-md border-gray-300 bg-gray-50 py-1.5 font-mono text-sm text-gray-900 shadow-xs sm:leading-6 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
class="block w-full rounded-md border-cool-steel-300 bg-cool-steel-50 py-1.5 font-mono text-sm text-cool-steel-900 shadow-xs sm:leading-6 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -424,12 +424,12 @@
|
||||||
phx-update="ignore"
|
phx-update="ignore"
|
||||||
data-target="#gaiia-webhook-secret"
|
data-target="#gaiia-webhook-secret"
|
||||||
id="copy-webhook-secret"
|
id="copy-webhook-secret"
|
||||||
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-white/10 dark:text-gray-300 dark:ring-white/10 dark:hover:bg-white/20"
|
class="inline-flex items-center gap-1 rounded-md bg-white px-2.5 py-1.5 text-xs font-medium text-cool-steel-700 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-cool-steel-300 dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<.icon name="hero-clipboard" class="h-3.5 w-3.5" /> Copy
|
<.icon name="hero-clipboard" class="h-3.5 w-3.5" /> Copy
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Used to verify that incoming webhooks are genuinely from Gaiia. Keep this secret."
|
"Used to verify that incoming webhooks are genuinely from Gaiia. Keep this secret."
|
||||||
)}
|
)}
|
||||||
|
|
@ -444,18 +444,18 @@
|
||||||
"Are you sure? Any existing Gaiia webhook using the current secret will stop working."
|
"Are you sure? Any existing Gaiia webhook using the current secret will stop working."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
class="text-xs text-sweet-salmon-600 hover:text-sweet-salmon-700 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
{t("Regenerate Secret")}
|
{t("Regenerate Secret")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-md bg-blue-50 p-3 dark:bg-blue-900/20">
|
<div class="rounded-md bg-cerulean-50 p-3 dark:bg-cerulean-900/20">
|
||||||
<h5 class="text-xs font-medium text-blue-800 dark:text-blue-300">
|
<h5 class="text-xs font-medium text-cerulean-800 dark:text-cerulean-300">
|
||||||
{t("Setup Instructions")}
|
{t("Setup Instructions")}
|
||||||
</h5>
|
</h5>
|
||||||
<ol class="mt-2 list-inside list-decimal space-y-1.5 text-xs text-blue-700 dark:text-blue-400">
|
<ol class="mt-2 list-inside list-decimal space-y-1.5 text-xs text-cerulean-700 dark:text-cerulean-400">
|
||||||
<li>
|
<li>
|
||||||
In your Gaiia admin panel, navigate to
|
In your Gaiia admin panel, navigate to
|
||||||
<span class="font-semibold">Settings → Webhooks</span>
|
<span class="font-semibold">Settings → Webhooks</span>
|
||||||
|
|
@ -479,7 +479,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li>Save the webhook configuration in Gaiia</li>
|
<li>Save the webhook configuration in Gaiia</li>
|
||||||
</ol>
|
</ol>
|
||||||
<p class="mt-3 text-xs text-blue-600 dark:text-blue-400/80">
|
<p class="mt-3 text-xs text-cerulean-600 dark:text-cerulean-400/80">
|
||||||
{t(
|
{t(
|
||||||
"Once configured, Gaiia will send updates to Towerops automatically. You can verify it's working by making a change in Gaiia and checking that it appears here within a few seconds."
|
"Once configured, Gaiia will send updates to Towerops automatically. You can verify it's working by making a change in Gaiia and checking that it appears here within a few seconds."
|
||||||
)}
|
)}
|
||||||
|
|
@ -495,21 +495,21 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div
|
<div
|
||||||
:for={provider <- category.providers}
|
:for={provider <- category.providers}
|
||||||
class="rounded-lg border border-gray-200 bg-white p-6 dark:border-white/10 dark:bg-white/5"
|
class="rounded-lg border border-cool-steel-200 bg-white p-6 dark:border-white/10 dark:bg-white/5"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-50 dark:bg-indigo-900/30">
|
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-cerulean-50 dark:bg-cerulean-900/30">
|
||||||
<.icon
|
<.icon
|
||||||
name={provider.icon}
|
name={provider.icon}
|
||||||
class="h-6 w-6 text-indigo-600 dark:text-indigo-400"
|
class="h-6 w-6 text-cerulean-600 dark:text-cerulean-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{provider.name}
|
{provider.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{provider.description}
|
{provider.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -520,14 +520,15 @@
|
||||||
if(integration.enabled,
|
if(integration.enabled,
|
||||||
do:
|
do:
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
|
||||||
else: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
else:
|
||||||
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{if integration.enabled, do: "Enabled", else: "Disabled"}
|
{if integration.enabled, do: "Enabled", else: "Disabled"}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<%= if integration.last_synced_at do %>
|
<%= if integration.last_synced_at do %>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Last synced:")}
|
{t("Last synced:")}
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={integration.last_synced_at}
|
datetime={integration.last_synced_at}
|
||||||
|
|
@ -545,20 +546,20 @@
|
||||||
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
||||||
|
|
||||||
"partial" ->
|
"partial" ->
|
||||||
"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-700 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
"failed" ->
|
"failed" ->
|
||||||
"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
"bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{String.capitalize(integration.last_sync_status)}
|
{String.capitalize(integration.last_sync_status)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
· Syncs every {humanize_interval(integration.sync_interval_minutes)}
|
· Syncs every {humanize_interval(integration.sync_interval_minutes)}
|
||||||
<%= if integration.last_synced_at do %>
|
<%= if integration.last_synced_at do %>
|
||||||
· Next in ~{next_sync_minutes(
|
· Next in ~{next_sync_minutes(
|
||||||
|
|
@ -573,13 +574,13 @@
|
||||||
navigate={
|
navigate={
|
||||||
~p"/orgs/#{@organization.slug}/settings/integrations/preseem/devices"
|
~p"/orgs/#{@organization.slug}/settings/integrations/preseem/devices"
|
||||||
}
|
}
|
||||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Manage Devices →
|
Manage Devices →
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights?source=preseem"}
|
navigate={~p"/insights?source=preseem"}
|
||||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Network Insights →
|
Network Insights →
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -596,10 +597,10 @@
|
||||||
phx-click="toggle_enabled"
|
phx-click="toggle_enabled"
|
||||||
phx-value-provider={provider.id}
|
phx-value-provider={provider.id}
|
||||||
class={[
|
class={[
|
||||||
"relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2",
|
"relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-cerulean-600 focus:ring-offset-2",
|
||||||
if(integration.enabled,
|
if(integration.enabled,
|
||||||
do: "bg-indigo-600",
|
do: "bg-cerulean-600",
|
||||||
else: "bg-gray-200 dark:bg-gray-700"
|
else: "bg-cool-steel-200 dark:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -615,7 +616,7 @@
|
||||||
id={"configure-#{provider.id}"}
|
id={"configure-#{provider.id}"}
|
||||||
phx-click="configure"
|
phx-click="configure"
|
||||||
phx-value-provider={provider.id}
|
phx-value-provider={provider.id}
|
||||||
class="rounded-md bg-white px-3 py-2 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"
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-cool-steel-900 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
{t("Configure")}
|
{t("Configure")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -623,7 +624,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @configuring == provider.id do %>
|
<%= if @configuring == provider.id do %>
|
||||||
<div class="mt-6 border-t border-gray-200 pt-6 dark:border-white/10">
|
<div class="mt-6 border-t border-cool-steel-200 pt-6 dark:border-white/10">
|
||||||
<.form
|
<.form
|
||||||
for={@form}
|
for={@form}
|
||||||
id={"#{provider.id}-form"}
|
id={"#{provider.id}-form"}
|
||||||
|
|
@ -697,14 +698,14 @@
|
||||||
"rounded-md p-4",
|
"rounded-md p-4",
|
||||||
case @test_result do
|
case @test_result do
|
||||||
{:ok, _} -> "bg-green-50 dark:bg-green-900/20"
|
{:ok, _} -> "bg-green-50 dark:bg-green-900/20"
|
||||||
{:error, _} -> "bg-red-50 dark:bg-red-900/20"
|
{:error, _} -> "bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
<p class={[
|
<p class={[
|
||||||
"text-sm",
|
"text-sm",
|
||||||
case @test_result do
|
case @test_result do
|
||||||
{:ok, _} -> "text-green-800 dark:text-green-200"
|
{:ok, _} -> "text-green-800 dark:text-green-200"
|
||||||
{:error, _} -> "text-red-800 dark:text-red-200"
|
{:error, _} -> "text-sweet-salmon-800 dark:text-sweet-salmon-200"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{elem(@test_result, 1)}
|
{elem(@test_result, 1)}
|
||||||
|
|
@ -717,7 +718,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="test-connection"
|
id="test-connection"
|
||||||
phx-click="test_connection"
|
phx-click="test_connection"
|
||||||
class="rounded-md bg-white px-3 py-2 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"
|
class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-cool-steel-900 shadow-xs ring-1 ring-inset ring-cool-steel-300 hover:bg-cool-steel-50 dark:bg-white/10 dark:text-white dark:ring-white/10 dark:hover:bg-white/20"
|
||||||
>
|
>
|
||||||
{t("Test Connection")}
|
{t("Test Connection")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -725,14 +726,14 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="close_config"
|
phx-click="close_config"
|
||||||
class="text-sm font-semibold text-gray-900 dark:text-white"
|
class="text-sm font-semibold text-cool-steel-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
phx-disable-with={t("Saving...")}
|
phx-disable-with={t("Saving...")}
|
||||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Save")}
|
{t("Save")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -108,13 +108,13 @@ defmodule ToweropsWeb.Org.PreseemDevicesLive do
|
||||||
defp match_confidence_classes(confidence) do
|
defp match_confidence_classes(confidence) do
|
||||||
case confidence do
|
case confidence do
|
||||||
"unmatched" ->
|
"unmatched" ->
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
"ambiguous" ->
|
"ambiguous" ->
|
||||||
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
|
|
||||||
"manual" ->
|
"manual" ->
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@
|
||||||
current_scope={@current_scope}
|
current_scope={@current_scope}
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<.breadcrumb items={[
|
<.breadcrumb items={[
|
||||||
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
||||||
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
||||||
%{label: "Integrations", navigate: ~p"/orgs/#{@organization.slug}/settings/integrations"},
|
%{label: "Integrations", navigate: ~p"/orgs/#{@organization.slug}/settings/integrations"},
|
||||||
%{label: "Preseem"}
|
%{label: "Preseem"}
|
||||||
]} />
|
]} />
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Preseem Devices")}
|
{t("Preseem Devices")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"For each Preseem access point, find and link the matching TowerOps device. This enables subscriber impact data when access points go down."
|
"For each Preseem access point, find and link the matching TowerOps device. This enables subscriber impact data when access points go down."
|
||||||
)}
|
)}
|
||||||
|
|
@ -21,17 +21,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Sub-navigation tabs --%>
|
<%!-- Sub-navigation tabs --%>
|
||||||
<div class="mt-4 border-b border-gray-200 dark:border-white/10">
|
<div class="mt-4 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<nav class="flex space-x-6">
|
<nav class="flex space-x-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/preseem/devices"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations/preseem/devices"}
|
||||||
class="border-b-2 border-indigo-500 px-1 pb-3 text-sm font-medium text-indigo-600 dark:text-indigo-400"
|
class="border-b-2 border-cerulean-500 px-1 pb-3 text-sm font-medium text-cerulean-600 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Devices")}
|
{t("Devices")}
|
||||||
</.link>
|
</.link>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights?source=preseem"}
|
navigate={~p"/insights?source=preseem"}
|
||||||
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-white"
|
class="border-b-2 border-transparent px-1 pb-3 text-sm font-medium text-cool-steel-500 hover:border-cool-steel-300 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:border-cool-steel-600 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Insights")}
|
{t("Insights")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -47,9 +47,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-full px-3 py-1 text-xs font-medium",
|
"rounded-full px-3 py-1 text-xs font-medium",
|
||||||
if(@filter == f,
|
if(@filter == f,
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300",
|
do: "bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/40 dark:text-cerulean-300",
|
||||||
else:
|
else:
|
||||||
"bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-white/5 dark:text-gray-400 dark:hover:bg-white/10"
|
"bg-cool-steel-100 text-cool-steel-600 hover:bg-cool-steel-200 dark:bg-white/5 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -62,13 +62,16 @@
|
||||||
<%= if @access_points == [] do %>
|
<%= if @access_points == [] do %>
|
||||||
<div
|
<div
|
||||||
id="empty-state"
|
id="empty-state"
|
||||||
class="rounded-lg border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-700"
|
class="rounded-lg border-2 border-dashed border-cool-steel-300 p-12 text-center dark:border-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-signal" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
|
<.icon
|
||||||
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
name="hero-signal"
|
||||||
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No access points found")}
|
{t("No access points found")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= case @filter do %>
|
<%= case @filter do %>
|
||||||
<% "unmatched" -> %>
|
<% "unmatched" -> %>
|
||||||
{t("All access points are matched. Nice!")}
|
{t("All access points are matched. Nice!")}
|
||||||
|
|
@ -82,48 +85,48 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-white/5">
|
<thead class="bg-cool-steel-50 dark:bg-white/5">
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Access Point")}
|
{t("Access Point")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 md:table-cell"
|
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 md:table-cell"
|
||||||
>
|
>
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 sm:table-cell"
|
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 sm:table-cell"
|
||||||
>
|
>
|
||||||
{t("Status")}
|
{t("Status")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 md:table-cell"
|
class="hidden px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 md:table-cell"
|
||||||
>
|
>
|
||||||
{t("Linked Device")}
|
{t("Linked Device")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
||||||
<%= for ap <- @access_points do %>
|
<%= for ap <- @access_points do %>
|
||||||
<tr
|
<tr
|
||||||
id={"ap-row-#{ap.id}"}
|
id={"ap-row-#{ap.id}"}
|
||||||
class={[
|
class={[
|
||||||
"hover:bg-gray-50 dark:hover:bg-white/5",
|
"hover:bg-cool-steel-50 dark:hover:bg-white/5",
|
||||||
if(is_nil(ap.device), do: "cursor-pointer")
|
if(is_nil(ap.device), do: "cursor-pointer")
|
||||||
]}
|
]}
|
||||||
phx-click={if(is_nil(ap.device), do: "start_link")}
|
phx-click={if(is_nil(ap.device), do: "start_link")}
|
||||||
|
|
@ -132,16 +135,16 @@
|
||||||
<td class="px-4 py-4 text-sm">
|
<td class="px-4 py-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<span class="break-all font-medium text-gray-900 dark:text-white">
|
<span class="break-all font-medium text-cool-steel-900 dark:text-white">
|
||||||
{ap.name || "Unnamed"}
|
{ap.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<%= if Map.has_key?(@suggestions, ap.id) do %>
|
<%= if Map.has_key?(@suggestions, ap.id) do %>
|
||||||
<span class="inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-400">
|
<span class="inline-flex items-center rounded-full bg-wheat-100 px-2 py-0.5 text-xs font-medium text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400">
|
||||||
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" /> Match found
|
<.icon name="hero-light-bulb-mini" class="mr-0.5 h-3 w-3" /> Match found
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<span class="mt-0.5 block text-xs text-gray-400 dark:text-gray-500 sm:hidden">
|
<span class="mt-0.5 block text-xs text-cool-steel-400 dark:text-cool-steel-500 sm:hidden">
|
||||||
{ap.preseem_id}
|
{ap.preseem_id}
|
||||||
</span>
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
|
|
@ -152,13 +155,13 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400 md:table-cell">
|
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400 md:table-cell">
|
||||||
<%= if ap.ip_address do %>
|
<%= if ap.ip_address do %>
|
||||||
<a
|
<a
|
||||||
href={"http://#{ap.ip_address}"}
|
href={"http://#{ap.ip_address}"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="font-mono hover:text-indigo-600 dark:hover:text-indigo-400"
|
class="font-mono hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{ap.ip_address}
|
{ap.ip_address}
|
||||||
|
|
@ -175,17 +178,17 @@
|
||||||
{humanize_confidence(ap.match_confidence)}
|
{humanize_confidence(ap.match_confidence)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-gray-500 dark:text-gray-400 md:table-cell">
|
<td class="hidden whitespace-nowrap px-4 py-4 text-sm text-cool-steel-500 dark:text-cool-steel-400 md:table-cell">
|
||||||
<%= if ap.device do %>
|
<%= if ap.device do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{ap.device.id}"}
|
navigate={~p"/devices/#{ap.device.id}"}
|
||||||
class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{ap.device.name || ap.device.ip_address}
|
{ap.device.name || ap.device.ip_address}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-500 dark:bg-white/10 dark:text-gray-400">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2.5 py-0.5 text-xs font-medium text-cool-steel-500 dark:bg-white/10 dark:text-cool-steel-400">
|
||||||
Not linked
|
Not linked
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -198,7 +201,7 @@
|
||||||
phx-click="unlink_device"
|
phx-click="unlink_device"
|
||||||
phx-value-ap-id={ap.id}
|
phx-value-ap-id={ap.id}
|
||||||
data-confirm={t("Unlink this device from the Preseem access point?")}
|
data-confirm={t("Unlink this device from the Preseem access point?")}
|
||||||
class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-800 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{t("Unlink")}
|
{t("Unlink")}
|
||||||
|
|
@ -209,7 +212,7 @@
|
||||||
id={"link-ap-#{ap.id}"}
|
id={"link-ap-#{ap.id}"}
|
||||||
phx-click="start_link"
|
phx-click="start_link"
|
||||||
phx-value-ap-id={ap.id}
|
phx-value-ap-id={ap.id}
|
||||||
class="text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
{t("Link")}
|
{t("Link")}
|
||||||
|
|
@ -219,19 +222,19 @@
|
||||||
</tr>
|
</tr>
|
||||||
<%!-- Inline linking row --%>
|
<%!-- Inline linking row --%>
|
||||||
<%= if ap.id == @linking_ap_id do %>
|
<%= if ap.id == @linking_ap_id do %>
|
||||||
<tr id={"linking-row-#{ap.id}"} class="bg-indigo-50 dark:bg-indigo-900/20">
|
<tr id={"linking-row-#{ap.id}"} class="bg-cerulean-50 dark:bg-cerulean-900/20">
|
||||||
<td colspan="5" class="px-4 py-4">
|
<td colspan="5" class="px-4 py-4">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
<p class="mb-2 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
Find a TowerOps device to link to
|
Find a TowerOps device to link to
|
||||||
<span class="text-indigo-600 dark:text-indigo-400">{ap.name}</span>
|
<span class="text-cerulean-600 dark:text-cerulean-400">{ap.name}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%!-- Suggestions --%>
|
<%!-- Suggestions --%>
|
||||||
<%= if suggestions = @suggestions[ap.id] do %>
|
<%= if suggestions = @suggestions[ap.id] do %>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<p class="mb-1 text-xs font-medium text-amber-700 dark:text-amber-400">
|
<p class="mb-1 text-xs font-medium text-wheat-700 dark:text-wheat-400">
|
||||||
<.icon name="hero-light-bulb-mini" class="mr-0.5 inline h-3 w-3" />
|
<.icon name="hero-light-bulb-mini" class="mr-0.5 inline h-3 w-3" />
|
||||||
{t("Suggested match (same IP):")}
|
{t("Suggested match (same IP):")}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -242,11 +245,11 @@
|
||||||
phx-click="link_device"
|
phx-click="link_device"
|
||||||
phx-value-ap-id={ap.id}
|
phx-value-ap-id={ap.id}
|
||||||
phx-value-device-id={suggestion.device.id}
|
phx-value-device-id={suggestion.device.id}
|
||||||
class="inline-flex items-center gap-1.5 rounded-md border border-amber-300 bg-amber-50 px-2.5 py-1.5 text-xs font-medium text-amber-800 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-300 dark:hover:bg-amber-900/40"
|
class="inline-flex items-center gap-1.5 rounded-md border border-wheat-300 bg-wheat-50 px-2.5 py-1.5 text-xs font-medium text-wheat-800 hover:bg-wheat-100 dark:border-wheat-700 dark:bg-wheat-900/20 dark:text-wheat-300 dark:hover:bg-wheat-900/40"
|
||||||
>
|
>
|
||||||
{suggestion.device.name || suggestion.device.ip_address ||
|
{suggestion.device.name || suggestion.device.ip_address ||
|
||||||
"Unnamed"}
|
"Unnamed"}
|
||||||
<span class="rounded bg-amber-200 px-1 text-amber-700 dark:bg-amber-800 dark:text-amber-300">
|
<span class="rounded bg-wheat-200 px-1 text-wheat-700 dark:bg-wheat-800 dark:text-wheat-300">
|
||||||
{suggestion.match_type}
|
{suggestion.match_type}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -269,13 +272,13 @@
|
||||||
placeholder={t("Search by device name or IP...")}
|
placeholder={t("Search by device name or IP...")}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
phx-debounce="300"
|
phx-debounce="300"
|
||||||
class="block w-full rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
class="block w-full rounded-md border-cool-steel-300 text-sm shadow-sm focus:border-cerulean-500 focus:ring-cerulean-500 dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<%= if @search_results != [] do %>
|
<%= if @search_results != [] do %>
|
||||||
<div class="mt-2 max-h-48 overflow-y-auto rounded-md border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800">
|
<div class="mt-2 max-h-48 overflow-y-auto rounded-md border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800">
|
||||||
<ul class="divide-y divide-gray-200 dark:divide-white/5">
|
<ul class="divide-y divide-cool-steel-200 dark:divide-white/5">
|
||||||
<li :for={device <- @search_results}>
|
<li :for={device <- @search_results}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -283,15 +286,15 @@
|
||||||
phx-click="link_device"
|
phx-click="link_device"
|
||||||
phx-value-ap-id={ap.id}
|
phx-value-ap-id={ap.id}
|
||||||
phx-value-device-id={device.id}
|
phx-value-device-id={device.id}
|
||||||
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-gray-50 dark:hover:bg-white/5"
|
class="flex w-full items-center justify-between px-3 py-2 text-left text-sm hover:bg-cool-steel-50 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{device.name || "Unnamed"}
|
{device.name || "Unnamed"}
|
||||||
</span>
|
</span>
|
||||||
<span class="flex items-center gap-2 text-gray-500 dark:text-gray-400">
|
<span class="flex items-center gap-2 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{device.ip_address}
|
{device.ip_address}
|
||||||
<%= if device.site do %>
|
<%= if device.site do %>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{device.site.name}
|
{device.site.name}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -303,7 +306,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @device_search != "" and String.length(@device_search) >= 2 and @search_results == [] do %>
|
<%= if @device_search != "" and String.length(@device_search) >= 2 and @search_results == [] do %>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No devices found matching "{@device_search}"
|
No devices found matching "{@device_search}"
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -312,7 +315,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="cancel-link"
|
id="cancel-link"
|
||||||
phx-click="cancel_link"
|
phx-click="cancel_link"
|
||||||
class="flex-shrink-0 self-start text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
class="flex-shrink-0 self-start text-sm text-cool-steel-500 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -133,16 +133,16 @@ defmodule ToweropsWeb.Org.PreseemInsightsLive do
|
||||||
defp urgency_classes(urgency) do
|
defp urgency_classes(urgency) do
|
||||||
case urgency do
|
case urgency do
|
||||||
"critical" ->
|
"critical" ->
|
||||||
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
"bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
"warning" ->
|
"warning" ->
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
"info" ->
|
"info" ->
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
"bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
current_scope={@current_scope}
|
current_scope={@current_scope}
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
<div class="border-b border-cool-steel-200 pb-5 dark:border-white/5">
|
||||||
<.breadcrumb items={[
|
<.breadcrumb items={[
|
||||||
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
||||||
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
%{label: "Settings", navigate: ~p"/orgs/#{@organization.slug}/settings"},
|
||||||
|
|
@ -12,10 +12,10 @@
|
||||||
]} />
|
]} />
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Network Insights")}
|
{t("Network Insights")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Proactive network health observations generated from Preseem data analysis.")}
|
{t("Proactive network health observations generated from Preseem data analysis.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Filter bar --%>
|
<%!-- Filter bar --%>
|
||||||
<div class="mt-6 flex flex-wrap items-center gap-4 border-b border-gray-200 pb-4 dark:border-white/10">
|
<div class="mt-6 flex flex-wrap items-center gap-4 border-b border-cool-steel-200 pb-4 dark:border-white/10">
|
||||||
<%!-- Status filter tabs --%>
|
<%!-- Status filter tabs --%>
|
||||||
<nav class="flex space-x-4">
|
<nav class="flex space-x-4">
|
||||||
<.link
|
<.link
|
||||||
|
|
@ -34,9 +34,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium",
|
"rounded-md px-3 py-1.5 text-sm font-medium",
|
||||||
if(@filter_status == "active",
|
if(@filter_status == "active",
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
|
do:
|
||||||
|
"bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-300"
|
"text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -50,9 +51,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-3 py-1.5 text-sm font-medium",
|
"rounded-md px-3 py-1.5 text-sm font-medium",
|
||||||
if(@filter_status == "dismissed",
|
if(@filter_status == "dismissed",
|
||||||
do: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
|
do:
|
||||||
|
"bg-cerulean-100 text-cerulean-700 dark:bg-cerulean-900/30 dark:text-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-300"
|
"text-cool-steel-500 hover:bg-cool-steel-100 hover:text-cool-steel-700 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-cool-steel-300"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -61,8 +63,10 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<%!-- Urgency filter --%>
|
<%!-- Urgency filter --%>
|
||||||
<div class="flex items-center gap-2 border-l border-gray-200 pl-4 dark:border-white/10">
|
<div class="flex items-center gap-2 border-l border-cool-steel-200 pl-4 dark:border-white/10">
|
||||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400">Urgency:</span>
|
<span class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Urgency:
|
||||||
|
</span>
|
||||||
<.link
|
<.link
|
||||||
id="filter-urgency-all"
|
id="filter-urgency-all"
|
||||||
patch={
|
patch={
|
||||||
|
|
@ -71,8 +75,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(is_nil(@filter_urgency),
|
if(is_nil(@filter_urgency),
|
||||||
do: "bg-gray-200 text-gray-800 dark:bg-white/20 dark:text-white",
|
do: "bg-cool-steel-200 text-cool-steel-800 dark:bg-white/20 dark:text-white",
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -86,8 +91,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(@filter_urgency == "critical",
|
if(@filter_urgency == "critical",
|
||||||
do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
|
do:
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
"bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -101,8 +108,9 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(@filter_urgency == "warning",
|
if(@filter_urgency == "warning",
|
||||||
do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",
|
do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400",
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -116,8 +124,10 @@
|
||||||
class={[
|
class={[
|
||||||
"rounded-md px-2 py-1 text-xs font-medium",
|
"rounded-md px-2 py-1 text-xs font-medium",
|
||||||
if(@filter_urgency == "info",
|
if(@filter_urgency == "info",
|
||||||
do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
|
do:
|
||||||
else: "text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/10"
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400",
|
||||||
|
else:
|
||||||
|
"text-cool-steel-500 hover:bg-cool-steel-100 dark:text-cool-steel-400 dark:hover:bg-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -130,16 +140,16 @@
|
||||||
<%= if any_selected?(@selected_ids) do %>
|
<%= if any_selected?(@selected_ids) do %>
|
||||||
<div
|
<div
|
||||||
id="bulk-actions"
|
id="bulk-actions"
|
||||||
class="mt-4 flex items-center gap-3 rounded-lg bg-indigo-50 px-4 py-3 dark:bg-indigo-900/20"
|
class="mt-4 flex items-center gap-3 rounded-lg bg-cerulean-50 px-4 py-3 dark:bg-cerulean-900/20"
|
||||||
>
|
>
|
||||||
<span class="text-sm font-medium text-indigo-700 dark:text-indigo-300">
|
<span class="text-sm font-medium text-cerulean-700 dark:text-cerulean-300">
|
||||||
{MapSet.size(@selected_ids)} selected
|
{MapSet.size(@selected_ids)} selected
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="deselect-all-btn"
|
id="deselect-all-btn"
|
||||||
phx-click="deselect_all"
|
phx-click="deselect_all"
|
||||||
class="text-sm text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-sm text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Deselect All")}
|
{t("Deselect All")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -147,7 +157,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="bulk-dismiss-btn"
|
id="bulk-dismiss-btn"
|
||||||
phx-click="bulk_dismiss"
|
phx-click="bulk_dismiss"
|
||||||
class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-3 py-1.5 text-sm font-medium text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Dismiss Selected")}
|
{t("Dismiss Selected")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -159,16 +169,16 @@
|
||||||
<%= if @insights == [] do %>
|
<%= if @insights == [] do %>
|
||||||
<div
|
<div
|
||||||
id="empty-state"
|
id="empty-state"
|
||||||
class="rounded-lg border-2 border-dashed border-gray-300 p-12 text-center dark:border-gray-700"
|
class="rounded-lg border-2 border-dashed border-cool-steel-300 p-12 text-center dark:border-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-light-bulb"
|
name="hero-light-bulb"
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No insights found")}
|
{t("No insights found")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= cond do %>
|
<%= cond do %>
|
||||||
<% @filter_status == "dismissed" -> %>
|
<% @filter_status == "dismissed" -> %>
|
||||||
{t("No dismissed insights.")}
|
{t("No dismissed insights.")}
|
||||||
|
|
@ -186,7 +196,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
id="select-all-btn"
|
id="select-all-btn"
|
||||||
phx-click="select_all"
|
phx-click="select_all"
|
||||||
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Select All")}
|
{t("Select All")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -197,11 +207,11 @@
|
||||||
:for={insight <- @insights}
|
:for={insight <- @insights}
|
||||||
id={"insight-#{insight.id}"}
|
id={"insight-#{insight.id}"}
|
||||||
class={[
|
class={[
|
||||||
"rounded-lg border bg-white p-4 transition-shadow hover:shadow-md dark:bg-gray-800/50",
|
"rounded-lg border bg-white p-4 transition-shadow hover:shadow-md dark:bg-cool-steel-800/50",
|
||||||
if(selected?(@selected_ids, insight.id),
|
if(selected?(@selected_ids, insight.id),
|
||||||
do:
|
do:
|
||||||
"border-indigo-300 ring-1 ring-indigo-200 dark:border-indigo-600 dark:ring-indigo-800",
|
"border-cerulean-300 ring-1 ring-cerulean-200 dark:border-cerulean-600 dark:ring-cerulean-800",
|
||||||
else: "border-gray-200 dark:border-white/10"
|
else: "border-cool-steel-200 dark:border-white/10"
|
||||||
)
|
)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
@ -218,8 +228,9 @@
|
||||||
"flex h-5 w-5 items-center justify-center rounded border",
|
"flex h-5 w-5 items-center justify-center rounded border",
|
||||||
if(selected?(@selected_ids, insight.id),
|
if(selected?(@selected_ids, insight.id),
|
||||||
do:
|
do:
|
||||||
"border-indigo-600 bg-indigo-600 dark:border-indigo-500 dark:bg-indigo-500",
|
"border-cerulean-600 bg-cerulean-600 dark:border-cerulean-500 dark:bg-cerulean-500",
|
||||||
else: "border-gray-300 bg-white dark:border-gray-600 dark:bg-gray-700"
|
else:
|
||||||
|
"border-cool-steel-300 bg-white dark:border-cool-steel-600 dark:bg-cool-steel-700"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<%= if selected?(@selected_ids, insight.id) do %>
|
<%= if selected?(@selected_ids, insight.id) do %>
|
||||||
|
|
@ -239,23 +250,23 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<%!-- Type badge --%>
|
<%!-- Type badge --%>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
<span class="inline-flex items-center rounded-full bg-cool-steel-100 px-2 py-0.5 text-xs font-medium text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{String.replace(insight.type, "_", " ")}
|
{String.replace(insight.type, "_", " ")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-1 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-1 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{insight.title}
|
{insight.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<%= if insight.description do %>
|
<%= if insight.description do %>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{insight.description}
|
{insight.description}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if insight.llm_summary do %>
|
<%= if insight.llm_summary do %>
|
||||||
<div class="mt-2 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-sm text-blue-900 dark:border-blue-900/40 dark:bg-blue-950/40 dark:text-blue-100">
|
<div class="mt-2 rounded-md border border-cerulean-200 bg-cerulean-50 px-3 py-2 text-sm text-cerulean-900 dark:border-cerulean-900/40 dark:bg-cerulean-950/40 dark:text-cerulean-100">
|
||||||
<p class="font-medium">{t("Summary")}</p>
|
<p class="font-medium">{t("Summary")}</p>
|
||||||
<p>{insight.llm_summary}</p>
|
<p>{insight.llm_summary}</p>
|
||||||
<%= if insight.recommended_action do %>
|
<%= if insight.recommended_action do %>
|
||||||
|
|
@ -264,13 +275,13 @@
|
||||||
{insight.recommended_action}
|
{insight.recommended_action}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p class="mt-1 text-xs text-blue-700 dark:text-blue-300">
|
<p class="mt-1 text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
AI-generated{if insight.llm_model, do: " · " <> insight.llm_model, else: ""}
|
AI-generated{if insight.llm_model, do: " · " <> insight.llm_model, else: ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="mt-2 flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
|
<div class="mt-2 flex flex-wrap items-center gap-3 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%!-- Access point --%>
|
<%!-- Access point --%>
|
||||||
<%= if insight.preseem_access_point do %>
|
<%= if insight.preseem_access_point do %>
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
|
|
@ -283,7 +294,7 @@
|
||||||
<%= if insight.device do %>
|
<%= if insight.device do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{insight.device.id}"}
|
navigate={~p"/devices/#{insight.device.id}"}
|
||||||
class="flex items-center gap-1 text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="flex items-center gap-1 text-cerulean-600 hover:text-cerulean-800 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
||||||
{insight.device.name || "Device"}
|
{insight.device.name || "Device"}
|
||||||
|
|
@ -309,7 +320,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="dismiss"
|
phx-click="dismiss"
|
||||||
phx-value-id={insight.id}
|
phx-value-id={insight.id}
|
||||||
class="flex-shrink-0 rounded-md px-2.5 py-1.5 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-white"
|
class="flex-shrink-0 rounded-md px-2.5 py-1.5 text-xs font-medium text-cool-steel-600 hover:bg-cool-steel-100 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:bg-white/10 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Dismiss")}
|
{t("Dismiss")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,27 @@
|
||||||
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
%{label: "Dashboard", navigate: ~p"/dashboard"},
|
||||||
%{label: "Settings"}
|
%{label: "Settings"}
|
||||||
]} />
|
]} />
|
||||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
<h1 class="text-3xl font-semibold tracking-tight text-cool-steel-900 dark:text-white">
|
||||||
{t("Organization Settings")}
|
{t("Organization Settings")}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Manage organization defaults, integrations, and configuration")}
|
{t("Manage organization defaults, integrations, and configuration")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<header class="border-b border-gray-200 dark:border-white/5">
|
<header class="border-b border-cool-steel-200 dark:border-white/5">
|
||||||
<nav class="flex overflow-x-auto py-4">
|
<nav class="flex overflow-x-auto py-4">
|
||||||
<ul
|
<ul
|
||||||
role="list"
|
role="list"
|
||||||
class="flex min-w-full flex-none gap-x-6 px-4 text-sm/6 font-semibold text-gray-500 sm:px-6 lg:px-8 dark:text-gray-400"
|
class="flex min-w-full flex-none gap-x-6 px-4 text-sm/6 font-semibold text-cool-steel-500 sm:px-6 lg:px-8 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=general"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=general"}
|
||||||
class={
|
class={
|
||||||
if @active_tab == "general", do: "text-indigo-600 dark:text-indigo-400", else: ""
|
if @active_tab == "general",
|
||||||
|
do: "text-cerulean-600 dark:text-cerulean-400",
|
||||||
|
else: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("General")}
|
{t("General")}
|
||||||
|
|
@ -35,7 +37,9 @@
|
||||||
<li>
|
<li>
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=snmp"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=snmp"}
|
||||||
class={if @active_tab == "snmp", do: "text-indigo-600 dark:text-indigo-400", else: ""}
|
class={
|
||||||
|
if @active_tab == "snmp", do: "text-cerulean-600 dark:text-cerulean-400", else: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("SNMP")}
|
{t("SNMP")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -47,7 +51,7 @@
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=mikrotik"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=mikrotik"}
|
||||||
class={
|
class={
|
||||||
if @active_tab == "mikrotik",
|
if @active_tab == "mikrotik",
|
||||||
do: "text-indigo-600 dark:text-indigo-400",
|
do: "text-cerulean-600 dark:text-cerulean-400",
|
||||||
else: ""
|
else: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -60,7 +64,7 @@
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=agents"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=agents"}
|
||||||
class={
|
class={
|
||||||
if @active_tab == "agents", do: "text-indigo-600 dark:text-indigo-400", else: ""
|
if @active_tab == "agents", do: "text-cerulean-600 dark:text-cerulean-400", else: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("Agents")}
|
{t("Agents")}
|
||||||
|
|
@ -70,7 +74,9 @@
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=members"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=members"}
|
||||||
class={
|
class={
|
||||||
if @active_tab == "members", do: "text-indigo-600 dark:text-indigo-400", else: ""
|
if @active_tab == "members",
|
||||||
|
do: "text-cerulean-600 dark:text-cerulean-400",
|
||||||
|
else: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("Members")}
|
{t("Members")}
|
||||||
|
|
@ -88,7 +94,9 @@
|
||||||
<.link
|
<.link
|
||||||
patch={~p"/orgs/#{@organization.slug}/settings?tab=billing"}
|
patch={~p"/orgs/#{@organization.slug}/settings?tab=billing"}
|
||||||
class={
|
class={
|
||||||
if @active_tab == "billing", do: "text-indigo-600 dark:text-indigo-400", else: ""
|
if @active_tab == "billing",
|
||||||
|
do: "text-cerulean-600 dark:text-cerulean-400",
|
||||||
|
else: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("Billing")}
|
{t("Billing")}
|
||||||
|
|
@ -107,15 +115,15 @@
|
||||||
data-1p-ignore
|
data-1p-ignore
|
||||||
data-form-type="other"
|
data-form-type="other"
|
||||||
>
|
>
|
||||||
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= if @active_tab == "general" do %>
|
<%= if @active_tab == "general" do %>
|
||||||
<!-- Organization Name Section -->
|
<!-- Organization Name Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Organization Name")}
|
{t("Organization Name")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Update the name of your organization.")}
|
{t("Update the name of your organization.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -134,10 +142,10 @@
|
||||||
<!-- Default Organization Section -->
|
<!-- Default Organization Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Default Organization")}
|
{t("Default Organization")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Set this organization as your default. When you log in, you'll be directed to your default organization."
|
"Set this organization as your default. When you log in, you'll be directed to your default organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -162,11 +170,11 @@
|
||||||
id="toggle-default-org"
|
id="toggle-default-org"
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_default_org"
|
phx-click="toggle_default_org"
|
||||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:shadow-none dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:shadow-none dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-star" class="h-4 w-4 inline" /> Set as Default Organization
|
<.icon name="hero-star" class="h-4 w-4 inline" /> Set as Default Organization
|
||||||
</button>
|
</button>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Click to make this your default organization")}
|
{t("Click to make this your default organization")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -177,10 +185,10 @@
|
||||||
<!-- Use Sites Section -->
|
<!-- Use Sites Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Site Organization")}
|
{t("Site Organization")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Enable site organization to group devices into physical locations (offices, datacenters, etc.)."
|
"Enable site organization to group devices into physical locations (offices, datacenters, etc.)."
|
||||||
)}
|
)}
|
||||||
|
|
@ -193,23 +201,23 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
label={t("Use sites to organize devices")}
|
label={t("Use sites to organize devices")}
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"When enabled, you can organize devices into sites. When disabled, all devices belong directly to the organization."
|
"When enabled, you can organize devices into sites. When disabled, all devices belong directly to the organization."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= if @organization.use_sites && @form[:use_sites].value == false do %>
|
<%= if @organization.use_sites && @form[:use_sites].value == false do %>
|
||||||
<div class="mt-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-4">
|
<div class="mt-4 rounded-md bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-amber-400" />
|
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-wheat-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-amber-800 dark:text-amber-200">
|
<h3 class="text-sm font-medium text-wheat-800 dark:text-wheat-200">
|
||||||
{t("Warning: Disabling Sites")}
|
{t("Warning: Disabling Sites")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 text-sm text-amber-700 dark:text-amber-300">
|
<div class="mt-2 text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<p>
|
<p>
|
||||||
{t(
|
{t(
|
||||||
"All devices will be removed from their current sites and assigned directly to the organization."
|
"All devices will be removed from their current sites and assigned directly to the organization."
|
||||||
|
|
@ -227,12 +235,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Alert Routing Section -->
|
<!-- Alert Routing Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8 border-t border-gray-200 dark:border-white/5">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8 border-t border-cool-steel-200 dark:border-white/5">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Alert Routing")}
|
{t("Alert Routing")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Choose how alerts are routed for on-call notifications.")}
|
{t("Choose how alerts are routed for on-call notifications.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -247,9 +255,9 @@
|
||||||
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
||||||
if(routing_value == "builtin",
|
if(routing_value == "builtin",
|
||||||
do:
|
do:
|
||||||
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
|
"border-cerulean-500 bg-cerulean-50/50 ring-1 ring-cerulean-500 dark:bg-cerulean-900/10 dark:border-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
|
"border-cool-steel-200 hover:border-cool-steel-300 dark:border-white/10 dark:hover:border-white/20"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<input
|
<input
|
||||||
|
|
@ -257,14 +265,14 @@
|
||||||
name="organization[alert_routing]"
|
name="organization[alert_routing]"
|
||||||
value="builtin"
|
value="builtin"
|
||||||
checked={routing_value == "builtin"}
|
checked={routing_value == "builtin"}
|
||||||
class="mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-600 dark:border-gray-600 dark:bg-gray-800"
|
class="mt-0.5 h-4 w-4 border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-600 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<span class="block text-sm font-semibold text-gray-900 dark:text-white">
|
<span class="block text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<.icon name="hero-bell-alert" class="h-4 w-4 inline -mt-0.5" />
|
<.icon name="hero-bell-alert" class="h-4 w-4 inline -mt-0.5" />
|
||||||
{t("TowerOps Built-in")}
|
{t("TowerOps Built-in")}
|
||||||
</span>
|
</span>
|
||||||
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
<span class="block text-sm text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{t(
|
{t(
|
||||||
"Use TowerOps schedules and escalation policies to notify on-call staff."
|
"Use TowerOps schedules and escalation policies to notify on-call staff."
|
||||||
)}
|
)}
|
||||||
|
|
@ -277,9 +285,9 @@
|
||||||
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
||||||
if(routing_value == "pagerduty",
|
if(routing_value == "pagerduty",
|
||||||
do:
|
do:
|
||||||
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
|
"border-cerulean-500 bg-cerulean-50/50 ring-1 ring-cerulean-500 dark:bg-cerulean-900/10 dark:border-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
|
"border-cool-steel-200 hover:border-cool-steel-300 dark:border-white/10 dark:hover:border-white/20"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<input
|
<input
|
||||||
|
|
@ -287,29 +295,29 @@
|
||||||
name="organization[alert_routing]"
|
name="organization[alert_routing]"
|
||||||
value="pagerduty"
|
value="pagerduty"
|
||||||
checked={routing_value == "pagerduty"}
|
checked={routing_value == "pagerduty"}
|
||||||
class="mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-600 dark:border-gray-600 dark:bg-gray-800"
|
class="mt-0.5 h-4 w-4 border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-600 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<span class="block text-sm font-semibold text-gray-900 dark:text-white">
|
<span class="block text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-top-right-on-square"
|
name="hero-arrow-top-right-on-square"
|
||||||
class="h-4 w-4 inline -mt-0.5"
|
class="h-4 w-4 inline -mt-0.5"
|
||||||
/>
|
/>
|
||||||
{t("PagerDuty")}
|
{t("PagerDuty")}
|
||||||
</span>
|
</span>
|
||||||
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
<span class="block text-sm text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{t(
|
{t(
|
||||||
"Route alerts to PagerDuty for incident management and on-call notifications."
|
"Route alerts to PagerDuty for incident management and on-call notifications."
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<%= if routing_value == "pagerduty" && !@integrations["pagerduty"] do %>
|
<%= if routing_value == "pagerduty" && !@integrations["pagerduty"] do %>
|
||||||
<div class="mt-2 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-2.5">
|
<div class="mt-2 rounded-md bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 p-2.5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-exclamation-triangle"
|
name="hero-exclamation-triangle"
|
||||||
class="h-4 w-4 text-amber-500 shrink-0"
|
class="h-4 w-4 text-wheat-500 shrink-0"
|
||||||
/>
|
/>
|
||||||
<span class="text-xs text-amber-800 dark:text-amber-200">
|
<span class="text-xs text-wheat-800 dark:text-wheat-200">
|
||||||
{t("PagerDuty integration not configured yet.")}
|
{t("PagerDuty integration not configured yet.")}
|
||||||
<.link
|
<.link
|
||||||
navigate={
|
navigate={
|
||||||
|
|
@ -331,9 +339,9 @@
|
||||||
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
"relative flex cursor-pointer rounded-lg border p-4 transition-colors",
|
||||||
if(routing_value == "both",
|
if(routing_value == "both",
|
||||||
do:
|
do:
|
||||||
"border-blue-500 bg-blue-50/50 ring-1 ring-blue-500 dark:bg-blue-900/10 dark:border-blue-400",
|
"border-cerulean-500 bg-cerulean-50/50 ring-1 ring-cerulean-500 dark:bg-cerulean-900/10 dark:border-cerulean-400",
|
||||||
else:
|
else:
|
||||||
"border-gray-200 hover:border-gray-300 dark:border-white/10 dark:hover:border-white/20"
|
"border-cool-steel-200 hover:border-cool-steel-300 dark:border-white/10 dark:hover:border-white/20"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<input
|
<input
|
||||||
|
|
@ -341,14 +349,14 @@
|
||||||
name="organization[alert_routing]"
|
name="organization[alert_routing]"
|
||||||
value="both"
|
value="both"
|
||||||
checked={routing_value == "both"}
|
checked={routing_value == "both"}
|
||||||
class="mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-600 dark:border-gray-600 dark:bg-gray-800"
|
class="mt-0.5 h-4 w-4 border-cool-steel-300 text-cerulean-600 focus:ring-cerulean-600 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
/>
|
/>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<span class="block text-sm font-semibold text-gray-900 dark:text-white">
|
<span class="block text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<.icon name="hero-arrows-right-left" class="h-4 w-4 inline -mt-0.5" />
|
<.icon name="hero-arrows-right-left" class="h-4 w-4 inline -mt-0.5" />
|
||||||
{t("Both")}
|
{t("Both")}
|
||||||
</span>
|
</span>
|
||||||
<span class="block text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
<span class="block text-sm text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{t(
|
{t(
|
||||||
"Send alerts to both PagerDuty and built-in escalation. Useful during migration."
|
"Send alerts to both PagerDuty and built-in escalation. Useful during migration."
|
||||||
)}
|
)}
|
||||||
|
|
@ -365,15 +373,15 @@
|
||||||
<!-- SNMP Configuration Section -->
|
<!-- SNMP Configuration Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("SNMP Configuration")}
|
{t("SNMP Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Set default SNMP settings for all devices in this organization. These can be overridden at the site or device level."
|
"Set default SNMP settings for all devices in this organization. These can be overridden at the site or device level."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
|
<p class="mt-3 text-xs text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
||||||
Hierarchy: Device > Site > Organization
|
Hierarchy: Device > Site > Organization
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -479,11 +487,11 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @organization.snmp_community do %>
|
<%= if @organization.snmp_community do %>
|
||||||
<div class="mt-6 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-4">
|
<div class="mt-6 rounded-md bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 p-4">
|
||||||
<h3 class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
|
<h3 class="text-sm font-medium text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Force Apply to All Devices")}
|
{t("Force Apply to All Devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300 mb-3">
|
||||||
{t(
|
{t(
|
||||||
"This will override SNMP settings for ALL devices across all sites in this organization."
|
"This will override SNMP settings for ALL devices across all sites in this organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -497,7 +505,7 @@
|
||||||
"This will replace SNMP settings for ALL devices in this organization. Are you sure?"
|
"This will replace SNMP settings for ALL devices in this organization. Are you sure?"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 dark:bg-red-500 dark:shadow-none dark:hover:bg-red-400"
|
class="rounded-md bg-sweet-salmon-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-sweet-salmon-500 dark:bg-sweet-salmon-500 dark:shadow-none dark:hover:bg-sweet-salmon-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
|
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
|
||||||
{t("Apply SNMP Config to All Devices")}
|
{t("Apply SNMP Config to All Devices")}
|
||||||
|
|
@ -512,22 +520,22 @@
|
||||||
<!-- MikroTik API Configuration Section -->
|
<!-- MikroTik API Configuration Section -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("MikroTik API Configuration")}
|
{t("MikroTik API Configuration")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Set default MikroTik RouterOS API credentials for all devices in this organization. Only applies to devices detected as MikroTik."
|
"Set default MikroTik RouterOS API credentials for all devices in this organization. Only applies to devices detected as MikroTik."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
|
<div class="mt-3 rounded-md bg-cerulean-50 dark:bg-cerulean-900/20 p-3">
|
||||||
<p class="text-xs text-blue-700 dark:text-blue-300">
|
<p class="text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
||||||
<strong>{t("Experimental Feature:")}</strong>
|
<strong>{t("Experimental Feature:")}</strong>
|
||||||
{t("MikroTik API integration is under active development.")}
|
{t("MikroTik API integration is under active development.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
|
<p class="mt-3 text-xs text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
||||||
Hierarchy: Device > Site > Organization
|
Hierarchy: Device > Site > Organization
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -579,8 +587,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={"#{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value != false, do: "hidden"}"}>
|
<div class={"#{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value != false, do: "hidden"}"}>
|
||||||
<div class="rounded-md bg-red-50 dark:bg-red-900/20 p-4">
|
<div class="rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 p-4">
|
||||||
<p class="text-sm text-red-700 dark:text-red-300">
|
<p class="text-sm text-sweet-salmon-700 dark:text-sweet-salmon-300">
|
||||||
<strong>{t("Critical Security Warning:")}</strong>
|
<strong>{t("Critical Security Warning:")}</strong>
|
||||||
{t(
|
{t(
|
||||||
"Plain API (port 8728) sends credentials unencrypted over the network. This setting is"
|
"Plain API (port 8728) sends credentials unencrypted over the network. This setting is"
|
||||||
|
|
@ -592,8 +600,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={"#{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value == false, do: "hidden"}"}>
|
<div class={"#{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value == false, do: "hidden"}"}>
|
||||||
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
|
<div class="rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-4">
|
||||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<strong>{t("Security Warning:")}</strong>
|
<strong>{t("Security Warning:")}</strong>
|
||||||
{t(
|
{t(
|
||||||
"Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible."
|
"Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible."
|
||||||
|
|
@ -610,15 +618,15 @@
|
||||||
<%= if @available_agents != [] do %>
|
<%= if @available_agents != [] do %>
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Default Agent")}
|
{t("Default Agent")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Select a default agent for SNMP polling. This will be used for all devices in this organization unless overridden at the site or device level."
|
"Select a default agent for SNMP polling. This will be used for all devices in this organization unless overridden at the site or device level."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
|
<p class="mt-3 text-xs text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
|
||||||
Hierarchy: Device > Site > Organization
|
Hierarchy: Device > Site > Organization
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -633,11 +641,11 @@
|
||||||
options={Enum.map(@available_agents, &{&1.name, &1.id})}
|
options={Enum.map(@available_agents, &{&1.name, &1.id})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-4">
|
<div class="rounded-md bg-cool-steel-50 dark:bg-cool-steel-800/50 p-4">
|
||||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<p class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2">
|
||||||
{t("Current Device Assignment:")}
|
{t("Current Device Assignment:")}
|
||||||
</p>
|
</p>
|
||||||
<div class="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
<div class="space-y-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<.icon name="hero-server" class="h-4 w-4" />
|
<.icon name="hero-server" class="h-4 w-4" />
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -667,11 +675,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @organization.default_agent_token_id do %>
|
<%= if @organization.default_agent_token_id do %>
|
||||||
<div class="mt-6 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-4">
|
<div class="mt-6 rounded-md bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 p-4">
|
||||||
<h3 class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
|
<h3 class="text-sm font-medium text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Force Apply to All Devices")}
|
{t("Force Apply to All Devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300 mb-3">
|
||||||
{t(
|
{t(
|
||||||
"This will assign the default agent to ALL devices across all sites in this organization."
|
"This will assign the default agent to ALL devices across all sites in this organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -685,7 +693,7 @@
|
||||||
"This will replace agent assignments for ALL devices in this organization. Are you sure?"
|
"This will replace agent assignments for ALL devices in this organization. Are you sure?"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 dark:bg-red-500 dark:shadow-none dark:hover:bg-red-400"
|
class="rounded-md bg-sweet-salmon-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-sweet-salmon-500 dark:bg-sweet-salmon-500 dark:shadow-none dark:hover:bg-sweet-salmon-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
|
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
|
||||||
{t("Apply Default Agent to All Devices")}
|
{t("Apply Default Agent to All Devices")}
|
||||||
|
|
@ -697,14 +705,14 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Default Agent")}
|
{t("Default Agent")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No agents configured yet.")}
|
{t("No agents configured yet.")}
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/agents"}
|
navigate={~p"/agents"}
|
||||||
class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
|
class="text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
{t("Create an agent")}
|
{t("Create an agent")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -721,17 +729,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Save Button at Bottom (not shown on integrations tab) -->
|
<!-- Save Button at Bottom (not shown on integrations tab) -->
|
||||||
<div class="flex items-center justify-end gap-x-6 border-t border-gray-200 px-4 py-4 sm:px-6 lg:px-8 dark:border-white/10">
|
<div class="flex items-center justify-end gap-x-6 border-t border-cool-steel-200 px-4 py-4 sm:px-6 lg:px-8 dark:border-white/10">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/dashboard"}
|
navigate={~p"/dashboard"}
|
||||||
class="text-sm font-semibold text-gray-900 dark:text-white"
|
class="text-sm font-semibold text-cool-steel-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</.link>
|
</.link>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
phx-disable-with={t("Saving...")}
|
phx-disable-with={t("Saving...")}
|
||||||
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 dark:bg-indigo-500 dark:shadow-none dark:hover:bg-indigo-400 dark:focus-visible:outline-indigo-500"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cerulean-600 dark:bg-cerulean-500 dark:shadow-none dark:hover:bg-cerulean-400 dark:focus-visible:outline-cerulean-500"
|
||||||
>
|
>
|
||||||
{t("Save Settings")}
|
{t("Save Settings")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -740,15 +748,15 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @active_tab == "members" do %>
|
<%= if @active_tab == "members" do %>
|
||||||
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<!-- Invite form (owner/admin only) -->
|
<!-- Invite form (owner/admin only) -->
|
||||||
<%= if @membership.role in [:owner, :admin] do %>
|
<%= if @membership.role in [:owner, :admin] do %>
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Invite a Team Member")}
|
{t("Invite a Team Member")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Send an invitation email to add someone to this organization.")}
|
{t("Send an invitation email to add someone to this organization.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -775,13 +783,13 @@
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm/6 font-medium text-gray-900 dark:text-white invisible">
|
<label class="block text-sm/6 font-medium text-cool-steel-900 dark:text-white invisible">
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
phx-disable-with={t("Sending...")}
|
phx-disable-with={t("Sending...")}
|
||||||
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:shadow-none dark:hover:bg-indigo-400 h-[42px]"
|
class="rounded-md bg-cerulean-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:shadow-none dark:hover:bg-cerulean-400 h-[42px]"
|
||||||
>
|
>
|
||||||
{t("Send Invite")}
|
{t("Send Invite")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -796,56 +804,56 @@
|
||||||
<%= if @pending_invitations != [] do %>
|
<%= if @pending_invitations != [] do %>
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Pending Invitations")}
|
{t("Pending Invitations")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Invitations that haven't been accepted yet.")}
|
{t("Invitations that haven't been accepted yet.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<div class="overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-white/5">
|
<thead class="bg-cool-steel-50 dark:bg-white/5">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Email")}
|
{t("Email")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Role")}
|
{t("Role")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Sent")}
|
{t("Sent")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Expires")}
|
{t("Expires")}
|
||||||
</th>
|
</th>
|
||||||
<%= if @membership.role in [:owner, :admin] do %>
|
<%= if @membership.role in [:owner, :admin] do %>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/10 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/10 dark:bg-transparent">
|
||||||
<tr :for={invitation <- @pending_invitations}>
|
<tr :for={invitation <- @pending_invitations}>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{invitation.email}
|
{invitation.email}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||||
<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400">
|
<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400">
|
||||||
{invitation.role}
|
{invitation.role}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={invitation.inserted_at}
|
datetime={invitation.inserted_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
format="relative"
|
format="relative"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={invitation.expires_at}
|
datetime={invitation.expires_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
|
|
@ -859,7 +867,7 @@
|
||||||
phx-click="cancel_invitation"
|
phx-click="cancel_invitation"
|
||||||
phx-value-id={invitation.id}
|
phx-value-id={invitation.id}
|
||||||
data-confirm={t("Cancel this invitation?")}
|
data-confirm={t("Cancel this invitation?")}
|
||||||
class="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
class="text-sweet-salmon-600 hover:text-sweet-salmon-700 dark:text-sweet-salmon-400 dark:hover:text-sweet-salmon-300"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -876,40 +884,40 @@
|
||||||
<!-- Current members -->
|
<!-- Current members -->
|
||||||
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-6 px-4 py-8 sm:px-6 md:grid-cols-3 lg:px-8">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-base/7 font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Members")}
|
{t("Members")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm/6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("People who have access to this organization.")}
|
{t("People who have access to this organization.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-white/5">
|
<thead class="bg-cool-steel-50 dark:bg-white/5">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("User")}
|
{t("User")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Role")}
|
{t("Role")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Joined")}
|
{t("Joined")}
|
||||||
</th>
|
</th>
|
||||||
<%= if @membership.role in [:owner, :admin] do %>
|
<%= if @membership.role in [:owner, :admin] do %>
|
||||||
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Actions")}
|
{t("Actions")}
|
||||||
</th>
|
</th>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/10 dark:bg-transparent">
|
<tbody class="divide-y divide-cool-steel-200 bg-white dark:divide-white/10 dark:bg-transparent">
|
||||||
<tr :for={member <- @members}>
|
<tr :for={member <- @members}>
|
||||||
<td class="whitespace-nowrap px-4 py-3">
|
<td class="whitespace-nowrap px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
<div class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{member.user.email}
|
{member.user.email}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -922,22 +930,22 @@
|
||||||
"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
|
|
||||||
:admin ->
|
:admin ->
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
"bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
:executive ->
|
:executive ->
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
"bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
|
||||||
role when role in [:technician, :member] ->
|
role when role in [:technician, :member] ->
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
||||||
|
|
||||||
:viewer ->
|
:viewer ->
|
||||||
"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-400"
|
"bg-cool-steel-100 text-cool-steel-800 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{member.role}
|
{member.role}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.timestamp
|
<.timestamp
|
||||||
datetime={member.inserted_at}
|
datetime={member.inserted_at}
|
||||||
timezone={@timezone}
|
timezone={@timezone}
|
||||||
|
|
@ -951,7 +959,7 @@
|
||||||
<form phx-change="change_role" phx-value-user-id={member.user_id}>
|
<form phx-change="change_role" phx-value-user-id={member.user_id}>
|
||||||
<select
|
<select
|
||||||
name="role"
|
name="role"
|
||||||
class="rounded-md border-gray-300 py-1 pl-2 pr-8 text-xs dark:border-white/10 dark:bg-white/5 dark:text-white"
|
class="rounded-md border-cool-steel-300 py-1 pl-2 pr-8 text-xs dark:border-white/10 dark:bg-white/5 dark:text-white"
|
||||||
>
|
>
|
||||||
<option value="admin" selected={member.role == :admin}>
|
<option value="admin" selected={member.role == :admin}>
|
||||||
{t("Admin")}
|
{t("Admin")}
|
||||||
|
|
@ -994,18 +1002,18 @@
|
||||||
|
|
||||||
<%= if @active_tab == "integrations" do %>
|
<%= if @active_tab == "integrations" do %>
|
||||||
<div class="mt-8 flex items-center justify-center">
|
<div class="mt-8 flex items-center justify-center">
|
||||||
<div class="text-center rounded-xl border border-gray-200 bg-white p-10 shadow-sm dark:border-white/10 dark:bg-white/[0.03]">
|
<div class="text-center rounded-xl border border-cool-steel-200 bg-white p-10 shadow-sm dark:border-white/10 dark:bg-white/[0.03]">
|
||||||
<.icon name="hero-arrow-top-right-on-square" class="mx-auto h-10 w-10 text-indigo-400" />
|
<.icon name="hero-arrow-top-right-on-square" class="mx-auto h-10 w-10 text-cerulean-400" />
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Integrations have moved to their own page")}
|
{t("Integrations have moved to their own page")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Manage all your integrations from the dedicated integrations page.")}
|
{t("Manage all your integrations from the dedicated integrations page.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/orgs/#{@organization.slug}/settings/integrations"}
|
navigate={~p"/orgs/#{@organization.slug}/settings/integrations"}
|
||||||
class="rounded-md bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
|
class="rounded-md bg-cerulean-600 px-4 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-cerulean-500 dark:bg-cerulean-500 dark:hover:bg-cerulean-400"
|
||||||
>
|
>
|
||||||
{t("Go to Integrations")}
|
{t("Go to Integrations")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -1021,18 +1029,22 @@
|
||||||
|
|
||||||
<dl class="space-y-3">
|
<dl class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm text-gray-600 dark:text-gray-400">{t("Current Plan")}</dt>
|
<dt class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Current Plan")}
|
||||||
|
</dt>
|
||||||
<dd class="text-lg font-semibold capitalize dark:text-white">
|
<dd class="text-lg font-semibold capitalize dark:text-white">
|
||||||
{@organization.subscription_plan || "free"}
|
{@organization.subscription_plan || "free"}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm text-gray-600 dark:text-gray-400">{t("Device Usage")}</dt>
|
<dt class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Device Usage")}
|
||||||
|
</dt>
|
||||||
<dd class="text-lg dark:text-white">
|
<dd class="text-lg dark:text-white">
|
||||||
{@current_devices} {t("devices")}
|
{@current_devices} {t("devices")}
|
||||||
<%= if @device_limit != :unlimited do %>
|
<%= if @device_limit != :unlimited do %>
|
||||||
<span class="text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
/ {@device_limit} {t("limit")}
|
/ {@device_limit} {t("limit")}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -1041,13 +1053,13 @@
|
||||||
|
|
||||||
<%= if @organization.subscription_plan == "paid" do %>
|
<%= if @organization.subscription_plan == "paid" do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
<dt class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Estimated Monthly Cost")}
|
{t("Estimated Monthly Cost")}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-2xl font-bold text-green-600 dark:text-green-400">
|
<dd class="text-2xl font-bold text-green-600 dark:text-green-400">
|
||||||
${@estimated_cost.cost_usd}
|
${@estimated_cost.cost_usd}
|
||||||
</dd>
|
</dd>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-1">
|
||||||
{@estimated_cost.billable} {t("billable devices")} × ${@estimated_cost.price_per_device}
|
{@estimated_cost.billable} {t("billable devices")} × ${@estimated_cost.price_per_device}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1069,23 +1081,23 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @organization.subscription_status == "past_due" do %>
|
<%= if @organization.subscription_status == "past_due" do %>
|
||||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800/50 rounded p-3">
|
<div class="bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800/50 rounded p-3">
|
||||||
<p class="text-sm text-yellow-800 dark:text-yellow-300">
|
<p class="text-sm text-wheat-800 dark:text-wheat-300">
|
||||||
⚠ {t("Payment Past Due")}
|
⚠ {t("Payment Past Due")}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-1">
|
<p class="text-xs text-wheat-600 dark:text-wheat-400 mt-1">
|
||||||
{t("Please update your payment method to avoid service interruption.")}
|
{t("Please update your payment method to avoid service interruption.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 rounded p-3">
|
<div class="bg-cerulean-50 dark:bg-cerulean-900/20 border border-cerulean-200 dark:border-cerulean-800/50 rounded p-3">
|
||||||
<p class="text-sm text-blue-800 dark:text-blue-300">
|
<p class="text-sm text-cerulean-800 dark:text-cerulean-300">
|
||||||
{t("Free Plan - First %{count} devices included",
|
{t("Free Plan - First %{count} devices included",
|
||||||
count: @estimated_cost.free_included
|
count: @estimated_cost.free_included
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-blue-600 dark:text-blue-400 mt-1">
|
<p class="text-xs text-cerulean-600 dark:text-cerulean-400 mt-1">
|
||||||
{t("Upgrade to monitor unlimited devices at $%{price}/device/month",
|
{t("Upgrade to monitor unlimited devices at $%{price}/device/month",
|
||||||
price: @estimated_cost.price_per_device
|
price: @estimated_cost.price_per_device
|
||||||
)}
|
)}
|
||||||
|
|
@ -1118,14 +1130,18 @@
|
||||||
<h3 class="text-lg font-semibold mb-3 dark:text-white">{t("Billing Information")}</h3>
|
<h3 class="text-lg font-semibold mb-3 dark:text-white">{t("Billing Information")}</h3>
|
||||||
<dl class="space-y-2 text-sm">
|
<dl class="space-y-2 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">{t("Subscription Status")}</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Subscription Status")}
|
||||||
|
</dt>
|
||||||
<dd class="font-medium capitalize dark:text-white">
|
<dd class="font-medium capitalize dark:text-white">
|
||||||
{@organization.subscription_status}
|
{@organization.subscription_status}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<%= if @organization.subscription_current_period_start do %>
|
<%= if @organization.subscription_current_period_start do %>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">{t("Current Period Start")}</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Current Period Start")}
|
||||||
|
</dt>
|
||||||
<dd class="font-medium dark:text-white">
|
<dd class="font-medium dark:text-white">
|
||||||
{Calendar.strftime(@organization.subscription_current_period_start, "%B %d, %Y")}
|
{Calendar.strftime(@organization.subscription_current_period_start, "%B %d, %Y")}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
@ -1133,7 +1149,9 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @organization.subscription_current_period_end do %>
|
<%= if @organization.subscription_current_period_end do %>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">{t("Current Period End")}</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Current Period End")}
|
||||||
|
</dt>
|
||||||
<dd class="font-medium dark:text-white">
|
<dd class="font-medium dark:text-white">
|
||||||
{Calendar.strftime(@organization.subscription_current_period_end, "%B %d, %Y")}
|
{Calendar.strftime(@organization.subscription_current_period_end, "%B %d, %Y")}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
@ -1141,12 +1159,14 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @organization.payment_method_status do %>
|
<%= if @organization.payment_method_status do %>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600 dark:text-gray-400">{t("Payment Method")}</dt>
|
<dt class="text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{t("Payment Method")}
|
||||||
|
</dt>
|
||||||
<dd class={[
|
<dd class={[
|
||||||
"font-medium capitalize",
|
"font-medium capitalize",
|
||||||
if(@organization.payment_method_status == "valid",
|
if(@organization.payment_method_status == "valid",
|
||||||
do: "text-green-600 dark:text-green-400",
|
do: "text-green-600 dark:text-green-400",
|
||||||
else: "text-yellow-600 dark:text-yellow-400"
|
else: "text-wheat-600 dark:text-wheat-400"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{String.replace(@organization.payment_method_status, "_", " ")}
|
{String.replace(@organization.payment_method_status, "_", " ")}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@
|
||||||
<input type="hidden" name="org_id" value={org.id} />
|
<input type="hidden" name="org_id" value={org.id} />
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="relative w-full overflow-hidden rounded-lg border border-gray-200 bg-white p-6 shadow-sm hover:shadow-md transition-shadow dark:border-white/10 dark:bg-gray-800/50 cursor-pointer text-left"
|
class="relative w-full overflow-hidden rounded-lg border border-cool-steel-200 bg-white p-6 shadow-sm hover:shadow-md transition-shadow dark:border-white/10 dark:bg-cool-steel-800/50 cursor-pointer text-left"
|
||||||
>
|
>
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">{org.name}</h2>
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white">{org.name}</h2>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{Enum.find(org.memberships, &(&1.user_id == @current_scope.user.id)).role
|
{Enum.find(org.memberships, &(&1.user_id == @current_scope.user.id)).role
|
||||||
|> to_string()
|
|> to_string()
|
||||||
|> String.capitalize()}
|
|> String.capitalize()}
|
||||||
|
|
@ -35,11 +35,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :if={@organizations == []} class="text-center py-16">
|
<div :if={@organizations == []} class="text-center py-16">
|
||||||
<.icon name="hero-building-office" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600" />
|
<.icon
|
||||||
<h3 class="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
|
name="hero-building-office"
|
||||||
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-600"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-4 text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No organizations")}
|
{t("No organizations")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Get started by creating a new organization.")}
|
{t("Get started by creating a new organization.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,13 @@ defmodule ToweropsWeb.ReportsLive do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def status_badge("success"), do: {"Success", "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"}
|
def status_badge("success"), do: {"Success", "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"}
|
||||||
def status_badge("failed"), do: {"Failed", "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"}
|
|
||||||
|
def status_badge("failed"),
|
||||||
|
do: {"Failed", "bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-900/50 dark:text-sweet-salmon-300"}
|
||||||
|
|
||||||
def status_badge("partial_failure"),
|
def status_badge("partial_failure"),
|
||||||
do: {"Partial", "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300"}
|
do: {"Partial", "bg-wheat-100 text-wheat-700 dark:bg-wheat-900/50 dark:text-wheat-300"}
|
||||||
|
|
||||||
def status_badge(_), do: {"Never Run", "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"}
|
def status_badge(_),
|
||||||
|
do: {"Never Run", "bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-800 dark:text-cool-steel-300"}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,17 @@
|
||||||
|
|
||||||
<div class="mb-6 flex items-center justify-between">
|
<div class="mb-6 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("Scheduled Reports")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
{t("Scheduled Reports")}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Automated report generation and email delivery")}
|
{t("Automated report generation and email delivery")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
:if={!@show_form}
|
:if={!@show_form}
|
||||||
phx-click="show_form"
|
phx-click="show_form"
|
||||||
class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-cerulean-600 hover:bg-cerulean-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="w-4 h-4" />
|
<.icon name="hero-plus" class="w-4 h-4" />
|
||||||
{t("New Report")}
|
{t("New Report")}
|
||||||
|
|
@ -28,12 +30,14 @@
|
||||||
|
|
||||||
<%!-- Create Report Form --%>
|
<%!-- Create Report Form --%>
|
||||||
<%= if @show_form do %>
|
<%= if @show_form do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6 mb-6">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-6 mb-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{t("Create Report")}</h2>
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("Create Report")}
|
||||||
|
</h2>
|
||||||
<button
|
<button
|
||||||
phx-click="hide_form"
|
phx-click="hide_form"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="w-5 h-5" />
|
<.icon name="hero-x-mark" class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -42,7 +46,7 @@
|
||||||
<.form for={@form} phx-submit="save" class="space-y-4">
|
<.form for={@form} phx-submit="save" class="space-y-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Report Name")}
|
{t("Report Name")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -50,18 +54,18 @@
|
||||||
name="report[name]"
|
name="report[name]"
|
||||||
placeholder="Weekly Uptime Report"
|
placeholder="Weekly Uptime Report"
|
||||||
required
|
required
|
||||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full rounded-lg border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm text-cool-steel-900 dark:text-white focus:ring-2 focus:ring-cerulean-500 focus:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Report Type")}
|
{t("Report Type")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="report[report_type]"
|
name="report[report_type]"
|
||||||
required
|
required
|
||||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full rounded-lg border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm text-cool-steel-900 dark:text-white focus:ring-2 focus:ring-cerulean-500 focus:border-cerulean-500"
|
||||||
>
|
>
|
||||||
<option value="uptime_summary">{t("Uptime Summary")}</option>
|
<option value="uptime_summary">{t("Uptime Summary")}</option>
|
||||||
<option value="alert_history">{t("Alert History")}</option>
|
<option value="alert_history">{t("Alert History")}</option>
|
||||||
|
|
@ -71,12 +75,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Schedule")}
|
{t("Schedule")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="report[schedule_type]"
|
name="report[schedule_type]"
|
||||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full rounded-lg border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm text-cool-steel-900 dark:text-white focus:ring-2 focus:ring-cerulean-500 focus:border-cerulean-500"
|
||||||
>
|
>
|
||||||
<option value="daily">{t("Daily")}</option>
|
<option value="daily">{t("Daily")}</option>
|
||||||
<option value="weekly" selected>{t("Weekly")}</option>
|
<option value="weekly" selected>{t("Weekly")}</option>
|
||||||
|
|
@ -86,12 +90,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Time Range (days)")}
|
{t("Time Range (days)")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="report[scope_days]"
|
name="report[scope_days]"
|
||||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full rounded-lg border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm text-cool-steel-900 dark:text-white focus:ring-2 focus:ring-cerulean-500 focus:border-cerulean-500"
|
||||||
>
|
>
|
||||||
<option value="1">{t("Last 24 hours")}</option>
|
<option value="1">{t("Last 24 hours")}</option>
|
||||||
<option value="7" selected>{t("Last 7 days")}</option>
|
<option value="7" selected>{t("Last 7 days")}</option>
|
||||||
|
|
@ -102,9 +106,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-1">
|
||||||
{t("Recipients")}
|
{t("Recipients")}
|
||||||
<span class="font-normal text-gray-400">
|
<span class="font-normal text-cool-steel-400">
|
||||||
{t("(comma-separated email addresses)")}
|
{t("(comma-separated email addresses)")}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -113,7 +117,7 @@
|
||||||
rows="2"
|
rows="2"
|
||||||
required
|
required
|
||||||
placeholder="admin@example.com, noc@example.com"
|
placeholder="admin@example.com, noc@example.com"
|
||||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-2 text-sm text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
class="w-full rounded-lg border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm text-cool-steel-900 dark:text-white focus:ring-2 focus:ring-cerulean-500 focus:border-cerulean-500"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -121,13 +125,13 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="hide_form"
|
phx-click="hide_form"
|
||||||
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors"
|
class="px-4 py-2 text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 bg-cool-steel-100 dark:bg-cool-steel-700 hover:bg-cool-steel-200 dark:hover:bg-cool-steel-600 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
class="px-4 py-2 text-sm font-medium text-white bg-cerulean-600 hover:bg-cerulean-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{t("Create Report")}
|
{t("Create Report")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -138,11 +142,11 @@
|
||||||
|
|
||||||
<%!-- Reports List --%>
|
<%!-- Reports List --%>
|
||||||
<%= if @reports != [] do %>
|
<%= if @reports != [] do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/75">
|
||||||
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
<tr class="text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Name")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Name")}</th>
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Type")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Type")}</th>
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Schedule")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Schedule")}</th>
|
||||||
|
|
@ -152,22 +156,24 @@
|
||||||
<th class="px-4 py-2 text-right font-medium">{t("Actions")}</th>
|
<th class="px-4 py-2 text-right font-medium">{t("Actions")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for report <- @reports do %>
|
<%= for report <- @reports do %>
|
||||||
<tr class={"text-sm #{if !report.enabled, do: "opacity-50"}"}>
|
<tr class={"text-sm #{if !report.enabled, do: "opacity-50"}"}>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<span class="font-medium text-gray-900 dark:text-white">{report.name}</span>
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
|
{report.name}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{humanize_type(report.report_type)}
|
{humanize_type(report.report_type)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{humanize_schedule(report.schedule)}
|
{humanize_schedule(report.schedule)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400 max-w-48 truncate">
|
<td class="px-4 py-3 text-xs text-cool-steel-500 dark:text-cool-steel-400 max-w-48 truncate">
|
||||||
{format_recipients(report.recipients)}
|
{format_recipients(report.recipients)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
|
<td class="px-4 py-3 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if report.last_run_at do %>
|
<%= if report.last_run_at do %>
|
||||||
{Calendar.strftime(report.last_run_at, "%b %d, %H:%M")}
|
{Calendar.strftime(report.last_run_at, "%b %d, %H:%M")}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -186,7 +192,7 @@
|
||||||
phx-click="run_now"
|
phx-click="run_now"
|
||||||
phx-value-id={report.id}
|
phx-value-id={report.id}
|
||||||
title="Run now"
|
title="Run now"
|
||||||
class="text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
|
class="text-cool-steel-400 hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-play" class="w-4 h-4" />
|
<.icon name="hero-play" class="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -194,7 +200,7 @@
|
||||||
phx-click="toggle"
|
phx-click="toggle"
|
||||||
phx-value-id={report.id}
|
phx-value-id={report.id}
|
||||||
title={if report.enabled, do: "Disable", else: "Enable"}
|
title={if report.enabled, do: "Disable", else: "Enable"}
|
||||||
class="text-gray-400 hover:text-yellow-600 dark:hover:text-yellow-400"
|
class="text-cool-steel-400 hover:text-wheat-600 dark:hover:text-wheat-400"
|
||||||
>
|
>
|
||||||
<.icon
|
<.icon
|
||||||
name={if report.enabled, do: "hero-pause", else: "hero-play"}
|
name={if report.enabled, do: "hero-pause", else: "hero-play"}
|
||||||
|
|
@ -206,7 +212,7 @@
|
||||||
phx-value-id={report.id}
|
phx-value-id={report.id}
|
||||||
data-confirm="Delete this report?"
|
data-confirm="Delete this report?"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
class="text-gray-400 hover:text-red-600 dark:hover:text-red-400"
|
class="text-cool-steel-400 hover:text-sweet-salmon-600 dark:hover:text-sweet-salmon-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="w-4 h-4" />
|
<.icon name="hero-trash" class="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -219,20 +225,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12 text-center">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-12 text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-document-chart-bar"
|
name="hero-document-chart-bar"
|
||||||
class="w-12 h-12 mx-auto text-gray-300 dark:text-gray-600"
|
class="w-12 h-12 mx-auto text-cool-steel-300 dark:text-cool-steel-600"
|
||||||
/>
|
/>
|
||||||
<h3 class="mt-4 text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="mt-4 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No scheduled reports")}
|
{t("No scheduled reports")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Create a report to start receiving automated updates via email.")}
|
{t("Create a report to start receiving automated updates via email.")}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
phx-click="show_form"
|
phx-click="show_form"
|
||||||
class="mt-4 inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
|
class="mt-4 inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-cerulean-600 hover:bg-cerulean-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="w-4 h-4" />
|
<.icon name="hero-plus" class="w-4 h-4" />
|
||||||
{t("Create First Report")}
|
{t("Create First Report")}
|
||||||
|
|
|
||||||
|
|
@ -82,15 +82,18 @@ defmodule ToweropsWeb.RfLinkHealthLive do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def health_color(:healthy), do: "text-green-600 dark:text-green-400"
|
def health_color(:healthy), do: "text-green-600 dark:text-green-400"
|
||||||
def health_color(:degraded), do: "text-yellow-600 dark:text-yellow-400"
|
def health_color(:degraded), do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def health_color(:critical), do: "text-red-600 dark:text-red-400"
|
def health_color(:critical), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def health_color(_), do: "text-gray-400 dark:text-gray-500"
|
def health_color(_), do: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def health_bg(:healthy), do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"
|
def health_bg(:healthy), do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"
|
||||||
def health_bg(:degraded), do: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300"
|
def health_bg(:degraded), do: "bg-wheat-100 text-wheat-700 dark:bg-wheat-900/50 dark:text-wheat-300"
|
||||||
def health_bg(:critical), do: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"
|
|
||||||
def health_bg(_), do: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
def health_bg(:critical),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-700 dark:bg-sweet-salmon-900/50 dark:text-sweet-salmon-300"
|
||||||
|
|
||||||
|
def health_bg(_), do: "bg-cool-steel-100 text-cool-steel-700 dark:bg-cool-steel-800 dark:text-cool-steel-300"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def health_label(:healthy), do: "Healthy"
|
def health_label(:healthy), do: "Healthy"
|
||||||
|
|
@ -108,10 +111,10 @@ defmodule ToweropsWeb.RfLinkHealthLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def signal_bar_color(nil), do: "bg-gray-300 dark:bg-gray-600"
|
def signal_bar_color(nil), do: "bg-cool-steel-300 dark:bg-cool-steel-600"
|
||||||
def signal_bar_color(signal) when signal > -65, do: "bg-green-500"
|
def signal_bar_color(signal) when signal > -65, do: "bg-green-500"
|
||||||
def signal_bar_color(signal) when signal > -75, do: "bg-yellow-500"
|
def signal_bar_color(signal) when signal > -75, do: "bg-wheat-500"
|
||||||
def signal_bar_color(_), do: "bg-red-500"
|
def signal_bar_color(_), do: "bg-sweet-salmon-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def snr_bar_width(nil), do: 0
|
def snr_bar_width(nil), do: 0
|
||||||
|
|
@ -123,10 +126,10 @@ defmodule ToweropsWeb.RfLinkHealthLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def snr_bar_color(nil), do: "bg-gray-300 dark:bg-gray-600"
|
def snr_bar_color(nil), do: "bg-cool-steel-300 dark:bg-cool-steel-600"
|
||||||
def snr_bar_color(snr) when snr > 25, do: "bg-green-500"
|
def snr_bar_color(snr) when snr > 25, do: "bg-green-500"
|
||||||
def snr_bar_color(snr) when snr > 15, do: "bg-yellow-500"
|
def snr_bar_color(snr) when snr > 15, do: "bg-wheat-500"
|
||||||
def snr_bar_color(_), do: "bg-red-500"
|
def snr_bar_color(_), do: "bg-sweet-salmon-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def format_signal(nil), do: "-"
|
def format_signal(nil), do: "-"
|
||||||
|
|
@ -171,8 +174,8 @@ defmodule ToweropsWeb.RfLinkHealthLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def stat_card_class(:all, _), do: "ring-2 ring-blue-500"
|
def stat_card_class(:all, _), do: "ring-2 ring-cerulean-500"
|
||||||
def stat_card_class(filter, filter), do: "ring-2 ring-blue-500"
|
def stat_card_class(filter, filter), do: "ring-2 ring-cerulean-500"
|
||||||
def stat_card_class(_, _), do: ""
|
def stat_card_class(_, _), do: ""
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
]} />
|
]} />
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("RF Link Health")}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{t("RF Link Health")}</h1>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Wireless link quality triage — weakest links first")}
|
{t("Wireless link quality triage — weakest links first")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -21,12 +21,12 @@
|
||||||
<button
|
<button
|
||||||
phx-click="filter"
|
phx-click="filter"
|
||||||
phx-value-filter="all"
|
phx-value-filter="all"
|
||||||
class={"bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-blue-300 dark:hover:border-blue-700 transition-colors #{stat_card_class(@filter, :all)}"}
|
class={"bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-cerulean-300 dark:hover:border-cerulean-700 transition-colors #{stat_card_class(@filter, :all)}"}
|
||||||
>
|
>
|
||||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
{t("Total Links")}
|
{t("Total Links")}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
<div class="mt-1 text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@summary.total}
|
{@summary.total}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -34,9 +34,9 @@
|
||||||
<button
|
<button
|
||||||
phx-click="filter"
|
phx-click="filter"
|
||||||
phx-value-filter="healthy"
|
phx-value-filter="healthy"
|
||||||
class={"bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-green-300 dark:hover:border-green-700 transition-colors #{stat_card_class(@filter, :healthy)}"}
|
class={"bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-green-300 dark:hover:border-green-700 transition-colors #{stat_card_class(@filter, :healthy)}"}
|
||||||
>
|
>
|
||||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
{t("Healthy")}
|
{t("Healthy")}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-2xl font-bold text-green-600 dark:text-green-400">
|
<div class="mt-1 text-2xl font-bold text-green-600 dark:text-green-400">
|
||||||
|
|
@ -47,12 +47,12 @@
|
||||||
<button
|
<button
|
||||||
phx-click="filter"
|
phx-click="filter"
|
||||||
phx-value-filter="degraded"
|
phx-value-filter="degraded"
|
||||||
class={"bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-yellow-300 dark:hover:border-yellow-700 transition-colors #{stat_card_class(@filter, :degraded)}"}
|
class={"bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-wheat-300 dark:hover:border-wheat-700 transition-colors #{stat_card_class(@filter, :degraded)}"}
|
||||||
>
|
>
|
||||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
{t("Degraded")}
|
{t("Degraded")}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-2xl font-bold text-yellow-600 dark:text-yellow-400">
|
<div class="mt-1 text-2xl font-bold text-wheat-600 dark:text-wheat-400">
|
||||||
{@summary.degraded}
|
{@summary.degraded}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -60,12 +60,12 @@
|
||||||
<button
|
<button
|
||||||
phx-click="filter"
|
phx-click="filter"
|
||||||
phx-value-filter="critical"
|
phx-value-filter="critical"
|
||||||
class={"bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-red-300 dark:hover:border-red-700 transition-colors #{stat_card_class(@filter, :critical)}"}
|
class={"bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-4 text-left cursor-pointer hover:border-sweet-salmon-300 dark:hover:border-sweet-salmon-700 transition-colors #{stat_card_class(@filter, :critical)}"}
|
||||||
>
|
>
|
||||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider">
|
||||||
{t("Critical")}
|
{t("Critical")}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-2xl font-bold text-red-600 dark:text-red-400">
|
<div class="mt-1 text-2xl font-bold text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{@summary.critical}
|
{@summary.critical}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -73,19 +73,19 @@
|
||||||
|
|
||||||
<%!-- Weakest Links Table --%>
|
<%!-- Weakest Links Table --%>
|
||||||
<%= if @links != [] do %>
|
<%= if @links != [] do %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
<div class="px-4 py-3 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h2 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h2 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("RF Links")}
|
{t("RF Links")}
|
||||||
<span class="text-gray-400 font-normal ml-1">
|
<span class="text-cool-steel-400 font-normal ml-1">
|
||||||
({length(@links)})
|
({length(@links)})
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/75">
|
||||||
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
<tr class="text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Client")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Client")}</th>
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("AP / Device")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("AP / Device")}</th>
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Signal")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Signal")}</th>
|
||||||
|
|
@ -95,19 +95,19 @@
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Last Seen")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Last Seen")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
<tbody class="divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<%= for link <- @links do %>
|
<%= for link <- @links do %>
|
||||||
<%!-- Main row --%>
|
<%!-- Main row --%>
|
||||||
<tr
|
<tr
|
||||||
phx-click="toggle_expand"
|
phx-click="toggle_expand"
|
||||||
phx-value-id={link.id}
|
phx-value-id={link.id}
|
||||||
class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer"
|
class="text-sm hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="font-medium text-gray-900 dark:text-white">
|
<div class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{link_name(link)}
|
{link_name(link)}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{link.mac_address}
|
{link.mac_address}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -115,17 +115,17 @@
|
||||||
<%= if link.device do %>
|
<%= if link.device do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{link.device_id}"}
|
navigate={~p"/devices/#{link.device_id}"}
|
||||||
class="text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
class="text-cool-steel-900 dark:text-white hover:text-cerulean-600 dark:hover:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
{device_name(link)}
|
{device_name(link)}
|
||||||
</.link>
|
</.link>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400">-</span>
|
<span class="text-cool-steel-400">-</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-16 h-2 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden">
|
<div class="w-16 h-2 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class={"h-full rounded-full #{signal_bar_color(link.signal_strength)}"}
|
class={"h-full rounded-full #{signal_bar_color(link.signal_strength)}"}
|
||||||
style={"width: #{signal_bar_width(link.signal_strength)}%"}
|
style={"width: #{signal_bar_width(link.signal_strength)}%"}
|
||||||
|
|
@ -139,21 +139,21 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-12 h-2 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden">
|
<div class="w-12 h-2 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class={"h-full rounded-full #{snr_bar_color(link.snr)}"}
|
class={"h-full rounded-full #{snr_bar_color(link.snr)}"}
|
||||||
style={"width: #{snr_bar_width(link.snr)}%"}
|
style={"width: #{snr_bar_width(link.snr)}%"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{format_snr(link.snr)}
|
{format_snr(link.snr)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-xs text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-xs text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<span>{format_rate(link.tx_rate)}</span>
|
<span>{format_rate(link.tx_rate)}</span>
|
||||||
<span class="text-gray-300 dark:text-gray-600 mx-1">/</span>
|
<span class="text-cool-steel-300 dark:text-cool-steel-600 mx-1">/</span>
|
||||||
<span>{format_rate(link.rx_rate)}</span>
|
<span>{format_rate(link.rx_rate)}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
|
|
@ -161,7 +161,7 @@
|
||||||
{health_label(link.health)}
|
{health_label(link.health)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
|
<td class="px-4 py-3 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<%= if link.last_seen_at do %>
|
<%= if link.last_seen_at do %>
|
||||||
<time datetime={DateTime.to_iso8601(link.last_seen_at)}>
|
<time datetime={DateTime.to_iso8601(link.last_seen_at)}>
|
||||||
{Calendar.strftime(link.last_seen_at, "%H:%M")}
|
{Calendar.strftime(link.last_seen_at, "%H:%M")}
|
||||||
|
|
@ -174,16 +174,16 @@
|
||||||
|
|
||||||
<%!-- Expandable detail row --%>
|
<%!-- Expandable detail row --%>
|
||||||
<%= if @expanded_id == link.id do %>
|
<%= if @expanded_id == link.id do %>
|
||||||
<tr class="bg-gray-50/50 dark:bg-gray-800/30">
|
<tr class="bg-cool-steel-50/50 dark:bg-cool-steel-800/30">
|
||||||
<td colspan="7" class="px-4 py-4">
|
<td colspan="7" class="px-4 py-4">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<%!-- Signal Strength Detail --%>
|
<%!-- Signal Strength Detail --%>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<h4 class="text-xs font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-2">
|
||||||
{t("Signal Strength")}
|
{t("Signal Strength")}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex-1 h-3 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden">
|
<div class="flex-1 h-3 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class={"h-full rounded-full transition-all #{signal_bar_color(link.signal_strength)}"}
|
class={"h-full rounded-full transition-all #{signal_bar_color(link.signal_strength)}"}
|
||||||
style={"width: #{signal_bar_width(link.signal_strength)}%"}
|
style={"width: #{signal_bar_width(link.signal_strength)}%"}
|
||||||
|
|
@ -194,9 +194,9 @@
|
||||||
{format_signal(link.signal_strength)}
|
{format_signal(link.signal_strength)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex justify-between text-[10px] text-gray-400">
|
<div class="mt-2 flex justify-between text-[10px] text-cool-steel-400">
|
||||||
<span>-100 dBm</span>
|
<span>-100 dBm</span>
|
||||||
<span class="text-yellow-500">-75</span>
|
<span class="text-wheat-500">-75</span>
|
||||||
<span class="text-green-500">-65</span>
|
<span class="text-green-500">-65</span>
|
||||||
<span>-30 dBm</span>
|
<span>-30 dBm</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -204,24 +204,24 @@
|
||||||
|
|
||||||
<%!-- SNR Detail --%>
|
<%!-- SNR Detail --%>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<h4 class="text-xs font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-2">
|
||||||
{t("Signal-to-Noise Ratio")}
|
{t("Signal-to-Noise Ratio")}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex-1 h-3 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden">
|
<div class="flex-1 h-3 rounded-full bg-cool-steel-200 dark:bg-cool-steel-700 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class={"h-full rounded-full transition-all #{snr_bar_color(link.snr)}"}
|
class={"h-full rounded-full transition-all #{snr_bar_color(link.snr)}"}
|
||||||
style={"width: #{snr_bar_width(link.snr)}%"}
|
style={"width: #{snr_bar_width(link.snr)}%"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm font-bold text-gray-900 dark:text-white">
|
<span class="text-sm font-bold text-cool-steel-900 dark:text-white">
|
||||||
{format_snr(link.snr)}
|
{format_snr(link.snr)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex justify-between text-[10px] text-gray-400">
|
<div class="mt-2 flex justify-between text-[10px] text-cool-steel-400">
|
||||||
<span>0 dB</span>
|
<span>0 dB</span>
|
||||||
<span class="text-yellow-500">15</span>
|
<span class="text-wheat-500">15</span>
|
||||||
<span class="text-green-500">25</span>
|
<span class="text-green-500">25</span>
|
||||||
<span>40 dB</span>
|
<span>40 dB</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -229,38 +229,48 @@
|
||||||
|
|
||||||
<%!-- Additional Metrics --%>
|
<%!-- Additional Metrics --%>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<h4 class="text-xs font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-2">
|
||||||
{t("Link Details")}
|
{t("Link Details")}
|
||||||
</h4>
|
</h4>
|
||||||
<dl class="space-y-1 text-sm">
|
<dl class="space-y-1 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{t("Distance")}</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="text-gray-900 dark:text-white font-medium">
|
{t("Distance")}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{format_distance(link.distance)}
|
{format_distance(link.distance)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{t("TX Rate")}</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="text-gray-900 dark:text-white font-medium">
|
{t("TX Rate")}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{format_rate(link.tx_rate)}
|
{format_rate(link.tx_rate)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{t("RX Rate")}</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="text-gray-900 dark:text-white font-medium">
|
{t("RX Rate")}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{format_rate(link.rx_rate)}
|
{format_rate(link.rx_rate)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{t("Uptime")}</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="text-gray-900 dark:text-white font-medium">
|
{t("Uptime")}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{format_uptime(link.uptime_seconds)}
|
{format_uptime(link.uptime_seconds)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<%= if link.capacity_utilization do %>
|
<%= if link.capacity_utilization do %>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-500 dark:text-gray-400">{t("Capacity")}</dt>
|
<dt class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<dd class="text-gray-900 dark:text-white font-medium">
|
{t("Capacity")}
|
||||||
|
</dt>
|
||||||
|
<dd class="text-cool-steel-900 dark:text-white font-medium">
|
||||||
{link.capacity_utilization}%
|
{link.capacity_utilization}%
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -271,8 +281,8 @@
|
||||||
|
|
||||||
<%!-- 24h Sparkline --%>
|
<%!-- 24h Sparkline --%>
|
||||||
<%= if sparkline = @sparkline_data[link.id] do %>
|
<%= if sparkline = @sparkline_data[link.id] do %>
|
||||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-white/10">
|
<div class="mt-4 pt-4 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
<h4 class="text-xs font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-2">
|
||||||
{t("Signal Strength — Last 24 Hours")}
|
{t("Signal Strength — Last 24 Hours")}
|
||||||
</h4>
|
</h4>
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -285,7 +295,7 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
class="text-blue-500"
|
class="text-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -299,12 +309,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12 text-center">
|
<div class="bg-white dark:bg-cool-steel-800/50 rounded-lg border border-cool-steel-200 dark:border-white/10 p-12 text-center">
|
||||||
<.icon name="hero-signal" class="w-12 h-12 mx-auto text-gray-300 dark:text-gray-600" />
|
<.icon
|
||||||
<h3 class="mt-4 text-sm font-semibold text-gray-900 dark:text-white">
|
name="hero-signal"
|
||||||
|
class="w-12 h-12 mx-auto text-cool-steel-300 dark:text-cool-steel-600"
|
||||||
|
/>
|
||||||
|
<h3 class="mt-4 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("No RF links found")}
|
{t("No RF links found")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("RF links will appear here when wireless clients are discovered via SNMP polling.")}
|
{t("RF links will appear here when wireless clients are discovered via SNMP polling.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-3 mb-6">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules"}
|
navigate={~p"/schedules"}
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{@page_title}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">{@page_title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
</.button>
|
</.button>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules"}
|
navigate={~p"/schedules"}
|
||||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@
|
||||||
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
unresolved_alert_count={assigns[:unresolved_alert_count] || 0}
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("On-Call & Schedules")}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
|
{t("On-Call & Schedules")}
|
||||||
|
</h1>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<%= if @tab == "escalation-policies" do %>
|
<%= if @tab == "escalation-policies" do %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/escalation-policies/new"}
|
navigate={~p"/schedules/escalation-policies/new"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("New Policy")}
|
{t("New Policy")}
|
||||||
|
|
@ -18,7 +20,7 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/new"}
|
navigate={~p"/schedules/new"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-cerulean-600 px-4 py-2 text-sm font-semibold text-white hover:bg-cerulean-500 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("New Schedule")}
|
{t("New Schedule")}
|
||||||
|
|
@ -26,7 +28,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 mb-6">
|
||||||
{t("Define on-call rotations and escalation policies for alert notifications.")}
|
{t("Define on-call rotations and escalation policies for alert notifications.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -35,9 +37,10 @@
|
||||||
patch={~p"/schedules?tab=schedules"}
|
patch={~p"/schedules?tab=schedules"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@tab == "schedules" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@tab == "schedules" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@tab != "schedules" &&
|
@tab != "schedules" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Schedules")}
|
{t("Schedules")}
|
||||||
|
|
@ -46,9 +49,10 @@
|
||||||
patch={~p"/schedules?tab=escalation-policies"}
|
patch={~p"/schedules?tab=escalation-policies"}
|
||||||
class={[
|
class={[
|
||||||
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
"inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-colors",
|
||||||
@tab == "escalation-policies" && "bg-gray-900 text-white dark:bg-white dark:text-gray-900",
|
@tab == "escalation-policies" &&
|
||||||
|
"bg-cool-steel-900 text-white dark:bg-white dark:text-cool-steel-900",
|
||||||
@tab != "escalation-policies" &&
|
@tab != "escalation-policies" &&
|
||||||
"bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
"bg-cool-steel-100 text-cool-steel-700 hover:bg-cool-steel-200 dark:bg-cool-steel-800 dark:text-cool-steel-300 dark:hover:bg-cool-steel-700"
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{t("Escalation Policies")}
|
{t("Escalation Policies")}
|
||||||
|
|
@ -61,39 +65,39 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-arrow-trending-up"
|
name="hero-arrow-trending-up"
|
||||||
class="h-12 w-12 text-gray-300 dark:text-gray-600 mx-auto mb-4"
|
class="h-12 w-12 text-cool-steel-300 dark:text-cool-steel-600 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300">
|
<h3 class="text-lg font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("No escalation policies")}
|
{t("No escalation policies")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Create an escalation policy to define how alerts are escalated to on-call users.")}
|
{t("Create an escalation policy to define how alerts are escalated to on-call users.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Name")}
|
{t("Name")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Levels")}
|
{t("Levels")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-900">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-900">
|
||||||
<%= for policy <- @policies do %>
|
<%= for policy <- @policies do %>
|
||||||
<tr
|
<tr
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors cursor-pointer"
|
class="hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800/30 transition-colors cursor-pointer"
|
||||||
phx-click={JS.navigate(~p"/schedules/escalation-policies/#{policy.id}")}
|
phx-click={JS.navigate(~p"/schedules/escalation-policies/#{policy.id}")}
|
||||||
>
|
>
|
||||||
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white">
|
<td class="px-4 py-3 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{policy.name}
|
{policy.name}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{length(policy.rules)}
|
{length(policy.rules)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -107,7 +111,7 @@
|
||||||
<form phx-change="search" phx-submit="search" class="mb-4">
|
<form phx-change="search" phx-submit="search" class="mb-4">
|
||||||
<div class="relative max-w-md">
|
<div class="relative max-w-md">
|
||||||
<span class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5">
|
<span class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5">
|
||||||
<.icon name="hero-magnifying-glass" class="h-4 w-4 text-gray-400" />
|
<.icon name="hero-magnifying-glass" class="h-4 w-4 text-cool-steel-400" />
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -115,7 +119,7 @@
|
||||||
value={@search}
|
value={@search}
|
||||||
placeholder={t("Search schedules…")}
|
placeholder={t("Search schedules…")}
|
||||||
phx-debounce="300"
|
phx-debounce="300"
|
||||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm pl-9 py-1.5"
|
class="block w-full rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-sm pl-9 py-1.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -125,32 +129,32 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
phx-click="today"
|
phx-click="today"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-1.5 text-sm font-medium text-cool-steel-700 dark:text-cool-steel-200 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
{t("Today")}
|
{t("Today")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
phx-click="prev"
|
phx-click="prev"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-1.5 text-gray-500 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 p-1.5 text-cool-steel-500 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
title={t("Previous range")}
|
title={t("Previous range")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-left" class="h-4 w-4" />
|
<.icon name="hero-chevron-left" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
phx-click="next"
|
phx-click="next"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-1.5 text-gray-500 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 p-1.5 text-cool-steel-500 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
title={t("Next range")}
|
title={t("Next range")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-right" class="h-4 w-4" />
|
<.icon name="hero-chevron-right" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<span class="ml-2 text-sm font-semibold text-gray-900 dark:text-white">
|
<span class="ml-2 text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{window_label(@start_date, @range_days)}
|
{window_label(@start_date, @range_days)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<form phx-change="set_range" class="inline-flex">
|
<form phx-change="set_range" class="inline-flex">
|
||||||
<select
|
<select
|
||||||
name="range"
|
name="range"
|
||||||
class="rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm py-1.5"
|
class="rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-sm py-1.5"
|
||||||
>
|
>
|
||||||
<option value="7" selected={@range_days == 7}>{t("1 Week")}</option>
|
<option value="7" selected={@range_days == 7}>{t("1 Week")}</option>
|
||||||
<option value="14" selected={@range_days == 14}>{t("2 Weeks")}</option>
|
<option value="14" selected={@range_days == 14}>{t("2 Weeks")}</option>
|
||||||
|
|
@ -164,12 +168,12 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-calendar-days"
|
name="hero-calendar-days"
|
||||||
class="h-12 w-12 text-gray-300 dark:text-gray-600 mx-auto mb-4"
|
class="h-12 w-12 text-cool-steel-300 dark:text-cool-steel-600 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300">
|
<h3 class="text-lg font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("No schedules")}
|
{t("No schedules")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Create an on-call schedule to define rotation coverage.")}
|
{t("Create an on-call schedule to define rotation coverage.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -179,21 +183,23 @@
|
||||||
<%= for schedule <- @schedules do %>
|
<%= for schedule <- @schedules do %>
|
||||||
<% days = days_in_window(@start_date, @range_days) %>
|
<% days = days_in_window(@start_date, @range_days) %>
|
||||||
<% today_idx = today_offset(@start_date, @range_days) %>
|
<% today_idx = today_offset(@start_date, @range_days) %>
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/#{schedule.id}"}
|
navigate={~p"/schedules/#{schedule.id}"}
|
||||||
class="text-base font-semibold text-blue-600 dark:text-blue-400 hover:underline"
|
class="text-base font-semibold text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
{schedule.name}
|
{schedule.name}
|
||||||
</.link>
|
</.link>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5">
|
||||||
{schedule.timezone}
|
{schedule.timezone}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">{t("On-Call Now")}</p>
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
{t("On-Call Now")}
|
||||||
|
</p>
|
||||||
<%= if schedule.current_on_call do %>
|
<%= if schedule.current_on_call do %>
|
||||||
<span class="inline-flex items-center gap-1.5">
|
<span class="inline-flex items-center gap-1.5">
|
||||||
<span class={[
|
<span class={[
|
||||||
|
|
@ -201,25 +207,27 @@
|
||||||
schedule.current_on_call_color
|
schedule.current_on_call_color
|
||||||
]}>
|
]}>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{schedule.current_on_call_name}
|
{schedule.current_on_call_name}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-sm text-gray-400 dark:text-gray-500">{t("No one")}</span>
|
<span class="text-sm text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
|
{t("No one")}
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Day header row --%>
|
<%!-- Day header row --%>
|
||||||
<div
|
<div
|
||||||
class="grid gap-px text-xs text-gray-500 dark:text-gray-400 mb-1"
|
class="grid gap-px text-xs text-cool-steel-500 dark:text-cool-steel-400 mb-1"
|
||||||
style={"grid-template-columns: repeat(#{@range_days}, minmax(0, 1fr));"}
|
style={"grid-template-columns: repeat(#{@range_days}, minmax(0, 1fr));"}
|
||||||
>
|
>
|
||||||
<%= for {day, idx} <- Enum.with_index(days) do %>
|
<%= for {day, idx} <- Enum.with_index(days) do %>
|
||||||
<div class={[
|
<div class={[
|
||||||
"px-1 py-0.5 text-center",
|
"px-1 py-0.5 text-center",
|
||||||
today_idx == idx && "font-bold text-red-600 dark:text-red-400"
|
today_idx == idx && "font-bold text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
]}>
|
]}>
|
||||||
<div class="leading-tight">{Calendar.strftime(day, "%a")}</div>
|
<div class="leading-tight">{Calendar.strftime(day, "%a")}</div>
|
||||||
<div class="leading-tight">{Calendar.strftime(day, "%-d")}</div>
|
<div class="leading-tight">{Calendar.strftime(day, "%-d")}</div>
|
||||||
|
|
@ -229,7 +237,7 @@
|
||||||
|
|
||||||
<%!-- Gantt strip --%>
|
<%!-- Gantt strip --%>
|
||||||
<div
|
<div
|
||||||
class="relative grid gap-px h-8 rounded-md overflow-hidden bg-gray-100 dark:bg-gray-800"
|
class="relative grid gap-px h-8 rounded-md overflow-hidden bg-cool-steel-100 dark:bg-cool-steel-800"
|
||||||
style={"grid-template-columns: repeat(#{@range_days}, minmax(0, 1fr));"}
|
style={"grid-template-columns: repeat(#{@range_days}, minmax(0, 1fr));"}
|
||||||
>
|
>
|
||||||
<%= for segment <- schedule.segments do %>
|
<%= for segment <- schedule.segments do %>
|
||||||
|
|
@ -246,7 +254,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if today_idx do %>
|
<%= if today_idx do %>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute top-0 bottom-0 w-px bg-red-500"
|
class="pointer-events-none absolute top-0 bottom-0 w-px bg-sweet-salmon-500"
|
||||||
style={"left: calc((100% / #{@range_days}) * #{today_idx});"}
|
style={"left: calc((100% / #{@range_days}) * #{today_idx});"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,21 @@
|
||||||
<div class="flex items-center gap-3 min-w-0">
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules"}
|
navigate={~p"/schedules"}
|
||||||
class="shrink-0 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
class="shrink-0 text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
<.icon name="hero-arrow-left" class="h-5 w-5" />
|
||||||
</.link>
|
</.link>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white truncate">{@schedule.name}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white truncate">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">{@schedule.timezone}</p>
|
{@schedule.name}
|
||||||
|
</h1>
|
||||||
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">{@schedule.timezone}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/schedules/#{@schedule.id}/edit"}
|
navigate={~p"/schedules/#{@schedule.id}/edit"}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 border border-cool-steel-300 dark:border-cool-steel-600 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-pencil" class="h-4 w-4" />
|
<.icon name="hero-pencil" class="h-4 w-4" />
|
||||||
<span class="hidden sm:inline">{t("Edit")}</span>
|
<span class="hidden sm:inline">{t("Edit")}</span>
|
||||||
|
|
@ -28,7 +30,7 @@
|
||||||
<button
|
<button
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
data-confirm={t("Are you sure you want to delete this schedule?")}
|
data-confirm={t("Are you sure you want to delete this schedule?")}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-gray-800 px-3 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-gray-300 dark:border-gray-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
class="inline-flex items-center gap-2 rounded-lg bg-white dark:bg-cool-steel-800 px-3 py-2 text-sm font-medium text-sweet-salmon-600 dark:text-sweet-salmon-400 border border-cool-steel-300 dark:border-cool-steel-600 hover:bg-sweet-salmon-50 dark:hover:bg-sweet-salmon-900/20 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
<span class="hidden sm:inline">{t("Delete")}</span>
|
<span class="hidden sm:inline">{t("Delete")}</span>
|
||||||
|
|
@ -37,36 +39,38 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @schedule.description do %>
|
<%= if @schedule.description do %>
|
||||||
<p class="mb-6 text-sm text-gray-600 dark:text-gray-400">{@schedule.description}</p>
|
<p class="mb-6 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
{@schedule.description}
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%!-- On-Call Status --%>
|
<%!-- On-Call Status --%>
|
||||||
<div class="mb-8 rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4">
|
<div class="mb-8 rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class={[
|
<div class={[
|
||||||
"h-10 w-10 rounded-full flex items-center justify-center",
|
"h-10 w-10 rounded-full flex items-center justify-center",
|
||||||
@on_call && "bg-green-100 dark:bg-green-900/40",
|
@on_call && "bg-green-100 dark:bg-green-900/40",
|
||||||
!@on_call && "bg-gray-100 dark:bg-gray-800"
|
!@on_call && "bg-cool-steel-100 dark:bg-cool-steel-800"
|
||||||
]}>
|
]}>
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-user"
|
name="hero-user"
|
||||||
class={[
|
class={[
|
||||||
"h-5 w-5",
|
"h-5 w-5",
|
||||||
@on_call && "text-green-600 dark:text-green-400",
|
@on_call && "text-green-600 dark:text-green-400",
|
||||||
!@on_call && "text-gray-400 dark:text-gray-500"
|
!@on_call && "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<p class="text-xs font-medium uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Currently On-Call")}
|
{t("Currently On-Call")}
|
||||||
</p>
|
</p>
|
||||||
<%= if @on_call do %>
|
<%= if @on_call do %>
|
||||||
<p class="text-sm font-semibold text-gray-900 dark:text-white">
|
<p class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{display_name(@on_call)}
|
{display_name(@on_call)}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No one is currently on-call")}
|
{t("No one is currently on-call")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -81,30 +85,30 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
phx-click="timeline_today"
|
phx-click="timeline_today"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 px-3 py-1.5 text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
{t("Today")}
|
{t("Today")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
phx-click="timeline_prev"
|
phx-click="timeline_prev"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 p-1.5 text-cool-steel-500 dark:text-cool-steel-400 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-left" class="h-4 w-4" />
|
<.icon name="hero-chevron-left" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
phx-click="timeline_next"
|
phx-click="timeline_next"
|
||||||
class="rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700"
|
class="rounded-md border border-cool-steel-300 dark:border-cool-steel-600 bg-white dark:bg-cool-steel-800 p-1.5 text-cool-steel-500 dark:text-cool-steel-400 hover:bg-cool-steel-50 dark:hover:bg-cool-steel-700"
|
||||||
>
|
>
|
||||||
<.icon name="hero-chevron-right" class="h-4 w-4" />
|
<.icon name="hero-chevron-right" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<span class="text-sm font-semibold text-gray-900 dark:text-white ml-2">
|
<span class="text-sm font-semibold text-cool-steel-900 dark:text-white ml-2">
|
||||||
{Calendar.strftime(@timeline_start, "%b %-d")} – {Calendar.strftime(
|
{Calendar.strftime(@timeline_start, "%b %-d")} – {Calendar.strftime(
|
||||||
@timeline_end,
|
@timeline_end,
|
||||||
"%b %-d, %Y"
|
"%b %-d, %Y"
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("2 Weeks")}
|
{t("2 Weeks")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -112,8 +116,8 @@
|
||||||
<%!-- Layer timelines --%>
|
<%!-- Layer timelines --%>
|
||||||
<%= if @layer_timelines != [] do %>
|
<%= if @layer_timelines != [] do %>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4 mb-4 min-w-[320px]">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4 mb-4 min-w-[320px]">
|
||||||
<h3 class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">
|
<h3 class="text-sm font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-4">
|
||||||
{t("Configuration Layers")}
|
{t("Configuration Layers")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -124,8 +128,8 @@
|
||||||
<div class="w-28 shrink-0"></div>
|
<div class="w-28 shrink-0"></div>
|
||||||
<div class="flex-1 flex">
|
<div class="flex-1 flex">
|
||||||
<%= for day <- @timeline_days do %>
|
<%= for day <- @timeline_days do %>
|
||||||
<div class="flex-1 text-center border-l first:border-l-0 border-gray-200 dark:border-gray-700">
|
<div class="flex-1 text-center border-l first:border-l-0 border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<span class="text-[10px] text-gray-400 dark:text-gray-500">
|
<span class="text-[10px] text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{Calendar.strftime(day, "%-d %a")}
|
{Calendar.strftime(day, "%-d %a")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -135,16 +139,16 @@
|
||||||
<%!-- Timeline bar --%>
|
<%!-- Timeline bar --%>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-28 shrink-0 pr-2 text-right">
|
<div class="w-28 shrink-0 pr-2 text-right">
|
||||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 truncate">
|
<span class="text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300 truncate">
|
||||||
{lt.layer.name}
|
{lt.layer.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 relative h-8 bg-gray-100 dark:bg-gray-800 rounded overflow-hidden">
|
<div class="flex-1 relative h-8 bg-cool-steel-100 dark:bg-cool-steel-800 rounded overflow-hidden">
|
||||||
<%= for span <- lt.spans do %>
|
<%= for span <- lt.spans do %>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
"absolute inset-y-0 flex items-center px-1 overflow-hidden",
|
"absolute inset-y-0 flex items-center px-1 overflow-hidden",
|
||||||
@user_colors[span.user.id] || "bg-gray-400"
|
@user_colors[span.user.id] || "bg-cool-steel-400"
|
||||||
]}
|
]}
|
||||||
style={"left: #{span.start_offset / span.total_hours * 100}%; width: #{span.duration / span.total_hours * 100}%;"}
|
style={"left: #{span.start_offset / span.total_hours * 100}%; width: #{span.duration / span.total_hours * 100}%;"}
|
||||||
>
|
>
|
||||||
|
|
@ -157,7 +161,7 @@
|
||||||
<% today_offset = Date.diff(Date.utc_today(), @timeline_start) %>
|
<% today_offset = Date.diff(Date.utc_today(), @timeline_start) %>
|
||||||
<%= if today_offset >= 0 and today_offset < @timeline_days do %>
|
<%= if today_offset >= 0 and today_offset < @timeline_days do %>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-y-0 w-px bg-red-500 z-10"
|
class="absolute inset-y-0 w-px bg-sweet-salmon-500 z-10"
|
||||||
style={"left: #{(today_offset * 24 + DateTime.utc_now().hour) / (length(@timeline_days) * 24) * 100}%;"}
|
style={"left: #{(today_offset * 24 + DateTime.utc_now().hour) / (length(@timeline_days) * 24) * 100}%;"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,8 +175,8 @@
|
||||||
|
|
||||||
<%!-- Final Schedule --%>
|
<%!-- Final Schedule --%>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4 min-w-[320px]">
|
<div class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4 min-w-[320px]">
|
||||||
<h3 class="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-4">
|
<h3 class="text-sm font-semibold text-cool-steel-500 dark:text-cool-steel-400 uppercase tracking-wider mb-4">
|
||||||
{t("Final Schedule")}
|
{t("Final Schedule")}
|
||||||
</h3>
|
</h3>
|
||||||
<%!-- Day headers --%>
|
<%!-- Day headers --%>
|
||||||
|
|
@ -180,8 +184,8 @@
|
||||||
<div class="w-28 shrink-0"></div>
|
<div class="w-28 shrink-0"></div>
|
||||||
<div class="flex-1 flex">
|
<div class="flex-1 flex">
|
||||||
<%= for day <- @timeline_days do %>
|
<%= for day <- @timeline_days do %>
|
||||||
<div class="flex-1 text-center border-l first:border-l-0 border-gray-200 dark:border-gray-700">
|
<div class="flex-1 text-center border-l first:border-l-0 border-cool-steel-200 dark:border-cool-steel-700">
|
||||||
<span class="text-[10px] text-gray-400 dark:text-gray-500">
|
<span class="text-[10px] text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{Calendar.strftime(day, "%-d %a")}
|
{Calendar.strftime(day, "%-d %a")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -191,12 +195,12 @@
|
||||||
<%!-- Timeline bar --%>
|
<%!-- Timeline bar --%>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-28 shrink-0"></div>
|
<div class="w-28 shrink-0"></div>
|
||||||
<div class="flex-1 relative h-8 bg-gray-100 dark:bg-gray-800 rounded overflow-hidden">
|
<div class="flex-1 relative h-8 bg-cool-steel-100 dark:bg-cool-steel-800 rounded overflow-hidden">
|
||||||
<%= for span <- @final_spans do %>
|
<%= for span <- @final_spans do %>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
"absolute inset-y-0 flex items-center px-1 overflow-hidden",
|
"absolute inset-y-0 flex items-center px-1 overflow-hidden",
|
||||||
@user_colors[span.user.id] || "bg-gray-400"
|
@user_colors[span.user.id] || "bg-cool-steel-400"
|
||||||
]}
|
]}
|
||||||
style={"left: #{span.start_offset / span.total_hours * 100}%; width: #{span.duration / span.total_hours * 100}%;"}
|
style={"left: #{span.start_offset / span.total_hours * 100}%; width: #{span.duration / span.total_hours * 100}%;"}
|
||||||
>
|
>
|
||||||
|
|
@ -209,7 +213,7 @@
|
||||||
<% today_offset = Date.diff(Date.utc_today(), @timeline_start) %>
|
<% today_offset = Date.diff(Date.utc_today(), @timeline_start) %>
|
||||||
<%= if today_offset >= 0 and today_offset < @timeline_days do %>
|
<%= if today_offset >= 0 and today_offset < @timeline_days do %>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-y-0 w-px bg-red-500 z-10"
|
class="absolute inset-y-0 w-px bg-sweet-salmon-500 z-10"
|
||||||
style={"left: #{(today_offset * 24 + DateTime.utc_now().hour) / (length(@timeline_days) * 24) * 100}%;"}
|
style={"left: #{(today_offset * 24 + DateTime.utc_now().hour) / (length(@timeline_days) * 24) * 100}%;"}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -224,10 +228,10 @@
|
||||||
<%!-- Layers --%>
|
<%!-- Layers --%>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{t("Layers")}</h2>
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">{t("Layers")}</h2>
|
||||||
<button
|
<button
|
||||||
phx-click="toggle_add_layer"
|
phx-click="toggle_add_layer"
|
||||||
class="inline-flex items-center gap-1.5 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500"
|
class="inline-flex items-center gap-1.5 text-sm font-medium text-cerulean-600 dark:text-cerulean-400 hover:text-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("Add Layer")}
|
{t("Add Layer")}
|
||||||
|
|
@ -235,7 +239,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @show_add_layer do %>
|
<%= if @show_add_layer do %>
|
||||||
<div class="mb-4 rounded-lg border border-blue-200 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-900/10 p-4">
|
<div class="mb-4 rounded-lg border border-cerulean-200 dark:border-cerulean-800 bg-cerulean-50/50 dark:bg-cerulean-900/10 p-4">
|
||||||
<.form for={@layer_form} phx-change="validate_layer" phx-submit="save_layer">
|
<.form for={@layer_form} phx-change="validate_layer" phx-submit="save_layer">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
||||||
<.input
|
<.input
|
||||||
|
|
@ -280,19 +284,19 @@
|
||||||
|
|
||||||
<%!-- User selection for new layer --%>
|
<%!-- User selection for new layer --%>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300 mb-2">
|
||||||
{t("Members")}
|
{t("Members")}
|
||||||
</label>
|
</label>
|
||||||
<%= if @new_layer_members != [] do %>
|
<%= if @new_layer_members != [] do %>
|
||||||
<div class="flex flex-wrap gap-2 mb-2">
|
<div class="flex flex-wrap gap-2 mb-2">
|
||||||
<%= for user <- @new_layer_members do %>
|
<%= for user <- @new_layer_members do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-blue-100 dark:bg-blue-900/40 pl-2.5 pr-1 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-300">
|
<span class="inline-flex items-center gap-1 rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 pl-2.5 pr-1 py-0.5 text-xs font-medium text-cerulean-700 dark:text-cerulean-300">
|
||||||
{display_name(user)}
|
{display_name(user)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="new_layer_remove_user"
|
phx-click="new_layer_remove_user"
|
||||||
phx-value-id={user.id}
|
phx-value-id={user.id}
|
||||||
class="ml-0.5 rounded-full p-0.5 hover:bg-blue-200 dark:hover:bg-blue-800 transition-colors"
|
class="ml-0.5 rounded-full p-0.5 hover:bg-cerulean-200 dark:hover:bg-cerulean-800 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-3 w-3" />
|
<.icon name="hero-x-mark" class="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -306,7 +310,7 @@
|
||||||
<select
|
<select
|
||||||
phx-change="new_layer_add_user"
|
phx-change="new_layer_add_user"
|
||||||
name="user_id"
|
name="user_id"
|
||||||
class="block w-full max-w-xs rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm py-1.5"
|
class="block w-full max-w-xs rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-sm py-1.5"
|
||||||
>
|
>
|
||||||
<option value="">{t("Select a user")}</option>
|
<option value="">{t("Select a user")}</option>
|
||||||
<%= for user <- available do %>
|
<%= for user <- available do %>
|
||||||
|
|
@ -323,7 +327,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_add_layer"
|
phx-click="toggle_add_layer"
|
||||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -333,8 +337,8 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if Enum.empty?(@schedule.layers) do %>
|
<%= if Enum.empty?(@schedule.layers) do %>
|
||||||
<div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-6 text-center">
|
<div class="rounded-lg border border-dashed border-cool-steel-300 dark:border-cool-steel-600 p-6 text-center">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No layers configured. Add a layer to define rotation coverage.")}
|
{t("No layers configured. Add a layer to define rotation coverage.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -351,16 +355,16 @@
|
||||||
<div
|
<div
|
||||||
data-id={layer.id}
|
data-id={layer.id}
|
||||||
draggable="true"
|
draggable="true"
|
||||||
class="rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-gray-900 p-4 cursor-move"
|
class="rounded-lg border border-cool-steel-200 dark:border-white/10 bg-white dark:bg-cool-steel-900 p-4 cursor-move"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between mb-3 gap-3">
|
<div class="flex items-start justify-between mb-3 gap-3">
|
||||||
<div class="flex items-start gap-2 min-w-0">
|
<div class="flex items-start gap-2 min-w-0">
|
||||||
<.icon name="hero-bars-3" class="h-4 w-4 text-gray-400 mt-0.5 shrink-0" />
|
<.icon name="hero-bars-3" class="h-4 w-4 text-cool-steel-400 mt-0.5 shrink-0" />
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white truncate">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white truncate">
|
||||||
{layer.name}
|
{layer.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400 truncate">
|
||||||
{String.capitalize(layer.rotation_type)} rotation, every {layer.rotation_interval}
|
{String.capitalize(layer.rotation_type)} rotation, every {layer.rotation_interval}
|
||||||
<%= case layer.rotation_type do %>
|
<%= case layer.rotation_type do %>
|
||||||
<% "daily" -> %>
|
<% "daily" -> %>
|
||||||
|
|
@ -379,7 +383,7 @@
|
||||||
phx-click="move_layer"
|
phx-click="move_layer"
|
||||||
phx-value-id={layer.id}
|
phx-value-id={layer.id}
|
||||||
phx-value-direction="up"
|
phx-value-direction="up"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300 disabled:opacity-30 disabled:hover:text-cool-steel-400"
|
||||||
disabled={idx == 0}
|
disabled={idx == 0}
|
||||||
title={t("Move up")}
|
title={t("Move up")}
|
||||||
>
|
>
|
||||||
|
|
@ -389,7 +393,7 @@
|
||||||
phx-click="move_layer"
|
phx-click="move_layer"
|
||||||
phx-value-id={layer.id}
|
phx-value-id={layer.id}
|
||||||
phx-value-direction="down"
|
phx-value-direction="down"
|
||||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:hover:text-gray-400"
|
class="text-cool-steel-400 hover:text-cool-steel-600 dark:hover:text-cool-steel-300 disabled:opacity-30 disabled:hover:text-cool-steel-400"
|
||||||
disabled={idx == layer_count - 1}
|
disabled={idx == layer_count - 1}
|
||||||
title={t("Move down")}
|
title={t("Move down")}
|
||||||
>
|
>
|
||||||
|
|
@ -399,7 +403,7 @@
|
||||||
phx-click="delete_layer"
|
phx-click="delete_layer"
|
||||||
phx-value-id={layer.id}
|
phx-value-id={layer.id}
|
||||||
data-confirm={t("Are you sure you want to delete this layer?")}
|
data-confirm={t("Are you sure you want to delete this layer?")}
|
||||||
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors ml-1"
|
class="text-cool-steel-400 hover:text-sweet-salmon-500 dark:hover:text-sweet-salmon-400 transition-colors ml-1"
|
||||||
title={t("Delete")}
|
title={t("Delete")}
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
|
|
@ -409,22 +413,22 @@
|
||||||
|
|
||||||
<%!-- Members --%>
|
<%!-- Members --%>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">
|
<p class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 mb-2">
|
||||||
{t("Members")}
|
{t("Members")}
|
||||||
</p>
|
</p>
|
||||||
<%= if Enum.empty?(layer.members) do %>
|
<%= if Enum.empty?(layer.members) do %>
|
||||||
<p class="text-xs text-gray-400 dark:text-gray-500 mb-2">
|
<p class="text-xs text-cool-steel-400 dark:text-cool-steel-500 mb-2">
|
||||||
{t("No members yet")}
|
{t("No members yet")}
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="flex flex-wrap gap-2 mb-2">
|
<div class="flex flex-wrap gap-2 mb-2">
|
||||||
<%= for member <- Enum.sort_by(layer.members, & &1.position) do %>
|
<%= for member <- Enum.sort_by(layer.members, & &1.position) do %>
|
||||||
<span class="inline-flex items-center gap-1 rounded-full bg-gray-100 dark:bg-gray-800 pl-2.5 pr-1 py-0.5 text-xs font-medium text-gray-700 dark:text-gray-300">
|
<span class="inline-flex items-center gap-1 rounded-full bg-cool-steel-100 dark:bg-cool-steel-800 pl-2.5 pr-1 py-0.5 text-xs font-medium text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{display_name(member.user)}
|
{display_name(member.user)}
|
||||||
<button
|
<button
|
||||||
phx-click="remove_member"
|
phx-click="remove_member"
|
||||||
phx-value-id={member.id}
|
phx-value-id={member.id}
|
||||||
class="ml-0.5 rounded-full p-0.5 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
class="ml-0.5 rounded-full p-0.5 hover:bg-cool-steel-200 dark:hover:bg-cool-steel-700 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-3 w-3" />
|
<.icon name="hero-x-mark" class="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -441,7 +445,7 @@
|
||||||
phx-change="add_member"
|
phx-change="add_member"
|
||||||
name="user_id"
|
name="user_id"
|
||||||
phx-value-layer_id={layer.id}
|
phx-value-layer_id={layer.id}
|
||||||
class="block w-full max-w-xs rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-sm py-1.5"
|
class="block w-full max-w-xs rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-sm py-1.5"
|
||||||
>
|
>
|
||||||
<option value="">{t("Select a user")}</option>
|
<option value="">{t("Select a user")}</option>
|
||||||
<%= for user <- available_users do %>
|
<%= for user <- available_users do %>
|
||||||
|
|
@ -452,8 +456,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Restrictions editor --%>
|
<%!-- Restrictions editor --%>
|
||||||
<div class="mt-3 pt-3 border-t border-gray-100 dark:border-white/5">
|
<div class="mt-3 pt-3 border-t border-cool-steel-100 dark:border-white/5">
|
||||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">
|
<p class="text-xs font-medium text-cool-steel-500 dark:text-cool-steel-400 mb-2">
|
||||||
{t("Restrict on-call shifts")}
|
{t("Restrict on-call shifts")}
|
||||||
</p>
|
</p>
|
||||||
<form
|
<form
|
||||||
|
|
@ -464,7 +468,7 @@
|
||||||
<input type="hidden" name="layer_id" value={layer.id} />
|
<input type="hidden" name="layer_id" value={layer.id} />
|
||||||
<select
|
<select
|
||||||
name="restriction_type"
|
name="restriction_type"
|
||||||
class="block rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1"
|
class="block rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-xs py-1"
|
||||||
>
|
>
|
||||||
<option value="" selected={is_nil(layer.restriction_type)}>
|
<option value="" selected={is_nil(layer.restriction_type)}>
|
||||||
{t("Always on call (no restriction)")}
|
{t("Always on call (no restriction)")}
|
||||||
|
|
@ -477,25 +481,25 @@
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<%= if layer.restriction_type in ["time_of_day", "time_of_week"] do %>
|
<%= if layer.restriction_type in ["time_of_day", "time_of_week"] do %>
|
||||||
<div class="flex items-center gap-2 text-xs text-gray-700 dark:text-gray-300">
|
<div class="flex items-center gap-2 text-xs text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<span>{t("From")}</span>
|
<span>{t("From")}</span>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
name="start_time"
|
name="start_time"
|
||||||
value={layer.restrictions["start_time"] || "09:00"}
|
value={layer.restrictions["start_time"] || "09:00"}
|
||||||
class="rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1"
|
class="rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-xs py-1"
|
||||||
/>
|
/>
|
||||||
<span>{t("to")}</span>
|
<span>{t("to")}</span>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
name="end_time"
|
name="end_time"
|
||||||
value={layer.restrictions["end_time"] || "17:00"}
|
value={layer.restrictions["end_time"] || "17:00"}
|
||||||
class="rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-white text-xs py-1"
|
class="rounded-md border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800 dark:text-white text-xs py-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if layer.restriction_type == "time_of_week" do %>
|
<%= if layer.restriction_type == "time_of_week" do %>
|
||||||
<div class="flex flex-wrap gap-2 text-xs text-gray-700 dark:text-gray-300">
|
<div class="flex flex-wrap gap-2 text-xs text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<% selected_days =
|
<% selected_days =
|
||||||
((layer.restrictions || %{})["days"] || [])
|
((layer.restrictions || %{})["days"] || [])
|
||||||
|> Enum.map(fn d ->
|
|> Enum.map(fn d ->
|
||||||
|
|
@ -511,7 +515,7 @@
|
||||||
name={"days[#{day}]"}
|
name={"days[#{day}]"}
|
||||||
value="true"
|
value="true"
|
||||||
checked={day in selected_days}
|
checked={day in selected_days}
|
||||||
class="rounded border-gray-300 dark:border-gray-600 dark:bg-gray-800"
|
class="rounded border-cool-steel-300 dark:border-cool-steel-600 dark:bg-cool-steel-800"
|
||||||
/>
|
/>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -529,10 +533,10 @@
|
||||||
<%!-- Overrides --%>
|
<%!-- Overrides --%>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{t("Overrides")}</h2>
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white">{t("Overrides")}</h2>
|
||||||
<button
|
<button
|
||||||
phx-click="toggle_add_override"
|
phx-click="toggle_add_override"
|
||||||
class="inline-flex items-center gap-1.5 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500"
|
class="inline-flex items-center gap-1.5 text-sm font-medium text-cerulean-600 dark:text-cerulean-400 hover:text-cerulean-500"
|
||||||
>
|
>
|
||||||
<.icon name="hero-plus" class="h-4 w-4" />
|
<.icon name="hero-plus" class="h-4 w-4" />
|
||||||
{t("Add Override")}
|
{t("Add Override")}
|
||||||
|
|
@ -540,7 +544,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= if @show_add_override do %>
|
<%= if @show_add_override do %>
|
||||||
<div class="mb-4 rounded-lg border border-blue-200 dark:border-blue-800 bg-blue-50/50 dark:bg-blue-900/10 p-4">
|
<div class="mb-4 rounded-lg border border-cerulean-200 dark:border-cerulean-800 bg-cerulean-50/50 dark:bg-cerulean-900/10 p-4">
|
||||||
<.form for={@override_form} phx-change="validate_override" phx-submit="save_override">
|
<.form for={@override_form} phx-change="validate_override" phx-submit="save_override">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
|
||||||
<.input
|
<.input
|
||||||
|
|
@ -568,7 +572,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="toggle_add_override"
|
phx-click="toggle_add_override"
|
||||||
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-400 hover:text-cool-steel-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -578,38 +582,38 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if Enum.empty?(@schedule.overrides) do %>
|
<%= if Enum.empty?(@schedule.overrides) do %>
|
||||||
<div class="rounded-lg border border-dashed border-gray-300 dark:border-gray-600 p-6 text-center">
|
<div class="rounded-lg border border-dashed border-cool-steel-300 dark:border-cool-steel-600 p-6 text-center">
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("No overrides configured.")}
|
{t("No overrides configured.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("User")}
|
{t("User")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Start")}
|
{t("Start")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("End")}
|
{t("End")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3"></th>
|
<th class="px-4 py-3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5 bg-white dark:bg-gray-900">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5 bg-white dark:bg-cool-steel-900">
|
||||||
<%= for override <- @schedule.overrides do %>
|
<%= for override <- @schedule.overrides do %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-3 text-sm text-gray-900 dark:text-white">
|
<td class="px-4 py-3 text-sm text-cool-steel-900 dark:text-white">
|
||||||
{display_name(override.user)}
|
{display_name(override.user)}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{Calendar.strftime(override.start_time, "%Y-%m-%d %H:%M")}
|
{Calendar.strftime(override.start_time, "%Y-%m-%d %H:%M")}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-3 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{Calendar.strftime(override.end_time, "%Y-%m-%d %H:%M")}
|
{Calendar.strftime(override.end_time, "%Y-%m-%d %H:%M")}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-right">
|
<td class="px-4 py-3 text-right">
|
||||||
|
|
@ -617,7 +621,7 @@
|
||||||
phx-click="delete_override"
|
phx-click="delete_override"
|
||||||
phx-value-id={override.id}
|
phx-value-id={override.id}
|
||||||
data-confirm={t("Are you sure?")}
|
data-confirm={t("Are you sure?")}
|
||||||
class="text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors"
|
class="text-cool-steel-400 hover:text-sweet-salmon-500 dark:hover:text-sweet-salmon-400 transition-colors"
|
||||||
>
|
>
|
||||||
<.icon name="hero-trash" class="h-4 w-4" />
|
<.icon name="hero-trash" class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
do: ~p"/sites/#{@site.id}",
|
do: ~p"/sites/#{@site.id}",
|
||||||
else: ~p"/sites"
|
else: ~p"/sites"
|
||||||
}
|
}
|
||||||
class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|
class="inline-flex items-center gap-1 text-sm text-cool-steel-600 hover:text-cool-steel-900 dark:text-cool-steel-400 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
<.icon name="hero-arrow-left" class="h-4 w-4" />
|
||||||
{if @live_action == :edit, do: "Back to Site", else: "Back to Sites"}
|
{if @live_action == :edit, do: "Back to Site", else: "Back to Sites"}
|
||||||
|
|
@ -32,14 +32,16 @@
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Parent Site")}
|
{t("Parent Site")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<select
|
<select
|
||||||
id={@form[:parent_site_id].id}
|
id={@form[:parent_site_id].id}
|
||||||
name={@form[:parent_site_id].name}
|
name={@form[:parent_site_id].name}
|
||||||
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
>
|
>
|
||||||
<option value="">No parent site</option>
|
<option value="">No parent site</option>
|
||||||
{Phoenix.HTML.Form.options_for_select(
|
{Phoenix.HTML.Form.options_for_select(
|
||||||
|
|
@ -52,15 +54,18 @@
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
Location <span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
Location
|
||||||
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id={@form[:location].id}
|
id={@form[:location].id}
|
||||||
name={@form[:location].name}
|
name={@form[:location].name}
|
||||||
value={@form[:location].value}
|
value={@form[:location].value}
|
||||||
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -69,16 +74,16 @@
|
||||||
<div class="mt-6 border-t pt-6">
|
<div class="mt-6 border-t pt-6">
|
||||||
<h3 class="text-base font-medium mb-4">
|
<h3 class="text-base font-medium mb-4">
|
||||||
{t("Geographic Location")}
|
{t("Geographic Location")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">(optional)</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t("Add geographic coordinates to enable network mapping and spatial analysis.")}
|
{t("Add geographic coordinates to enable network mapping and spatial analysis.")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Address")}
|
{t("Address")}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|
@ -88,17 +93,17 @@
|
||||||
name={@form[:address].name}
|
name={@form[:address].name}
|
||||||
value={@form[:address].value}
|
value={@form[:address].value}
|
||||||
placeholder="123 Main St, City, State, Country"
|
placeholder="123 Main St, City, State, Country"
|
||||||
class="flex-1 rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="flex-1 rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="geocode"
|
phx-click="geocode"
|
||||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-cerulean-600 hover:bg-cerulean-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cerulean-500 dark:focus:ring-offset-cool-steel-800"
|
||||||
>
|
>
|
||||||
{t("Geocode")}
|
{t("Geocode")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Enter a full address and click Geocode to automatically fill latitude/longitude"
|
"Enter a full address and click Geocode to automatically fill latitude/longitude"
|
||||||
)}
|
)}
|
||||||
|
|
@ -109,7 +114,7 @@
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Latitude")}
|
{t("Latitude")}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
|
|
@ -121,9 +126,9 @@
|
||||||
placeholder="40.7128"
|
placeholder="40.7128"
|
||||||
min="-90"
|
min="-90"
|
||||||
max="90"
|
max="90"
|
||||||
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
-90 to 90
|
-90 to 90
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -131,7 +136,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Longitude")}
|
{t("Longitude")}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
|
|
@ -143,9 +148,9 @@
|
||||||
placeholder="-74.0060"
|
placeholder="-74.0060"
|
||||||
min="-180"
|
min="-180"
|
||||||
max="180"
|
max="180"
|
||||||
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
-180 to 180
|
-180 to 180
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -155,10 +160,10 @@
|
||||||
<!-- Map Preview -->
|
<!-- Map Preview -->
|
||||||
<%= if @form[:latitude].value && @form[:longitude].value do %>
|
<%= if @form[:latitude].value && @form[:longitude].value do %>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Map Preview")}
|
{t("Map Preview")}
|
||||||
</span>
|
</span>
|
||||||
<div class="border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden">
|
<div class="border border-cool-steel-300 dark:border-cool-steel-600 rounded-lg overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={"https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/#{@form[:longitude].value},#{@form[:latitude].value},14,0/400x200?access_token=pk.1234"}
|
src={"https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/#{@form[:longitude].value},#{@form[:latitude].value},14,0/400x200?access_token=pk.1234"}
|
||||||
alt="Location preview"
|
alt="Location preview"
|
||||||
|
|
@ -166,7 +171,7 @@
|
||||||
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDQwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+Cjx0ZXh0IHg9IjIwMCIgeT0iMTAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNiIgZmlsbD0iIzZCNzI4MCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiPk1hcCBwcmV2aWV3IG5vdCBhdmFpbGFibGU8L3RleHQ+Cjwvc3ZnPgo='; this.alt='Map preview not available';"
|
onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDQwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjNGNEY2Ii8+Cjx0ZXh0IHg9IjIwMCIgeT0iMTAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNiIgZmlsbD0iIzZCNzI4MCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZG9taW5hbnQtYmFzZWxpbmU9ImNlbnRyYWwiPk1hcCBwcmV2aWV3IG5vdCBhdmFpbGFibGU8L3RleHQ+Cjwvc3ZnPgo='; this.alt='Map preview not available';"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Coordinates: {@form[:latitude].value}, {@form[:longitude].value}
|
Coordinates: {@form[:latitude].value}, {@form[:longitude].value}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -176,14 +181,16 @@
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label>
|
<label>
|
||||||
<span class="block text-sm font-medium text-gray-900 dark:text-white mb-2">
|
<span class="block text-sm font-medium text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Description")}
|
{t("Description")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<textarea
|
<textarea
|
||||||
id={@form[:description].id}
|
id={@form[:description].id}
|
||||||
name={@form[:description].name}
|
name={@form[:description].name}
|
||||||
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-gray-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-blue-500"
|
class="block w-full rounded-lg border-0 py-2 px-3 text-cool-steel-900 shadow-sm ring-1 ring-inset ring-cool-steel-300 focus:ring-2 focus:ring-inset focus:ring-cerulean-600 sm:text-sm sm:leading-6 dark:bg-cool-steel-800/50 dark:text-white dark:ring-white/10 dark:focus:ring-cerulean-500"
|
||||||
>{Phoenix.HTML.Form.normalize_value("textarea", @form[:description].value)}</textarea>
|
>{Phoenix.HTML.Form.normalize_value("textarea", @form[:description].value)}</textarea>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -192,9 +199,11 @@
|
||||||
<div class="mt-6 border-t pt-6">
|
<div class="mt-6 border-t pt-6">
|
||||||
<h3 class="text-base font-medium mb-4">
|
<h3 class="text-base font-medium mb-4">
|
||||||
{t("Agent Configuration")}
|
{t("Agent Configuration")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Set a default agent for SNMP polling at this site. This will override the organization default for all devices at this site. Leave blank to inherit from organization."
|
"Set a default agent for SNMP polling at this site. This will override the organization default for all devices at this site. Leave blank to inherit from organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -209,17 +218,17 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @live_action == :edit and @site.agent_token_id do %>
|
<%= if @live_action == :edit and @site.agent_token_id do %>
|
||||||
<p class="mt-1 text-sm text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
<p class="mt-1 text-sm text-wheat-600 dark:text-wheat-400 flex items-center gap-1">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
<.icon name="hero-exclamation-triangle" class="h-4 w-4" />
|
||||||
<strong>{t("Overriding")}</strong> organization default -
|
<strong>{t("Overriding")}</strong> organization default -
|
||||||
all devices at this site will use this agent
|
all devices at this site will use this agent
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-4 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
<div class="mt-4 p-3 bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 rounded-lg">
|
||||||
<p class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
|
<p class="text-sm font-medium text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Force Apply to Site Device")}
|
{t("Force Apply to Site Device")}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300 mb-3">
|
||||||
{t("This will assign this agent to ALL devices at this site.")}
|
{t("This will assign this agent to ALL devices at this site.")}
|
||||||
</p>
|
</p>
|
||||||
<.button
|
<.button
|
||||||
|
|
@ -237,12 +246,12 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= if Map.get(assigns, :org_agent) do %>
|
<%= if Map.get(assigns, :org_agent) do %>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400 flex items-center gap-1">
|
||||||
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
<.icon name="hero-building-office-2" class="h-4 w-4" />
|
||||||
Currently inheriting organization default: <strong>{@org_agent.name}</strong>
|
Currently inheriting organization default: <strong>{@org_agent.name}</strong>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t(
|
{t(
|
||||||
"Set a default agent for all devices at this site. Device can override this setting individually."
|
"Set a default agent for all devices at this site. Device can override this setting individually."
|
||||||
)}
|
)}
|
||||||
|
|
@ -255,9 +264,9 @@
|
||||||
<div class="mt-6 border-t pt-6">
|
<div class="mt-6 border-t pt-6">
|
||||||
<h3 class="text-base font-medium mb-4">
|
<h3 class="text-base font-medium mb-4">
|
||||||
{t("SNMP Configuration")}
|
{t("SNMP Configuration")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">(optional)</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Override organization SNMP defaults for all devices at this site. Leave blank to inherit from organization."
|
"Override organization SNMP defaults for all devices at this site. Leave blank to inherit from organization."
|
||||||
)}
|
)}
|
||||||
|
|
@ -362,11 +371,11 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @live_action == :edit and @site.snmp_community do %>
|
<%= if @live_action == :edit and @site.snmp_community do %>
|
||||||
<div class="mt-4 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
<div class="mt-4 p-3 bg-wheat-50 dark:bg-wheat-900/20 border border-wheat-200 dark:border-wheat-800 rounded-lg">
|
||||||
<p class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
|
<p class="text-sm font-medium text-wheat-900 dark:text-wheat-200 mb-2">
|
||||||
{t("Force Apply to Site Device")}
|
{t("Force Apply to Site Device")}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300 mb-3">
|
||||||
{t("This will override SNMP settings for ALL devices at this site.")}
|
{t("This will override SNMP settings for ALL devices at this site.")}
|
||||||
</p>
|
</p>
|
||||||
<.button
|
<.button
|
||||||
|
|
@ -387,15 +396,17 @@
|
||||||
<div class="mt-6 border-t pt-6">
|
<div class="mt-6 border-t pt-6">
|
||||||
<h3 class="text-base font-medium mb-4">
|
<h3 class="text-base font-medium mb-4">
|
||||||
{t("MikroTik API Configuration")}
|
{t("MikroTik API Configuration")}
|
||||||
<span class="text-gray-400 dark:text-gray-500 font-normal">(optional)</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 font-normal">
|
||||||
|
(optional)
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Override organization MikroTik API defaults for all devices at this site. Leave blank to inherit from organization. Only applies to MikroTik devices."
|
"Override organization MikroTik API defaults for all devices at this site. Leave blank to inherit from organization. Only applies to MikroTik devices."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="mb-4 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
|
<div class="mb-4 rounded-md bg-cerulean-50 dark:bg-cerulean-900/20 p-3">
|
||||||
<p class="text-xs text-blue-700 dark:text-blue-300">
|
<p class="text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
<.icon name="hero-beaker" class="h-4 w-4 inline" />
|
||||||
<strong>{t("Experimental Feature:")}</strong>
|
<strong>{t("Experimental Feature:")}</strong>
|
||||||
{t("MikroTik API integration is under active development.")}
|
{t("MikroTik API integration is under active development.")}
|
||||||
|
|
@ -437,8 +448,8 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @form[:mikrotik_use_ssl].value == false do %>
|
<%= if @form[:mikrotik_use_ssl].value == false do %>
|
||||||
<div class="mt-3 rounded-md bg-red-50 dark:bg-red-900/20 p-4">
|
<div class="mt-3 rounded-md bg-sweet-salmon-50 dark:bg-sweet-salmon-900/20 p-4">
|
||||||
<p class="text-sm text-red-700 dark:text-red-300">
|
<p class="text-sm text-sweet-salmon-700 dark:text-sweet-salmon-300">
|
||||||
<strong>🚨 Critical Security Warning:</strong>
|
<strong>🚨 Critical Security Warning:</strong>
|
||||||
{t(
|
{t(
|
||||||
"Plain API (port 8728) sends credentials unencrypted over the network. This setting is"
|
"Plain API (port 8728) sends credentials unencrypted over the network. This setting is"
|
||||||
|
|
@ -448,8 +459,8 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-3 rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
|
<div class="mt-3 rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-4">
|
||||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">
|
<p class="text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<strong>⚠️ Security Warning:</strong>
|
<strong>⚠️ Security Warning:</strong>
|
||||||
{t(
|
{t(
|
||||||
"Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible."
|
"Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible."
|
||||||
|
|
@ -468,11 +479,11 @@
|
||||||
</.form>
|
</.form>
|
||||||
|
|
||||||
<%= if @live_action == :edit do %>
|
<%= if @live_action == :edit do %>
|
||||||
<div class="mt-12 pt-8 border-t border-gray-200 dark:border-white/10">
|
<div class="mt-12 pt-8 border-t border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-lg font-semibold text-red-600 dark:text-red-500 mb-4">
|
<h3 class="text-lg font-semibold text-sweet-salmon-600 dark:text-sweet-salmon-500 mb-4">
|
||||||
{t("Danger Zone")}
|
{t("Danger Zone")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 mb-4">
|
||||||
{t(
|
{t(
|
||||||
"Once you delete a site, there is no going back. This will also delete all devices at this site."
|
"Once you delete a site, there is no going back. This will also delete all devices at this site."
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -55,32 +55,33 @@ defmodule ToweropsWeb.SiteLive.Index do
|
||||||
def format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
|
def format_qoe(score), do: :erlang.float_to_binary(score / 1, decimals: 1)
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def qoe_color(nil), do: "text-gray-400 dark:text-gray-500"
|
def qoe_color(nil), do: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
def qoe_color(score) when score < 2.0, do: "text-red-600 dark:text-red-400"
|
def qoe_color(score) when score < 2.0, do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def qoe_color(score) when score < 4.0, do: "text-yellow-600 dark:text-yellow-400"
|
def qoe_color(score) when score < 4.0, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def qoe_color(_), do: "text-green-600 dark:text-green-400"
|
def qoe_color(_), do: "text-green-600 dark:text-green-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def health_dot(nil), do: "bg-gray-400"
|
def health_dot(nil), do: "bg-cool-steel-400"
|
||||||
def health_dot(:red), do: "bg-red-500"
|
def health_dot(:red), do: "bg-sweet-salmon-500"
|
||||||
def health_dot(:yellow), do: "bg-yellow-500"
|
def health_dot(:yellow), do: "bg-wheat-500"
|
||||||
def health_dot(_), do: "bg-green-500"
|
def health_dot(_), do: "bg-green-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def urgency_classes("critical"), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
def urgency_classes("critical"),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
def urgency_classes("warning"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
def urgency_classes("warning"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
def urgency_classes("info"), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
def urgency_classes("info"), do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
|
||||||
def urgency_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
def urgency_classes(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def source_classes("preseem"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
def source_classes("preseem"), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
|
|
||||||
def source_classes("snmp"), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
def source_classes("snmp"), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
||||||
|
|
||||||
def source_classes("gaiia"), do: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400"
|
def source_classes("gaiia"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
def source_classes(_), do: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
def source_classes(_), do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-800 dark:text-cool-steel-400"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,16 @@
|
||||||
<div class="flex items-center justify-center py-16">
|
<div class="flex items-center justify-center py-16">
|
||||||
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
<div class="card shadow-md border dark:border-white/10 max-w-md w-full">
|
||||||
<div class="items-center text-center">
|
<div class="items-center text-center">
|
||||||
<div class="rounded-full bg-blue-100 dark:bg-blue-900/40 p-4 mb-2">
|
<div class="rounded-full bg-cerulean-100 dark:bg-cerulean-900/40 p-4 mb-2">
|
||||||
<.icon name="hero-map-pin-solid" class="h-12 w-12 text-blue-500 dark:text-blue-400" />
|
<.icon
|
||||||
|
name="hero-map-pin-solid"
|
||||||
|
class="h-12 w-12 text-cerulean-500 dark:text-cerulean-400"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{t("No Sites Yet")}</h3>
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
{t("No Sites Yet")}
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Create your first site to organize devices by tower location.")}
|
{t("Create your first site to organize devices by tower location.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4 flex gap-2">
|
<div class="mt-4 flex gap-2">
|
||||||
|
|
@ -41,45 +46,45 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%!-- Sites table --%>
|
<%!-- Sites table --%>
|
||||||
<div class="mt-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-white/10">
|
<div class="mt-6 overflow-x-auto rounded-lg border border-cool-steel-200 dark:border-white/10">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800/80">
|
<thead class="bg-cool-steel-50 dark:bg-cool-steel-800/80">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Site")}
|
{t("Site")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Devices")}
|
{t("Devices")}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Down")}
|
{t("Down")}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400"
|
class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
title={t("Quality of Experience — Preseem network quality score (0–100)")}
|
title={t("Quality of Experience — Preseem network quality score (0–100)")}
|
||||||
>
|
>
|
||||||
{t("QoE")}
|
{t("QoE")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="hidden sm:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Subscribers")}
|
{t("Subscribers")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden md:table-cell px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="hidden md:table-cell px-4 py-2 text-left text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Location")}
|
{t("Location")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Coordinates")}
|
{t("Coordinates")}
|
||||||
</th>
|
</th>
|
||||||
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<th class="hidden lg:table-cell px-4 py-2 text-right text-xs font-medium uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Last Check")}
|
{t("Last Check")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 bg-white dark:divide-white/5 dark:bg-gray-800/50">
|
<tbody class="divide-y divide-cool-steel-100 bg-white dark:divide-white/5 dark:bg-cool-steel-800/50">
|
||||||
<tr :for={site <- @sites} class="hover:bg-gray-50 dark:hover:bg-white/5">
|
<tr :for={site <- @sites} class="hover:bg-cool-steel-50 dark:hover:bg-white/5">
|
||||||
<td class="px-4 py-2.5">
|
<td class="px-4 py-2.5">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{site.id}"}
|
navigate={~p"/sites/#{site.id}"}
|
||||||
class="flex items-center gap-2 text-sm font-medium text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400"
|
class="flex items-center gap-2 text-sm font-medium text-cool-steel-900 hover:text-cerulean-600 dark:text-white dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
<span class={[
|
<span class={[
|
||||||
"h-3 w-3 rounded-full flex-shrink-0",
|
"h-3 w-3 rounded-full flex-shrink-0",
|
||||||
|
|
@ -88,20 +93,23 @@
|
||||||
</span>
|
</span>
|
||||||
{site.name}
|
{site.name}
|
||||||
</.link>
|
</.link>
|
||||||
<p :if={site.parent_site} class="text-xs text-gray-500 dark:text-gray-400 ml-4">
|
<p
|
||||||
|
:if={site.parent_site}
|
||||||
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400 ml-4"
|
||||||
|
>
|
||||||
↳ {site.parent_site.name}
|
↳ {site.parent_site.name}
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2.5 text-right text-sm text-gray-600 dark:text-gray-400">
|
<td class="px-4 py-2.5 text-right text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{if @summary_map[site.id], do: @summary_map[site.id].device_count, else: 0}
|
{if @summary_map[site.id], do: @summary_map[site.id].device_count, else: 0}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2.5 text-right text-sm">
|
<td class="px-4 py-2.5 text-right text-sm">
|
||||||
<%= if @summary_map[site.id] && @summary_map[site.id].devices_down > 0 do %>
|
<%= if @summary_map[site.id] && @summary_map[site.id].devices_down > 0 do %>
|
||||||
<span class="text-red-600 dark:text-red-400">
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
{@summary_map[site.id].devices_down}
|
{@summary_map[site.id].devices_down}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-500">0</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500">0</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm">
|
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm">
|
||||||
|
|
@ -110,30 +118,30 @@
|
||||||
{format_qoe(@summary_map[site.id].avg_qoe)}
|
{format_qoe(@summary_map[site.id].avg_qoe)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-500">—</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm text-gray-600 dark:text-gray-400">
|
<td class="hidden sm:table-cell px-4 py-2.5 text-right text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<%= if @summary_map[site.id] && @summary_map[site.id].subscribers do %>
|
<%= if @summary_map[site.id] && @summary_map[site.id].subscribers do %>
|
||||||
{@summary_map[site.id].subscribers}
|
{@summary_map[site.id].subscribers}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-500">—</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden md:table-cell px-4 py-2.5 text-sm text-gray-500 dark:text-gray-400 max-w-xs truncate">
|
<td class="hidden md:table-cell px-4 py-2.5 text-sm text-cool-steel-500 dark:text-cool-steel-400 max-w-xs truncate">
|
||||||
{site.location || "—"}
|
{site.location || "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden lg:table-cell px-4 py-2.5 text-right">
|
<td class="hidden lg:table-cell px-4 py-2.5 text-right">
|
||||||
<%= if site.latitude && site.longitude do %>
|
<%= if site.latitude && site.longitude do %>
|
||||||
<span class="text-xs font-mono text-gray-400 dark:text-gray-500">
|
<span class="text-xs font-mono text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{Float.round(site.latitude, 4)}, {Float.round(site.longitude, 4)}
|
{Float.round(site.latitude, 4)}, {Float.round(site.longitude, 4)}
|
||||||
</span>
|
</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 dark:text-gray-500 text-sm">—</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500 text-sm">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden lg:table-cell px-4 py-2.5 text-right text-xs text-gray-500 dark:text-gray-400">
|
<td class="hidden lg:table-cell px-4 py-2.5 text-right text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<span class="text-gray-400 dark:text-gray-500">—</span>
|
<span class="text-cool-steel-400 dark:text-cool-steel-500">—</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -144,21 +152,21 @@
|
||||||
<%= if @insights != [] do %>
|
<%= if @insights != [] do %>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Insights")}
|
{t("Insights")}
|
||||||
</h2>
|
</h2>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/insights"}
|
navigate={~p"/insights"}
|
||||||
class="text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
class="text-xs font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400"
|
||||||
>
|
>
|
||||||
View all →
|
View all →
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10 divide-y divide-gray-100 dark:divide-white/5">
|
<div class="overflow-hidden rounded-lg border border-cool-steel-200 dark:border-white/10 divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<div
|
<div
|
||||||
:for={insight <- @insights}
|
:for={insight <- @insights}
|
||||||
id={"insight-#{insight.id}"}
|
id={"insight-#{insight.id}"}
|
||||||
class="flex items-start justify-between gap-3 px-4 py-3 bg-white dark:bg-gray-800/50 hover:bg-gray-50 dark:hover:bg-white/5"
|
class="flex items-start justify-between gap-3 px-4 py-3 bg-white dark:bg-cool-steel-800/50 hover:bg-cool-steel-50 dark:hover:bg-white/5"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
|
|
@ -175,12 +183,12 @@
|
||||||
{insight.source}
|
{insight.source}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
<p class="mt-1 text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{insight.title}
|
{insight.title}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
:if={insight.description}
|
:if={insight.description}
|
||||||
class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-1"
|
class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400 line-clamp-1"
|
||||||
>
|
>
|
||||||
{insight.description}
|
{insight.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -189,7 +197,7 @@
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="dismiss_insight"
|
phx-click="dismiss_insight"
|
||||||
phx-value-id={insight.id}
|
phx-value-id={insight.id}
|
||||||
class="flex-shrink-0 rounded p-1 text-gray-300 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-600 dark:hover:bg-white/10 dark:hover:text-gray-400"
|
class="flex-shrink-0 rounded p-1 text-cool-steel-300 hover:bg-cool-steel-100 hover:text-cool-steel-500 dark:text-cool-steel-600 dark:hover:bg-white/10 dark:hover:text-cool-steel-400"
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
>
|
>
|
||||||
<.icon name="hero-x-mark" class="h-3.5 w-3.5" />
|
<.icon name="hero-x-mark" class="h-3.5 w-3.5" />
|
||||||
|
|
|
||||||
|
|
@ -168,30 +168,30 @@ defmodule ToweropsWeb.SiteLive.Show do
|
||||||
defp format_number(number), do: to_string(number)
|
defp format_number(number), do: to_string(number)
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def qoe_color(nil), do: "text-gray-400 dark:text-gray-500"
|
def qoe_color(nil), do: "text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
def qoe_color(score) when score >= 8.0, do: "text-green-600 dark:text-green-400"
|
def qoe_color(score) when score >= 8.0, do: "text-green-600 dark:text-green-400"
|
||||||
def qoe_color(score) when score >= 6.0, do: "text-yellow-600 dark:text-yellow-400"
|
def qoe_color(score) when score >= 6.0, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def qoe_color(_), do: "text-red-600 dark:text-red-400"
|
def qoe_color(_), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def qoe_bg(nil), do: "bg-gray-50 border-gray-200 dark:bg-gray-800/50 dark:border-white/10"
|
def qoe_bg(nil), do: "bg-cool-steel-50 border-cool-steel-200 dark:bg-cool-steel-800/50 dark:border-white/10"
|
||||||
def qoe_bg(score) when score >= 8.0, do: "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800/30"
|
def qoe_bg(score) when score >= 8.0, do: "bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800/30"
|
||||||
|
|
||||||
def qoe_bg(score) when score >= 6.0,
|
def qoe_bg(score) when score >= 6.0, do: "bg-wheat-50 border-wheat-200 dark:bg-wheat-900/20 dark:border-wheat-800/30"
|
||||||
do: "bg-yellow-50 border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800/30"
|
|
||||||
|
|
||||||
def qoe_bg(_), do: "bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800/30"
|
def qoe_bg(_),
|
||||||
|
do: "bg-sweet-salmon-50 border-sweet-salmon-200 dark:bg-sweet-salmon-900/20 dark:border-sweet-salmon-800/30"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def capacity_bar_color(nil), do: "bg-gray-300"
|
def capacity_bar_color(nil), do: "bg-cool-steel-300"
|
||||||
def capacity_bar_color(score) when score >= 70, do: "bg-green-500"
|
def capacity_bar_color(score) when score >= 70, do: "bg-green-500"
|
||||||
def capacity_bar_color(score) when score >= 40, do: "bg-yellow-500"
|
def capacity_bar_color(score) when score >= 40, do: "bg-wheat-500"
|
||||||
def capacity_bar_color(_), do: "bg-red-500"
|
def capacity_bar_color(_), do: "bg-sweet-salmon-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def status_dot_class(:up), do: "bg-green-500"
|
def status_dot_class(:up), do: "bg-green-500"
|
||||||
def status_dot_class(:down), do: "bg-red-500"
|
def status_dot_class(:down), do: "bg-sweet-salmon-500"
|
||||||
def status_dot_class(_), do: "bg-gray-400"
|
def status_dot_class(_), do: "bg-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def time_ago(nil), do: "—"
|
def time_ago(nil), do: "—"
|
||||||
|
|
@ -218,17 +218,18 @@ defmodule ToweropsWeb.SiteLive.Show do
|
||||||
def change_size_label(_), do: "Large"
|
def change_size_label(_), do: "Large"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def change_size_color(size) when size < 100, do: "bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
|
def change_size_color(size) when size < 100,
|
||||||
|
do: "bg-cool-steel-100 text-cool-steel-600 dark:bg-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def change_size_color(size) when size < 500,
|
def change_size_color(size) when size < 500, do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
|
||||||
|
|
||||||
def change_size_color(_), do: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
def change_size_color(_),
|
||||||
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def insight_urgency_class("critical"), do: "text-red-600 dark:text-red-400"
|
def insight_urgency_class("critical"), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def insight_urgency_class("warning"), do: "text-yellow-600 dark:text-yellow-400"
|
def insight_urgency_class("warning"), do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def insight_urgency_class(_), do: "text-blue-600 dark:text-blue-400"
|
def insight_urgency_class(_), do: "text-cerulean-600 dark:text-cerulean-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def insight_urgency_icon("critical"), do: "hero-exclamation-triangle"
|
def insight_urgency_icon("critical"), do: "hero-exclamation-triangle"
|
||||||
|
|
@ -249,13 +250,13 @@ defmodule ToweropsWeb.SiteLive.Show do
|
||||||
def format_capacity(_), do: "-"
|
def format_capacity(_), do: "-"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def utilization_bar_color(pct) when pct >= 90, do: "bg-red-500"
|
def utilization_bar_color(pct) when pct >= 90, do: "bg-sweet-salmon-500"
|
||||||
def utilization_bar_color(pct) when pct >= 70, do: "bg-yellow-500"
|
def utilization_bar_color(pct) when pct >= 70, do: "bg-wheat-500"
|
||||||
def utilization_bar_color(_pct), do: "bg-green-500"
|
def utilization_bar_color(_pct), do: "bg-green-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def utilization_text_color(pct) when pct >= 90, do: "text-red-600 dark:text-red-400"
|
def utilization_text_color(pct) when pct >= 90, do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def utilization_text_color(pct) when pct >= 70, do: "text-yellow-600 dark:text-yellow-400"
|
def utilization_text_color(pct) when pct >= 70, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
def utilization_text_color(_pct), do: "text-green-600 dark:text-green-400"
|
||||||
|
|
||||||
# Enqueue discovery job - safe to call in test environment
|
# Enqueue discovery job - safe to call in test environment
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
]} />
|
]} />
|
||||||
<!-- Prominent site header -->
|
<!-- Prominent site header -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{@site.name}</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">{@site.name}</h1>
|
||||||
<p :if={@site.location} class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p :if={@site.location} class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-map-pin" class="h-4 w-4 inline" /> {@site.location}
|
<.icon name="hero-map-pin" class="h-4 w-4 inline" /> {@site.location}
|
||||||
<%= if @site.latitude && @site.longitude do %>
|
<%= if @site.latitude && @site.longitude do %>
|
||||||
<span class="ml-2 font-mono text-xs text-gray-400">
|
<span class="ml-2 font-mono text-xs text-cool-steel-400">
|
||||||
({Float.round(@site.latitude, 4)}, {Float.round(@site.longitude, 4)})
|
({Float.round(@site.latitude, 4)}, {Float.round(@site.longitude, 4)})
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
data-lng={@site.longitude}
|
data-lng={@site.longitude}
|
||||||
data-zoom="14"
|
data-zoom="14"
|
||||||
data-marker-title={@site.name}
|
data-marker-title={@site.name}
|
||||||
class="h-48 w-full rounded-lg border border-gray-200 dark:border-white/10 mb-6 z-0"
|
class="h-48 w-full rounded-lg border border-cool-steel-200 dark:border-white/10 mb-6 z-0"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -77,35 +77,35 @@
|
||||||
<%!-- Summary Stat Cards --%>
|
<%!-- Summary Stat Cards --%>
|
||||||
<div class="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
<div class="mt-6 grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||||
<%!-- Subscribers --%>
|
<%!-- Subscribers --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50 p-4 transition-shadow hover:shadow-sm">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50 p-4 transition-shadow hover:shadow-sm">
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-gray-500 dark:text-gray-400">
|
<div class="flex items-center gap-2 text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-users" class="h-4 w-4" /> {t("Subscribers")}
|
<.icon name="hero-users" class="h-4 w-4" /> {t("Subscribers")}
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white font-mono">
|
<p class="mt-1 text-2xl font-bold text-cool-steel-900 dark:text-white font-mono">
|
||||||
{format_number(@site_summary.subscribers || 0)}
|
{format_number(@site_summary.subscribers || 0)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<%!-- Financial data: role-gated --%>
|
<%!-- Financial data: role-gated --%>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
<%!-- MRR --%>
|
<%!-- MRR --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50 p-4 transition-shadow hover:shadow-sm">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50 p-4 transition-shadow hover:shadow-sm">
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-gray-500 dark:text-gray-400">
|
<div class="flex items-center gap-2 text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-currency-dollar" class="h-4 w-4" /> {t("Monthly Revenue")}
|
<.icon name="hero-currency-dollar" class="h-4 w-4" /> {t("Monthly Revenue")}
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
|
<p class="mt-1 text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{format_mrr(@site_summary.mrr)}
|
{format_mrr(@site_summary.mrr)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%!-- Device Count --%>
|
<%!-- Device Count --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50 p-4 transition-shadow hover:shadow-sm">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50 p-4 transition-shadow hover:shadow-sm">
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-gray-500 dark:text-gray-400">
|
<div class="flex items-center gap-2 text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-server" class="h-4 w-4" /> {t("Devices")}
|
<.icon name="hero-server" class="h-4 w-4" /> {t("Devices")}
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white font-mono">
|
<p class="mt-1 text-2xl font-bold text-cool-steel-900 dark:text-white font-mono">
|
||||||
{format_number(@site_summary.device_count)}
|
{format_number(@site_summary.device_count)}
|
||||||
<%= if @site_summary.devices_down > 0 do %>
|
<%= if @site_summary.devices_down > 0 do %>
|
||||||
<span class="text-sm font-medium text-red-600 dark:text-red-400">
|
<span class="text-sm font-medium text-sweet-salmon-600 dark:text-sweet-salmon-400">
|
||||||
({@site_summary.devices_down} {t("down")})
|
({@site_summary.devices_down} {t("down")})
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -113,20 +113,20 @@
|
||||||
</div>
|
</div>
|
||||||
<%!-- Active Alerts --%>
|
<%!-- Active Alerts --%>
|
||||||
<div class={[
|
<div class={[
|
||||||
"rounded-lg border bg-white dark:bg-gray-800/50 p-4 transition-shadow hover:shadow-sm",
|
"rounded-lg border bg-white dark:bg-cool-steel-800/50 p-4 transition-shadow hover:shadow-sm",
|
||||||
if(length(@active_alerts) > 0,
|
if(length(@active_alerts) > 0,
|
||||||
do: "border-red-200 dark:border-red-900/50",
|
do: "border-sweet-salmon-200 dark:border-sweet-salmon-900/50",
|
||||||
else: "border-gray-200 dark:border-white/10"
|
else: "border-cool-steel-200 dark:border-white/10"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
<div class="flex items-center gap-2 text-sm font-medium text-gray-500 dark:text-gray-400">
|
<div class="flex items-center gap-2 text-sm font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-bell-alert" class="h-4 w-4" /> {t("Active Alerts")}
|
<.icon name="hero-bell-alert" class="h-4 w-4" /> {t("Active Alerts")}
|
||||||
</div>
|
</div>
|
||||||
<p class={[
|
<p class={[
|
||||||
"mt-1 text-2xl font-bold font-mono",
|
"mt-1 text-2xl font-bold font-mono",
|
||||||
if(length(@active_alerts) > 0,
|
if(length(@active_alerts) > 0,
|
||||||
do: "text-red-600 dark:text-red-400",
|
do: "text-sweet-salmon-600 dark:text-sweet-salmon-400",
|
||||||
else: "text-gray-900 dark:text-white"
|
else: "text-cool-steel-900 dark:text-white"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
{length(@active_alerts)}
|
{length(@active_alerts)}
|
||||||
|
|
@ -136,17 +136,17 @@
|
||||||
|
|
||||||
<%!-- Backhaul Capacity Card --%>
|
<%!-- Backhaul Capacity Card --%>
|
||||||
<%= if @capacity_summary.interfaces != [] do %>
|
<%= if @capacity_summary.interfaces != [] do %>
|
||||||
<div class="mt-6 rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50 p-4">
|
<div class="mt-6 rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50 p-4">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
<.icon name="hero-signal" class="h-4 w-4 inline mr-1" />{t("Backhaul Capacity")}
|
<.icon name="hero-signal" class="h-4 w-4 inline mr-1" />{t("Backhaul Capacity")}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{format_capacity(@capacity_summary.total_capacity_bps)} {t("total")}
|
{format_capacity(@capacity_summary.total_capacity_bps)} {t("total")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3 mb-3">
|
<div class="flex items-center gap-3 mb-3">
|
||||||
<div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-3 overflow-hidden">
|
<div class="flex-1 bg-cool-steel-200 dark:bg-cool-steel-700 rounded-full h-3 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
"h-full rounded-full transition-all",
|
"h-full rounded-full transition-all",
|
||||||
|
|
@ -165,14 +165,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-2 text-xs">
|
<div class="grid grid-cols-3 gap-2 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-gray-500 dark:text-gray-400">{t("Throughput")}</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">{t("Throughput")}</span>
|
||||||
<p class="font-mono font-medium text-gray-900 dark:text-white">
|
<p class="font-mono font-medium text-cool-steel-900 dark:text-white">
|
||||||
{format_capacity(@capacity_summary.total_throughput_bps)}
|
{format_capacity(@capacity_summary.total_throughput_bps)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-gray-500 dark:text-gray-400">{t("Headroom")}</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">{t("Headroom")}</span>
|
||||||
<p class="font-mono font-medium text-gray-900 dark:text-white">
|
<p class="font-mono font-medium text-cool-steel-900 dark:text-white">
|
||||||
{format_capacity(
|
{format_capacity(
|
||||||
max(
|
max(
|
||||||
0,
|
0,
|
||||||
|
|
@ -182,8 +182,8 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-gray-500 dark:text-gray-400">{t("Interfaces")}</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">{t("Interfaces")}</span>
|
||||||
<p class="font-mono font-medium text-gray-900 dark:text-white">
|
<p class="font-mono font-medium text-cool-steel-900 dark:text-white">
|
||||||
{length(@capacity_summary.interfaces)}
|
{length(@capacity_summary.interfaces)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
<div class={["mt-6 rounded-lg border p-4", qoe_bg(@qoe_summary[:score])]}>
|
<div class={["mt-6 rounded-lg border p-4", qoe_bg(@qoe_summary[:score])]}>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<h3 class="text-sm font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{t("Preseem QoE Score")}
|
{t("Preseem QoE Score")}
|
||||||
</h3>
|
</h3>
|
||||||
<p class={["mt-1 text-3xl font-bold", qoe_color(@qoe_summary[:score])]}>
|
<p class={["mt-1 text-3xl font-bold", qoe_color(@qoe_summary[:score])]}>
|
||||||
|
|
@ -205,15 +205,17 @@
|
||||||
</div>
|
</div>
|
||||||
<%= if @qoe_summary[:capacity_score] do %>
|
<%= if @qoe_summary[:capacity_score] do %>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Capacity</p>
|
<p class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Capacity
|
||||||
|
</p>
|
||||||
<div class="mt-1 w-32">
|
<div class="mt-1 w-32">
|
||||||
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-gray-700">
|
<div class="h-2 w-full rounded-full bg-cool-steel-200 dark:bg-cool-steel-700">
|
||||||
<div
|
<div
|
||||||
class={["h-2 rounded-full", capacity_bar_color(@qoe_summary[:capacity_score])]}
|
class={["h-2 rounded-full", capacity_bar_color(@qoe_summary[:capacity_score])]}
|
||||||
style={"width: #{min(@qoe_summary[:capacity_score], 100)}%"}
|
style={"width: #{min(@qoe_summary[:capacity_score], 100)}%"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{@qoe_summary[:capacity_score]}%
|
{@qoe_summary[:capacity_score]}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -225,13 +227,13 @@
|
||||||
|
|
||||||
<%!-- Subscriber Impact Breakdown by Device --%>
|
<%!-- Subscriber Impact Breakdown by Device --%>
|
||||||
<%= if @site_impact && @site_impact.subscriber_count > 0 do %>
|
<%= if @site_impact && @site_impact.subscriber_count > 0 do %>
|
||||||
<div class="mt-6 rounded-lg border border-indigo-200 bg-indigo-50 dark:border-indigo-800/50 dark:bg-indigo-950/30">
|
<div class="mt-6 rounded-lg border border-cerulean-200 bg-cerulean-50 dark:border-cerulean-800/50 dark:bg-cerulean-950/30">
|
||||||
<div class="px-4 py-3 border-b border-indigo-200 dark:border-indigo-800/50">
|
<div class="px-4 py-3 border-b border-cerulean-200 dark:border-cerulean-800/50">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="text-sm font-semibold text-indigo-900 dark:text-indigo-200 flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cerulean-900 dark:text-cerulean-200 flex items-center gap-1.5">
|
||||||
<.icon name="hero-users" class="h-4 w-4" /> {t("Subscriber Impact")}
|
<.icon name="hero-users" class="h-4 w-4" /> {t("Subscriber Impact")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-4 text-sm font-medium text-indigo-900 dark:text-indigo-200">
|
<div class="flex items-center gap-4 text-sm font-medium text-cerulean-900 dark:text-cerulean-200">
|
||||||
<span>{format_number(@site_impact.subscriber_count)} subscribers</span>
|
<span>{format_number(@site_impact.subscriber_count)} subscribers</span>
|
||||||
<%= if @can_view_financials && @site_impact.mrr do %>
|
<%= if @can_view_financials && @site_impact.mrr do %>
|
||||||
<span>{format_mrr(@site_impact.mrr)}/mo</span>
|
<span>{format_mrr(@site_impact.mrr)}/mo</span>
|
||||||
|
|
@ -241,9 +243,9 @@
|
||||||
</div>
|
</div>
|
||||||
<%= if @site_impact.devices != [] do %>
|
<%= if @site_impact.devices != [] do %>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-indigo-200 dark:divide-indigo-800/50">
|
<table class="min-w-full divide-y divide-cerulean-200 dark:divide-cerulean-800/50">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs text-indigo-700 dark:text-indigo-300">
|
<tr class="text-xs text-cerulean-700 dark:text-cerulean-300">
|
||||||
<th class="px-4 py-2 text-left font-medium">{t("Device")}</th>
|
<th class="px-4 py-2 text-left font-medium">{t("Device")}</th>
|
||||||
<th class="px-4 py-2 text-right font-medium">{t("Subscribers")}</th>
|
<th class="px-4 py-2 text-right font-medium">{t("Subscribers")}</th>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
|
|
@ -251,17 +253,17 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-indigo-100 dark:divide-indigo-900/30">
|
<tbody class="divide-y divide-cerulean-100 dark:divide-cerulean-900/30">
|
||||||
<%= for dev <- @site_impact.devices do %>
|
<%= for dev <- @site_impact.devices do %>
|
||||||
<tr class="text-sm">
|
<tr class="text-sm">
|
||||||
<td class="px-4 py-2 text-indigo-900 dark:text-indigo-200 font-medium">
|
<td class="px-4 py-2 text-cerulean-900 dark:text-cerulean-200 font-medium">
|
||||||
{dev.name || dev.device_id}
|
{dev.name || dev.device_id}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-2 text-right text-indigo-800 dark:text-indigo-300 font-mono">
|
<td class="px-4 py-2 text-right text-cerulean-800 dark:text-cerulean-300 font-mono">
|
||||||
{dev.count}
|
{dev.count}
|
||||||
</td>
|
</td>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
<td class="px-4 py-2 text-right text-indigo-800 dark:text-indigo-300 font-mono">
|
<td class="px-4 py-2 text-right text-cerulean-800 dark:text-cerulean-300 font-mono">
|
||||||
{format_mrr(dev.mrr)}
|
{format_mrr(dev.mrr)}
|
||||||
</td>
|
</td>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -278,15 +280,17 @@
|
||||||
<%!-- Left Column --%>
|
<%!-- Left Column --%>
|
||||||
<div class="space-y-6 min-w-0">
|
<div class="space-y-6 min-w-0">
|
||||||
<%!-- Site Details --%>
|
<%!-- Site Details --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
{t("Site Details")}
|
{t("Site Details")}
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="mt-2 space-y-3">
|
<dl class="mt-2 space-y-3">
|
||||||
<%= if @site.parent_site do %>
|
<%= if @site.parent_site do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Parent Site</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
|
Parent Site
|
||||||
|
</dt>
|
||||||
<dd class="mt-0.5">
|
<dd class="mt-0.5">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{@site.parent_site.id}"}
|
navigate={~p"/sites/#{@site.parent_site.id}"}
|
||||||
|
|
@ -299,20 +303,28 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @site.location do %>
|
<%= if @site.location do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Location</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-0.5 text-sm text-gray-900 dark:text-white">{@site.location}</dd>
|
Location
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-0.5 text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{@site.location}
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @site.description do %>
|
<%= if @site.description do %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Description</dt>
|
<dt class="text-sm font-medium text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<dd class="mt-0.5 text-sm text-gray-900 dark:text-white">{@site.description}</dd>
|
Description
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-0.5 text-sm text-cool-steel-900 dark:text-white">
|
||||||
|
{@site.description}
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</dl>
|
</dl>
|
||||||
<%= if @site.child_sites != [] do %>
|
<%= if @site.child_sites != [] do %>
|
||||||
<div class="mt-4 border-t border-cool-steel-300 dark:border-cool-steel-600 pt-3">
|
<div class="mt-4 border-t border-cool-steel-300 dark:border-cool-steel-600 pt-3">
|
||||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
<h4 class="text-sm font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Child Sites")}
|
{t("Child Sites")}
|
||||||
</h4>
|
</h4>
|
||||||
<ul class="mt-2 space-y-1">
|
<ul class="mt-2 space-y-1">
|
||||||
|
|
@ -331,18 +343,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Active Alerts Panel --%>
|
<%!-- Active Alerts Panel --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
<.icon name="hero-bell-alert" class="h-5 w-5" /> Active Alerts
|
<.icon name="hero-bell-alert" class="h-5 w-5" /> Active Alerts
|
||||||
<%= if @active_alerts != [] do %>
|
<%= if @active_alerts != [] do %>
|
||||||
<span class="ml-2 inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900/30 dark:text-red-400">
|
<span class="ml-2 inline-flex items-center rounded-full bg-sweet-salmon-100 px-2 py-0.5 text-xs font-medium text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400">
|
||||||
{length(@active_alerts)}
|
{length(@active_alerts)}
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @active_alerts == [] do %>
|
<%= if @active_alerts == [] do %>
|
||||||
<div class="mt-2 text-center py-6 text-gray-500 dark:text-gray-400">
|
<div class="mt-2 text-center py-6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<.icon name="hero-check-circle" class="mx-auto h-8 w-8 text-green-500" />
|
<.icon name="hero-check-circle" class="mx-auto h-8 w-8 text-green-500" />
|
||||||
<p class="mt-2 text-sm">No active alerts</p>
|
<p class="mt-2 text-sm">No active alerts</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -350,7 +362,7 @@
|
||||||
<div class="mt-2 space-y-2">
|
<div class="mt-2 space-y-2">
|
||||||
<div
|
<div
|
||||||
:for={alert <- @active_alerts}
|
:for={alert <- @active_alerts}
|
||||||
class="flex items-start gap-3 rounded-lg border border-gray-200 dark:border-white/10 p-3"
|
class="flex items-start gap-3 rounded-lg border border-cool-steel-200 dark:border-white/10 p-3"
|
||||||
>
|
>
|
||||||
<.icon
|
<.icon
|
||||||
name={insight_urgency_icon(alert_severity(alert.alert_type))}
|
name={insight_urgency_icon(alert_severity(alert.alert_type))}
|
||||||
|
|
@ -360,8 +372,10 @@
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{alert.message}</p>
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
{alert.message}
|
||||||
|
</p>
|
||||||
|
<p class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{time_ago(alert.triggered_at)}
|
{time_ago(alert.triggered_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -372,26 +386,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Recent Config Changes Panel — hidden, feature temporarily disabled --%>
|
<%!-- Recent Config Changes Panel — hidden, feature temporarily disabled --%>
|
||||||
<div class="hidden rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="hidden rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
<.icon name="hero-document-text" class="h-5 w-5" /> Recent Config Changes
|
<.icon name="hero-document-text" class="h-5 w-5" /> Recent Config Changes
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @config_changes == [] do %>
|
<%= if @config_changes == [] do %>
|
||||||
<div class="mt-2 text-center py-6 text-gray-500 dark:text-gray-400">
|
<div class="mt-2 text-center py-6 text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<p class="text-sm">No config changes in the last 30 days</p>
|
<p class="text-sm">No config changes in the last 30 days</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-2 space-y-2">
|
<div class="mt-2 space-y-2">
|
||||||
<div
|
<div
|
||||||
:for={change <- @config_changes}
|
:for={change <- @config_changes}
|
||||||
class="flex items-center justify-between rounded-lg border border-gray-200 dark:border-white/10 p-3"
|
class="flex items-center justify-between rounded-lg border border-cool-steel-200 dark:border-white/10 p-3"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
{change.device_name || "Unknown device"}
|
{change.device_name || "Unknown device"}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{time_ago(change.detected_at)}
|
{time_ago(change.detected_at)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -411,10 +425,10 @@
|
||||||
<%!-- Right Column --%>
|
<%!-- Right Column --%>
|
||||||
<div class="space-y-6 min-w-0">
|
<div class="space-y-6 min-w-0">
|
||||||
<%!-- Device Health Grid --%>
|
<%!-- Device Health Grid --%>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
<.icon name="hero-server" class="h-5 w-5" /> Device Health
|
<.icon name="hero-server" class="h-5 w-5" /> Device Health
|
||||||
</h3>
|
</h3>
|
||||||
<.button
|
<.button
|
||||||
|
|
@ -426,15 +440,15 @@
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
<%= if @device == [] do %>
|
<%= if @device == [] do %>
|
||||||
<div class="mt-2 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 p-8 text-center">
|
<div class="mt-2 rounded-lg border-2 border-dashed border-cool-steel-300 dark:border-cool-steel-600 p-8 text-center">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-server"
|
name="hero-server"
|
||||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500"
|
class="mx-auto h-12 w-12 text-cool-steel-400 dark:text-cool-steel-500"
|
||||||
/>
|
/>
|
||||||
<h4 class="mt-4 text-base font-semibold text-gray-900 dark:text-white">
|
<h4 class="mt-4 text-base font-semibold text-cool-steel-900 dark:text-white">
|
||||||
{t("Add your first device")}
|
{t("Add your first device")}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-2 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{t("Start monitoring by adding network devices to this site.")}
|
{t("Start monitoring by adding network devices to this site.")}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|
@ -445,9 +459,9 @@
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-2 overflow-x-auto">
|
<div class="mt-2 overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
<table class="min-w-full divide-y divide-cool-steel-200 dark:divide-white/10">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs text-gray-500 dark:text-gray-400">
|
<tr class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<th class="px-3 py-2 text-left font-medium">{t("Device")}</th>
|
<th class="px-3 py-2 text-left font-medium">{t("Device")}</th>
|
||||||
<th class="hidden sm:table-cell px-3 py-2 text-left font-medium">
|
<th class="hidden sm:table-cell px-3 py-2 text-left font-medium">
|
||||||
{t("IP Address")}
|
{t("IP Address")}
|
||||||
|
|
@ -463,10 +477,10 @@
|
||||||
<th class="px-3 py-2 text-right font-medium">{t("Latency")}</th>
|
<th class="px-3 py-2 text-right font-medium">{t("Latency")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
|
<tbody class="divide-y divide-cool-steel-100 dark:divide-white/5">
|
||||||
<tr
|
<tr
|
||||||
:for={eq <- @device}
|
:for={eq <- @device}
|
||||||
class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer"
|
class="hover:bg-cool-steel-50 dark:hover:bg-cool-steel-800 transition-colors cursor-pointer"
|
||||||
phx-click={JS.navigate(~p"/devices/#{eq.id}")}
|
phx-click={JS.navigate(~p"/devices/#{eq.id}")}
|
||||||
>
|
>
|
||||||
<td class="px-3 py-2">
|
<td class="px-3 py-2">
|
||||||
|
|
@ -475,15 +489,15 @@
|
||||||
"h-2 w-2 shrink-0 rounded-full",
|
"h-2 w-2 shrink-0 rounded-full",
|
||||||
status_dot_class(eq.status)
|
status_dot_class(eq.status)
|
||||||
]} />
|
]} />
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
{eq.name}
|
{eq.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-2 font-mono text-xs text-gray-500 dark:text-gray-400">
|
<td class="hidden sm:table-cell px-3 py-2 font-mono text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{eq.ip_address}
|
{eq.ip_address}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden sm:table-cell px-3 py-2 text-right font-mono text-xs text-gray-700 dark:text-gray-300">
|
<td class="hidden sm:table-cell px-3 py-2 text-right font-mono text-xs text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<%= if di = @device_impact[eq.id] do %>
|
<%= if di = @device_impact[eq.id] do %>
|
||||||
{if di.count > 0, do: di.count, else: "—"}
|
{if di.count > 0, do: di.count, else: "—"}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -491,7 +505,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
<td class="hidden md:table-cell px-3 py-2 text-right font-mono text-xs text-gray-700 dark:text-gray-300">
|
<td class="hidden md:table-cell px-3 py-2 text-right font-mono text-xs text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
<%= if di = @device_impact[eq.id] do %>
|
<%= if di = @device_impact[eq.id] do %>
|
||||||
{if Decimal.gt?(di.mrr, 0), do: format_mrr(di.mrr), else: "—"}
|
{if Decimal.gt?(di.mrr, 0), do: format_mrr(di.mrr), else: "—"}
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
@ -502,10 +516,10 @@
|
||||||
<td class={[
|
<td class={[
|
||||||
"px-3 py-2 text-right font-mono text-xs",
|
"px-3 py-2 text-right font-mono text-xs",
|
||||||
case @response_times[eq.id] do
|
case @response_times[eq.id] do
|
||||||
nil -> "text-gray-400"
|
nil -> "text-cool-steel-400"
|
||||||
ms when ms < 50 -> "text-green-600 dark:text-green-400"
|
ms when ms < 50 -> "text-green-600 dark:text-green-400"
|
||||||
ms when ms < 200 -> "text-yellow-600 dark:text-yellow-400"
|
ms when ms < 200 -> "text-wheat-600 dark:text-wheat-400"
|
||||||
_ -> "text-red-600 dark:text-red-400"
|
_ -> "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
end
|
end
|
||||||
]}>
|
]}>
|
||||||
{format_response_time(@response_times[eq.id])}
|
{format_response_time(@response_times[eq.id])}
|
||||||
|
|
@ -520,24 +534,26 @@
|
||||||
|
|
||||||
<%!-- Insights Panel --%>
|
<%!-- Insights Panel --%>
|
||||||
<%= if @insights != [] do %>
|
<%= if @insights != [] do %>
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
<.icon name="hero-light-bulb" class="h-5 w-5" /> Preseem Insights
|
<.icon name="hero-light-bulb" class="h-5 w-5" /> Preseem Insights
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 space-y-2">
|
<div class="mt-2 space-y-2">
|
||||||
<div
|
<div
|
||||||
:for={insight <- @insights}
|
:for={insight <- @insights}
|
||||||
class="flex items-start gap-3 rounded-lg border border-gray-200 dark:border-white/10 p-3"
|
class="flex items-start gap-3 rounded-lg border border-cool-steel-200 dark:border-white/10 p-3"
|
||||||
>
|
>
|
||||||
<.icon
|
<.icon
|
||||||
name={insight_urgency_icon(insight.urgency)}
|
name={insight_urgency_icon(insight.urgency)}
|
||||||
class={["h-5 w-5 mt-0.5 shrink-0", insight_urgency_class(insight.urgency)]}
|
class={["h-5 w-5 mt-0.5 shrink-0", insight_urgency_class(insight.urgency)]}
|
||||||
/>
|
/>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{insight.title}</p>
|
<p class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
|
{insight.title}
|
||||||
|
</p>
|
||||||
<%= if insight.description do %>
|
<%= if insight.description do %>
|
||||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 line-clamp-2">
|
<p class="mt-0.5 text-xs text-cool-steel-500 dark:text-cool-steel-400 line-clamp-2">
|
||||||
{insight.description}
|
{insight.description}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -553,9 +569,9 @@
|
||||||
<%!-- Latency Chart --%>
|
<%!-- Latency Chart --%>
|
||||||
<%= if @latency_chart_data do %>
|
<%= if @latency_chart_data do %>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-white/10 dark:bg-gray-800/50">
|
<div class="rounded-lg border border-cool-steel-200 bg-white dark:border-white/10 dark:bg-cool-steel-800/50">
|
||||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
<div class="px-4 py-3 border-b border-cool-steel-200 dark:border-white/10">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-1.5">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white flex items-center gap-1.5">
|
||||||
<.icon name="hero-chart-bar" class="h-4 w-4" /> {t("Site Latency — Last 24 Hours")}
|
<.icon name="hero-chart-bar" class="h-4 w-4" /> {t("Site Latency — Last 24 Hours")}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -572,9 +588,9 @@
|
||||||
>
|
>
|
||||||
<div class="absolute inset-0 flex items-center justify-center" data-loading>
|
<div class="absolute inset-0 flex items-center justify-center" data-loading>
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<div class="h-8 w-8 animate-spin rounded-full border-2 border-gray-200 dark:border-gray-700 border-t-blue-500">
|
<div class="h-8 w-8 animate-spin rounded-full border-2 border-cool-steel-200 dark:border-cool-steel-700 border-t-cerulean-500">
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-400">{t("Loading chart...")}</span>
|
<span class="text-xs text-cool-steel-400">{t("Loading chart...")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,11 @@ defmodule ToweropsWeb.StatusPageLive do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def overall_color(:operational), do: "bg-green-500"
|
def overall_color(:operational), do: "bg-green-500"
|
||||||
def overall_color(:degraded), do: "bg-yellow-500"
|
def overall_color(:degraded), do: "bg-wheat-500"
|
||||||
def overall_color(:partial_outage), do: "bg-orange-500"
|
def overall_color(:partial_outage), do: "bg-orange-500"
|
||||||
def overall_color(:major_outage), do: "bg-red-500"
|
def overall_color(:major_outage), do: "bg-sweet-salmon-500"
|
||||||
def overall_color(:maintenance), do: "bg-blue-500"
|
def overall_color(:maintenance), do: "bg-cerulean-500"
|
||||||
def overall_color(_), do: "bg-gray-500"
|
def overall_color(_), do: "bg-cool-steel-500"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def overall_text(:operational), do: "All Systems Operational"
|
def overall_text(:operational), do: "All Systems Operational"
|
||||||
|
|
@ -121,11 +121,11 @@ defmodule ToweropsWeb.StatusPageLive do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def component_color("operational"), do: "bg-green-500"
|
def component_color("operational"), do: "bg-green-500"
|
||||||
def component_color("degraded"), do: "bg-yellow-500"
|
def component_color("degraded"), do: "bg-wheat-500"
|
||||||
def component_color("partial_outage"), do: "bg-orange-500"
|
def component_color("partial_outage"), do: "bg-orange-500"
|
||||||
def component_color("major_outage"), do: "bg-red-500"
|
def component_color("major_outage"), do: "bg-sweet-salmon-500"
|
||||||
def component_color("maintenance"), do: "bg-blue-500"
|
def component_color("maintenance"), do: "bg-cerulean-500"
|
||||||
def component_color(_), do: "bg-gray-400"
|
def component_color(_), do: "bg-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def component_label("operational"), do: "Operational"
|
def component_label("operational"), do: "Operational"
|
||||||
|
|
@ -136,9 +136,9 @@ defmodule ToweropsWeb.StatusPageLive do
|
||||||
def component_label(s), do: s
|
def component_label(s), do: s
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def severity_color("critical"), do: "text-red-600"
|
def severity_color("critical"), do: "text-sweet-salmon-600"
|
||||||
def severity_color("major"), do: "text-orange-600"
|
def severity_color("major"), do: "text-orange-600"
|
||||||
def severity_color(_), do: "text-yellow-600"
|
def severity_color(_), do: "text-wheat-600"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def incident_status_label("investigating"), do: "Investigating"
|
def incident_status_label("investigating"), do: "Investigating"
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Status Page Not Found</title>
|
<title>Status Page Not Found</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 flex items-center justify-center min-h-screen">
|
<body class="bg-cool-steel-50 flex items-center justify-center min-h-screen">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Status Page Not Found</h1>
|
<h1 class="text-2xl font-bold text-cool-steel-900">Status Page Not Found</h1>
|
||||||
<p class="mt-2 text-gray-600">
|
<p class="mt-2 text-cool-steel-600">
|
||||||
The requested status page does not exist or is not enabled.
|
The requested status page does not exist or is not enabled.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -29,14 +29,14 @@
|
||||||
</style>
|
</style>
|
||||||
<% end %>
|
<% end %>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 dark:bg-gray-950 min-h-screen">
|
<body class="bg-cool-steel-50 dark:bg-cool-steel-950 min-h-screen">
|
||||||
<div class="max-w-3xl mx-auto px-4 py-8">
|
<div class="max-w-3xl mx-auto px-4 py-8">
|
||||||
<%!-- Header --%>
|
<%!-- Header --%>
|
||||||
<header class="text-center mb-8">
|
<header class="text-center mb-8">
|
||||||
<%= if @config.logo_url do %>
|
<%= if @config.logo_url do %>
|
||||||
<img src={@config.logo_url} alt={@config.company_name} class="h-10 mx-auto mb-4" />
|
<img src={@config.logo_url} alt={@config.company_name} class="h-10 mx-auto mb-4" />
|
||||||
<% end %>
|
<% end %>
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@config.company_name || "System Status"}
|
{@config.company_name || "System Status"}
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -52,28 +52,28 @@
|
||||||
<%!-- Active Incidents --%>
|
<%!-- Active Incidents --%>
|
||||||
<%= if @active_incidents != [] do %>
|
<%= if @active_incidents != [] do %>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
Active Incidents
|
Active Incidents
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<%= for incident <- @active_incidents do %>
|
<%= for incident <- @active_incidents do %>
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-cool-steel-700 p-4">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class={"font-semibold #{severity_color(incident.severity)}"}>
|
<h3 class={"font-semibold #{severity_color(incident.severity)}"}>
|
||||||
{incident.title}
|
{incident.title}
|
||||||
</h3>
|
</h3>
|
||||||
<%= if incident.body do %>
|
<%= if incident.body do %>
|
||||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
<p class="mt-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{incident.body}
|
{incident.body}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300">
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-wheat-100 text-wheat-800 dark:bg-wheat-900/50 dark:text-wheat-300">
|
||||||
{incident_status_label(incident.status)}
|
{incident_status_label(incident.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-xs text-gray-500">
|
<p class="mt-2 text-xs text-cool-steel-500">
|
||||||
Started: {Calendar.strftime(incident.started_at, "%b %d, %H:%M UTC")}
|
Started: {Calendar.strftime(incident.started_at, "%b %d, %H:%M UTC")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -84,21 +84,23 @@
|
||||||
|
|
||||||
<%!-- Components --%>
|
<%!-- Components --%>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Components</h2>
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700">
|
Components
|
||||||
|
</h2>
|
||||||
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-cool-steel-700 divide-y divide-cool-steel-200 dark:divide-cool-steel-700">
|
||||||
<%= if @components == [] do %>
|
<%= if @components == [] do %>
|
||||||
<div class="p-4 text-center text-gray-500 dark:text-gray-400">
|
<div class="p-4 text-center text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
No components configured
|
No components configured
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= for component <- @components do %>
|
<%= for component <- @components do %>
|
||||||
<div class="px-4 py-3 flex items-center justify-between">
|
<div class="px-4 py-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{component.name}
|
{component.name}
|
||||||
</span>
|
</span>
|
||||||
<%= if component.description do %>
|
<%= if component.description do %>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{component.description}
|
{component.description}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -106,7 +108,7 @@
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class={"w-2.5 h-2.5 rounded-full #{component_color(component.status)}"}>
|
<div class={"w-2.5 h-2.5 rounded-full #{component_color(component.status)}"}>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
{component_label(component.status)}
|
{component_label(component.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -118,26 +120,26 @@
|
||||||
|
|
||||||
<%!-- Recent Incidents --%>
|
<%!-- Recent Incidents --%>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
<h2 class="text-lg font-semibold text-cool-steel-900 dark:text-white mb-4">
|
||||||
Recent Incidents
|
Recent Incidents
|
||||||
</h2>
|
</h2>
|
||||||
<%= if @recent_incidents == [] do %>
|
<%= if @recent_incidents == [] do %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 text-center">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-cool-steel-700 p-4 text-center">
|
||||||
No recent incidents — looking good!
|
No recent incidents — looking good!
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<%= for incident <- @recent_incidents do %>
|
<%= for incident <- @recent_incidents do %>
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
<div class="bg-white dark:bg-cool-steel-800 rounded-lg border border-cool-steel-200 dark:border-cool-steel-700 p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
<h3 class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{incident.title}
|
{incident.title}
|
||||||
</h3>
|
</h3>
|
||||||
<span class={"text-xs px-2 py-0.5 rounded font-medium " <> if(incident.status == "resolved", do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300", else: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300")}>
|
<span class={"text-xs px-2 py-0.5 rounded font-medium " <> if(incident.status == "resolved", do: "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300", else: "bg-wheat-100 text-wheat-700 dark:bg-wheat-900/50 dark:text-wheat-300")}>
|
||||||
{incident_status_label(incident.status)}
|
{incident_status_label(incident.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
<p class="mt-1 text-xs text-cool-steel-500">
|
||||||
{Calendar.strftime(incident.started_at, "%b %d, %Y")}
|
{Calendar.strftime(incident.started_at, "%b %d, %Y")}
|
||||||
<%= if incident.resolved_at do %>
|
<%= if incident.resolved_at do %>
|
||||||
— Resolved {Calendar.strftime(incident.resolved_at, "%b %d, %H:%M UTC")}
|
— Resolved {Calendar.strftime(incident.resolved_at, "%b %d, %H:%M UTC")}
|
||||||
|
|
@ -150,13 +152,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Footer --%>
|
<%!-- Footer --%>
|
||||||
<footer class="text-center text-xs text-gray-400 dark:text-gray-600 mt-12">
|
<footer class="text-center text-xs text-cool-steel-400 dark:text-cool-steel-600 mt-12">
|
||||||
<%= if @config.support_email do %>
|
<%= if @config.support_email do %>
|
||||||
<p>
|
<p>
|
||||||
Need help? Contact
|
Need help? Contact
|
||||||
<a
|
<a
|
||||||
href={"mailto:#{@config.support_email}"}
|
href={"mailto:#{@config.support_email}"}
|
||||||
class="underline hover:text-gray-600 dark:hover:text-gray-400"
|
class="underline hover:text-cool-steel-600 dark:hover:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{@config.support_email}
|
{@config.support_email}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ defmodule ToweropsWeb.TraceLive.Index do
|
||||||
color =
|
color =
|
||||||
case assigns.quality do
|
case assigns.quality do
|
||||||
:good -> "border-green-200 dark:border-green-800"
|
:good -> "border-green-200 dark:border-green-800"
|
||||||
:warning -> "border-yellow-200 dark:border-yellow-800"
|
:warning -> "border-wheat-200 dark:border-wheat-800"
|
||||||
:bad -> "border-red-200 dark:border-red-800"
|
:bad -> "border-sweet-salmon-200 dark:border-sweet-salmon-800"
|
||||||
_ -> "border-cool-steel-200 dark:border-cool-steel-700"
|
_ -> "border-cool-steel-200 dark:border-cool-steel-700"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -140,9 +140,12 @@ defmodule ToweropsWeb.TraceLive.Index do
|
||||||
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
def type_badge_class(:inventory_item), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
def type_badge_class(:inventory_item), do: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
|
||||||
def type_badge_class(:site), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
def type_badge_class(:site), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
def type_badge_class(:device), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
def type_badge_class(:device), do: "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-400"
|
||||||
def type_badge_class(:access_point), do: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
|
|
||||||
|
def type_badge_class(:access_point),
|
||||||
|
do: "bg-cerulean-100 text-cerulean-800 dark:bg-cerulean-900/30 dark:text-cerulean-400"
|
||||||
|
|
||||||
def type_badge_class(_), do: "text-cool-steel-700 dark:text-cool-steel-300"
|
def type_badge_class(_), do: "text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
|
|
||||||
def type_badge_label(:account), do: "Account"
|
def type_badge_label(:account), do: "Account"
|
||||||
|
|
@ -154,7 +157,7 @@ defmodule ToweropsWeb.TraceLive.Index do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def status_badge_class("active"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
def status_badge_class("active"), do: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
||||||
def status_badge_class("suspended"), do: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
def status_badge_class("suspended"), do: "bg-wheat-100 text-wheat-800 dark:bg-wheat-900/30 dark:text-wheat-400"
|
||||||
|
|
||||||
def status_badge_class("cancelled"),
|
def status_badge_class("cancelled"),
|
||||||
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
do: "bg-sweet-salmon-100 text-sweet-salmon-800 dark:bg-sweet-salmon-900/30 dark:text-sweet-salmon-400"
|
||||||
|
|
@ -163,8 +166,8 @@ defmodule ToweropsWeb.TraceLive.Index do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def device_status_color(:up), do: "text-green-600 dark:text-green-400"
|
def device_status_color(:up), do: "text-green-600 dark:text-green-400"
|
||||||
def device_status_color(:down), do: "text-red-600 dark:text-red-400"
|
def device_status_color(:down), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
def device_status_color(_), do: "text-gray-500 dark:text-gray-400"
|
def device_status_color(_), do: "text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def format_speed(nil), do: "—"
|
def format_speed(nil), do: "—"
|
||||||
|
|
@ -218,45 +221,45 @@ defmodule ToweropsWeb.TraceLive.Index do
|
||||||
def score_quality(_), do: :bad
|
def score_quality(_), do: :bad
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def score_color(nil), do: "text-gray-400"
|
def score_color(nil), do: "text-cool-steel-400"
|
||||||
def score_color(s) when s >= 80, do: "text-green-600 dark:text-green-400"
|
def score_color(s) when s >= 80, do: "text-green-600 dark:text-green-400"
|
||||||
def score_color(s) when s >= 50, do: "text-yellow-600 dark:text-yellow-400"
|
def score_color(s) when s >= 50, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def score_color(_), do: "text-red-600 dark:text-red-400"
|
def score_color(_), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def latency_color(nil), do: "text-gray-400"
|
def latency_color(nil), do: "text-cool-steel-400"
|
||||||
def latency_color(v) when v <= 30, do: "text-green-600 dark:text-green-400"
|
def latency_color(v) when v <= 30, do: "text-green-600 dark:text-green-400"
|
||||||
def latency_color(v) when v <= 80, do: "text-yellow-600 dark:text-yellow-400"
|
def latency_color(v) when v <= 80, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def latency_color(_), do: "text-red-600 dark:text-red-400"
|
def latency_color(_), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def jitter_color(nil), do: "text-gray-400"
|
def jitter_color(nil), do: "text-cool-steel-400"
|
||||||
def jitter_color(v) when v <= 10, do: "text-green-600 dark:text-green-400"
|
def jitter_color(v) when v <= 10, do: "text-green-600 dark:text-green-400"
|
||||||
def jitter_color(v) when v <= 30, do: "text-yellow-600 dark:text-yellow-400"
|
def jitter_color(v) when v <= 30, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def jitter_color(_), do: "text-red-600 dark:text-red-400"
|
def jitter_color(_), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def loss_color(nil), do: "text-gray-400"
|
def loss_color(nil), do: "text-cool-steel-400"
|
||||||
def loss_color(v) when v <= 1, do: "text-green-600 dark:text-green-400"
|
def loss_color(v) when v <= 1, do: "text-green-600 dark:text-green-400"
|
||||||
def loss_color(v) when v <= 5, do: "text-yellow-600 dark:text-yellow-400"
|
def loss_color(v) when v <= 5, do: "text-wheat-600 dark:text-wheat-400"
|
||||||
def loss_color(_), do: "text-red-600 dark:text-red-400"
|
def loss_color(_), do: "text-sweet-salmon-600 dark:text-sweet-salmon-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def device_status_dot(:up), do: "bg-green-500"
|
def device_status_dot(:up), do: "bg-green-500"
|
||||||
def device_status_dot(:down), do: "bg-red-500"
|
def device_status_dot(:down), do: "bg-sweet-salmon-500"
|
||||||
def device_status_dot(_), do: "bg-gray-400"
|
def device_status_dot(_), do: "bg-cool-steel-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def alert_dot(%{severity: "critical"}), do: "bg-red-500"
|
def alert_dot(%{severity: "critical"}), do: "bg-sweet-salmon-500"
|
||||||
def alert_dot(%{severity: "warning"}), do: "bg-yellow-500"
|
def alert_dot(%{severity: "warning"}), do: "bg-wheat-500"
|
||||||
def alert_dot(%{alert_type: "down"}), do: "bg-red-500"
|
def alert_dot(%{alert_type: "down"}), do: "bg-sweet-salmon-500"
|
||||||
def alert_dot(_), do: "bg-orange-400"
|
def alert_dot(_), do: "bg-orange-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def urgency_dot("critical"), do: "bg-red-500"
|
def urgency_dot("critical"), do: "bg-sweet-salmon-500"
|
||||||
def urgency_dot("high"), do: "bg-orange-500"
|
def urgency_dot("high"), do: "bg-orange-500"
|
||||||
def urgency_dot("medium"), do: "bg-yellow-500"
|
def urgency_dot("medium"), do: "bg-wheat-500"
|
||||||
def urgency_dot(_), do: "bg-blue-400"
|
def urgency_dot(_), do: "bg-cerulean-400"
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def airtime_quality(nil), do: :unknown
|
def airtime_quality(nil), do: :unknown
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@
|
||||||
end
|
end
|
||||||
} />
|
} />
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">{t("Trace")}</h1>
|
<h1 class="text-xl font-bold text-cool-steel-900 dark:text-white">{t("Trace")}</h1>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Investigate subscriber impact, site health, and network path issues.")}
|
{t("Investigate subscriber impact, site health, and network path issues.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<%!-- Search Bar --%>
|
<%!-- Search Bar --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<.icon name="hero-magnifying-glass" class="h-6 w-6 text-gray-400 flex-shrink-0" />
|
<.icon name="hero-magnifying-glass" class="h-6 w-6 text-cool-steel-400 flex-shrink-0" />
|
||||||
<form phx-change="search" phx-submit="search" class="flex-1 flex gap-2">
|
<form phx-change="search" phx-submit="search" class="flex-1 flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
"Search by customer name, IP address, account ID, serial number, site name, or device name..."
|
"Search by customer name, IP address, account ID, serial number, site name, or device name..."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
class="w-full rounded-field border border-cool-steel-300 bg-cool-steel-50 px-3 py-2 text-sm text-cool-steel-900 placeholder:text-cool-steel-400 focus:border-cerulean-500 focus:ring-1 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-900 dark:text-cool-steel-100 dark:placeholder:text-cool-steel-500 w-full dark:bg-gray-800 dark:border-white/10 dark:text-white"
|
class="w-full rounded-field border border-cool-steel-300 bg-cool-steel-50 px-3 py-2 text-sm text-cool-steel-900 placeholder:text-cool-steel-400 focus:border-cerulean-500 focus:ring-1 focus:ring-cerulean-500 dark:border-cool-steel-600 dark:bg-cool-steel-900 dark:text-cool-steel-100 dark:placeholder:text-cool-steel-500 w-full dark:bg-cool-steel-800 dark:border-white/10 dark:text-white"
|
||||||
phx-debounce="300"
|
phx-debounce="300"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<%!-- Search Results --%>
|
<%!-- Search Results --%>
|
||||||
<div
|
<div
|
||||||
:if={@results != [] && !@trace}
|
:if={@results != [] && !@trace}
|
||||||
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900"
|
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900"
|
||||||
>
|
>
|
||||||
<div class="p-0">
|
<div class="p-0">
|
||||||
<div class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700 dark:divide-white/10">
|
<div class="divide-y divide-cool-steel-200 dark:divide-cool-steel-700 dark:divide-white/10">
|
||||||
|
|
@ -72,12 +72,17 @@
|
||||||
{type_badge_label(result.type)}
|
{type_badge_label(result.type)}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="font-medium text-gray-900 dark:text-white truncate">{result.label}</div>
|
<div class="font-medium text-cool-steel-900 dark:text-white truncate">
|
||||||
<div :if={result.sublabel} class="text-sm text-gray-500 dark:text-gray-400 truncate">
|
{result.label}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:if={result.sublabel}
|
||||||
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 truncate"
|
||||||
|
>
|
||||||
{result.sublabel}
|
{result.sublabel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<.icon name="hero-chevron-right" class="h-4 w-4 text-gray-400 flex-shrink-0" />
|
<.icon name="hero-chevron-right" class="h-4 w-4 text-cool-steel-400 flex-shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,7 +91,7 @@
|
||||||
<%!-- No Results --%>
|
<%!-- No Results --%>
|
||||||
<div
|
<div
|
||||||
:if={@query != "" && @results == [] && !@trace}
|
:if={@query != "" && @results == [] && !@trace}
|
||||||
class="text-center py-12 text-gray-500 dark:text-gray-400"
|
class="text-center py-12 text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-magnifying-glass" class="h-12 w-12 mx-auto mb-3 opacity-50" />
|
<.icon name="hero-magnifying-glass" class="h-12 w-12 mx-auto mb-3 opacity-50" />
|
||||||
<p class="text-lg font-medium">No results found</p>
|
<p class="text-lg font-medium">No results found</p>
|
||||||
|
|
@ -98,23 +103,29 @@
|
||||||
<%!-- Site Trace View --%>
|
<%!-- Site Trace View --%>
|
||||||
<div :if={@trace && @trace_type == :site} class="space-y-4">
|
<div :if={@trace && @trace_type == :site} class="space-y-4">
|
||||||
<%!-- Site Info --%>
|
<%!-- Site Info --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
<h3 class="text-lg font-semibold text-cool-steel-900 dark:text-white flex items-center gap-2">
|
||||||
<.icon name="hero-building-office-2" class="h-5 w-5 text-gray-400" />
|
<.icon name="hero-building-office-2" class="h-5 w-5 text-cool-steel-400" />
|
||||||
{@trace.site.name}
|
{@trace.site.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
<div class="mt-2 space-y-1 text-sm text-cool-steel-600 dark:text-cool-steel-400">
|
||||||
<p :if={@trace.site.location}>
|
<p :if={@trace.site.location}>
|
||||||
<span class="font-medium text-gray-500 dark:text-gray-400">Location:</span>
|
<span class="font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Location:
|
||||||
|
</span>
|
||||||
{@trace.site.location}
|
{@trace.site.location}
|
||||||
</p>
|
</p>
|
||||||
<p :if={@trace.site.address}>
|
<p :if={@trace.site.address}>
|
||||||
<span class="font-medium text-gray-500 dark:text-gray-400">Address:</span>
|
<span class="font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Address:
|
||||||
|
</span>
|
||||||
{@trace.site.address}
|
{@trace.site.address}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span class="font-medium text-gray-500 dark:text-gray-400">Devices:</span>
|
<span class="font-medium text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Devices:
|
||||||
|
</span>
|
||||||
{length(@trace.devices)}
|
{length(@trace.devices)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -123,16 +134,16 @@
|
||||||
<%!-- Devices at Site --%>
|
<%!-- Devices at Site --%>
|
||||||
<div
|
<div
|
||||||
:if={length(@trace.devices) > 0}
|
:if={length(@trace.devices) > 0}
|
||||||
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900"
|
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900"
|
||||||
>
|
>
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white mb-3">
|
||||||
{t("Devices")}
|
{t("Devices")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full text-sm">
|
<table class="min-w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs text-gray-500 dark:text-gray-400 uppercase">
|
<tr class="text-xs text-cool-steel-500 dark:text-cool-steel-400 uppercase">
|
||||||
<th>{t("Name")}</th>
|
<th>{t("Name")}</th>
|
||||||
<th>{t("IP Address")}</th>
|
<th>{t("IP Address")}</th>
|
||||||
<th>{t("Status")}</th>
|
<th>{t("Status")}</th>
|
||||||
|
|
@ -146,7 +157,7 @@
|
||||||
<td>
|
<td>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{device.id}"}
|
navigate={~p"/devices/#{device.id}"}
|
||||||
class="font-medium text-blue-600 dark:text-blue-400 hover:underline"
|
class="font-medium text-cerulean-600 dark:text-cerulean-400 hover:underline"
|
||||||
>
|
>
|
||||||
{device.name || device.ip_address}
|
{device.name || device.ip_address}
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -170,10 +181,10 @@
|
||||||
<%!-- Recent Alerts --%>
|
<%!-- Recent Alerts --%>
|
||||||
<div
|
<div
|
||||||
:if={@trace.alerts != []}
|
:if={@trace.alerts != []}
|
||||||
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900"
|
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900"
|
||||||
>
|
>
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
<h3 class="text-sm font-semibold text-cool-steel-900 dark:text-white mb-3">
|
||||||
{t("Recent Alerts")}
|
{t("Recent Alerts")}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|
@ -185,10 +196,13 @@
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{format_alert_type(alert.alert_type)}
|
{format_alert_type(alert.alert_type)}
|
||||||
</span>
|
</span>
|
||||||
<span :if={alert.device} class="text-xs text-gray-500 dark:text-gray-400">
|
<span
|
||||||
|
:if={alert.device}
|
||||||
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
|
>
|
||||||
{alert.device.name}
|
{alert.device.name}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -198,10 +212,10 @@
|
||||||
{t("Resolved")}
|
{t("Resolved")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 truncate">
|
<p class="text-sm text-cool-steel-600 dark:text-cool-steel-400 truncate">
|
||||||
{alert.message}
|
{alert.message}
|
||||||
</p>
|
</p>
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
<span class="text-xs text-cool-steel-400 dark:text-cool-steel-500">
|
||||||
{format_relative_time(alert.triggered_at)}
|
{format_relative_time(alert.triggered_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,17 +233,20 @@
|
||||||
<%!-- Subscriber + Connection --%>
|
<%!-- Subscriber + Connection --%>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<%!-- Subscriber Info --%>
|
<%!-- Subscriber Info --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-user" class="h-4 w-4 inline" /> Subscriber
|
<.icon name="hero-user" class="h-4 w-4 inline" /> Subscriber
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.subscriber do %>
|
<%= if @trace.subscriber do %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="text-xl font-bold text-gray-900 dark:text-white">
|
<div class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@trace.subscriber.name}
|
{@trace.subscriber.name}
|
||||||
</div>
|
</div>
|
||||||
<div :if={@trace.subscriber.plan} class="text-sm text-gray-600 dark:text-gray-300">
|
<div
|
||||||
|
:if={@trace.subscriber.plan}
|
||||||
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
{String.capitalize(@trace.subscriber.plan)}
|
{String.capitalize(@trace.subscriber.plan)}
|
||||||
<%!-- Financial data: role-gated --%>
|
<%!-- Financial data: role-gated --%>
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
|
|
@ -243,24 +260,24 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={@trace.subscriber.speed_download}
|
:if={@trace.subscriber.speed_download}
|
||||||
class="text-sm text-gray-500 dark:text-gray-400"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
↓ {format_speed(@trace.subscriber.speed_download)} / ↑ {format_speed(
|
↓ {format_speed(@trace.subscriber.speed_download)} / ↑ {format_speed(
|
||||||
@trace.subscriber.speed_upload
|
@trace.subscriber.speed_upload
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
Account #: {@trace.subscriber.account_id}
|
Account #: {@trace.subscriber.account_id}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={@trace.subscriber.address}
|
:if={@trace.subscriber.address}
|
||||||
class="text-sm text-gray-500 dark:text-gray-400"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
<.icon name="hero-map-pin" class="h-3.5 w-3.5 inline" /> {@trace.subscriber.address}
|
<.icon name="hero-map-pin" class="h-3.5 w-3.5 inline" /> {@trace.subscriber.address}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={@trace.subscriber.customer_since}
|
:if={@trace.subscriber.customer_since}
|
||||||
class="text-sm text-gray-500 dark:text-gray-400"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
Customer since: {Calendar.strftime(@trace.subscriber.customer_since, "%b %Y")}
|
Customer since: {Calendar.strftime(@trace.subscriber.customer_since, "%b %Y")}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -275,7 +292,7 @@
|
||||||
<%= if @can_view_financials do %>
|
<%= if @can_view_financials do %>
|
||||||
<span
|
<span
|
||||||
:if={@trace.subscriber.mrr}
|
:if={@trace.subscriber.mrr}
|
||||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
class="text-sm font-medium text-cool-steel-700 dark:text-cool-steel-300"
|
||||||
>
|
>
|
||||||
MRR: ${if(is_struct(@trace.subscriber.mrr, Decimal),
|
MRR: ${if(is_struct(@trace.subscriber.mrr, Decimal),
|
||||||
do: Decimal.round(@trace.subscriber.mrr, 2),
|
do: Decimal.round(@trace.subscriber.mrr, 2),
|
||||||
|
|
@ -286,7 +303,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
{t("No subscriber info available")}
|
{t("No subscriber info available")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -294,31 +311,36 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Connection / Device Info --%>
|
<%!-- Connection / Device Info --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-signal" class="h-4 w-4 inline" /> Connection
|
<.icon name="hero-signal" class="h-4 w-4 inline" /> Connection
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.device do %>
|
<%= if @trace.device do %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="text-xl font-bold text-gray-900 dark:text-white">
|
<div class="text-xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/devices/#{@trace.device.id}"}
|
navigate={~p"/devices/#{@trace.device.id}"}
|
||||||
class="hover:text-blue-600 dark:hover:text-blue-400"
|
class="hover:text-cerulean-600 dark:hover:text-cerulean-400"
|
||||||
>
|
>
|
||||||
{@trace.device.name || @trace.device.ip_address}
|
{@trace.device.name || @trace.device.ip_address}
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div :if={@trace.device.site} class="text-sm text-gray-600 dark:text-gray-300">
|
<div
|
||||||
|
:if={@trace.device.site}
|
||||||
|
class="text-sm text-cool-steel-600 dark:text-cool-steel-300"
|
||||||
|
>
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/sites/#{@trace.device.site.id}"}
|
navigate={~p"/sites/#{@trace.device.site.id}"}
|
||||||
class="hover:text-blue-600"
|
class="hover:text-cerulean-600"
|
||||||
>
|
>
|
||||||
{@trace.device.site.name}
|
{@trace.device.site.name}
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">Status:</span>
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
|
Status:
|
||||||
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"inline-flex items-center gap-1 text-sm font-medium",
|
"inline-flex items-center gap-1 text-sm font-medium",
|
||||||
device_status_color(@trace.device.status)
|
device_status_color(@trace.device.status)
|
||||||
|
|
@ -331,12 +353,12 @@
|
||||||
{String.capitalize(to_string(@trace.device.status))}
|
{String.capitalize(to_string(@trace.device.status))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
<div class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
IP: {@trace.device.ip_address}
|
IP: {@trace.device.ip_address}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={@trace.device.last_checked_at}
|
:if={@trace.device.last_checked_at}
|
||||||
class="text-sm text-gray-500 dark:text-gray-400"
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
Last checked: {format_relative_time(@trace.device.last_checked_at)}
|
Last checked: {format_relative_time(@trace.device.last_checked_at)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -344,22 +366,24 @@
|
||||||
:if={@trace.access_point}
|
:if={@trace.access_point}
|
||||||
class="mt-2 pt-2 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10"
|
class="mt-2 pt-2 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10"
|
||||||
>
|
>
|
||||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-400 mb-1">
|
<div class="text-xs font-semibold uppercase tracking-wider text-cool-steel-400 mb-1">
|
||||||
{t("Preseem AP")}
|
{t("Preseem AP")}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
<div class="text-sm text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@trace.access_point.name}
|
{@trace.access_point.name}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={@trace.access_point.model}
|
:if={@trace.access_point.model}
|
||||||
class="text-xs text-gray-500 dark:text-gray-400"
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400"
|
||||||
>
|
>
|
||||||
{@trace.access_point.model}
|
{@trace.access_point.model}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">No device linked</p>
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
|
No device linked
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,9 +392,9 @@
|
||||||
<%!-- QoE Metrics + Peer Impact --%>
|
<%!-- QoE Metrics + Peer Impact --%>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<%!-- QoE Metrics --%>
|
<%!-- QoE Metrics --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-chart-bar" class="h-4 w-4 inline" /> QoE Metrics
|
<.icon name="hero-chart-bar" class="h-4 w-4 inline" /> QoE Metrics
|
||||||
<span class="text-xs font-normal normal-case">(from Preseem)</span>
|
<span class="text-xs font-normal normal-case">(from Preseem)</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -399,12 +423,12 @@
|
||||||
</div>
|
</div>
|
||||||
<%= if @trace.qoe_metrics.latest_metric do %>
|
<%= if @trace.qoe_metrics.latest_metric do %>
|
||||||
<div class="mt-4 pt-3 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10">
|
<div class="mt-4 pt-3 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10">
|
||||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">
|
<div class="text-xs font-semibold uppercase tracking-wider text-cool-steel-400 mb-2">
|
||||||
{t("Latest Metrics")}
|
{t("Latest Metrics")}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Latency</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">Latency</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-medium",
|
"font-medium",
|
||||||
latency_color(@trace.qoe_metrics.latest_metric.avg_latency)
|
latency_color(@trace.qoe_metrics.latest_metric.avg_latency)
|
||||||
|
|
@ -413,7 +437,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Jitter</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">Jitter</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-medium",
|
"font-medium",
|
||||||
jitter_color(@trace.qoe_metrics.latest_metric.avg_jitter)
|
jitter_color(@trace.qoe_metrics.latest_metric.avg_jitter)
|
||||||
|
|
@ -422,7 +446,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Loss</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">Loss</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"font-medium",
|
"font-medium",
|
||||||
loss_color(@trace.qoe_metrics.latest_metric.avg_loss)
|
loss_color(@trace.qoe_metrics.latest_metric.avg_loss)
|
||||||
|
|
@ -431,8 +455,8 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Throughput</span>
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">Throughput</span>
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{format_throughput(@trace.qoe_metrics.latest_metric.avg_throughput)}
|
{format_throughput(@trace.qoe_metrics.latest_metric.avg_throughput)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -440,7 +464,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
{t("No Preseem data available")}
|
{t("No Preseem data available")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -448,37 +472,37 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Peer Impact --%>
|
<%!-- Peer Impact --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-users" class="h-4 w-4 inline" /> Peer Impact
|
<.icon name="hero-users" class="h-4 w-4 inline" /> Peer Impact
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.peer_impact do %>
|
<%= if @trace.peer_impact do %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Subscribers on this AP")}
|
{t("Subscribers on this AP")}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
<span class="text-2xl font-bold text-cool-steel-900 dark:text-white">
|
||||||
{@trace.peer_impact.subscriber_count}
|
{@trace.peer_impact.subscriber_count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Other APs on device")}
|
{t("Other APs on device")}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-lg font-semibold text-gray-700 dark:text-gray-300">
|
<span class="text-lg font-semibold text-cool-steel-700 dark:text-cool-steel-300">
|
||||||
{@trace.peer_impact.peer_ap_count}
|
{@trace.peer_impact.peer_ap_count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
<span class="text-sm text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{t("Active alerts on device")}
|
{t("Active alerts on device")}
|
||||||
</span>
|
</span>
|
||||||
<span class={[
|
<span class={[
|
||||||
"text-lg font-semibold",
|
"text-lg font-semibold",
|
||||||
if(@trace.peer_impact.active_alert_count > 0,
|
if(@trace.peer_impact.active_alert_count > 0,
|
||||||
do: "text-red-600",
|
do: "text-sweet-salmon-600",
|
||||||
else: "text-green-600"
|
else: "text-green-600"
|
||||||
)
|
)
|
||||||
]}>
|
]}>
|
||||||
|
|
@ -489,7 +513,7 @@
|
||||||
:if={@trace.peer_impact.peer_aps != []}
|
:if={@trace.peer_impact.peer_aps != []}
|
||||||
class="mt-3 pt-3 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10"
|
class="mt-3 pt-3 border-t border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10"
|
||||||
>
|
>
|
||||||
<div class="text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">
|
<div class="text-xs font-semibold uppercase tracking-wider text-cool-steel-400 mb-2">
|
||||||
{t("Peer APs")}
|
{t("Peer APs")}
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
|
|
@ -497,9 +521,11 @@
|
||||||
:for={ap <- @trace.peer_impact.peer_aps}
|
:for={ap <- @trace.peer_impact.peer_aps}
|
||||||
class="flex items-center justify-between text-sm"
|
class="flex items-center justify-between text-sm"
|
||||||
>
|
>
|
||||||
<span class="text-gray-700 dark:text-gray-300 truncate">{ap.name}</span>
|
<span class="text-cool-steel-700 dark:text-cool-steel-300 truncate">
|
||||||
|
{ap.name}
|
||||||
|
</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-gray-500 dark:text-gray-400">
|
<span class="text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{ap.subscriber_count || 0} subs
|
{ap.subscriber_count || 0} subs
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -514,7 +540,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">
|
<p class="text-sm text-cool-steel-500 dark:text-cool-steel-400 italic">
|
||||||
{t("No peer data available")}
|
{t("No peer data available")}
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
@ -523,9 +549,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Active Alerts --%>
|
<%!-- Active Alerts --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-bell-alert" class="h-4 w-4 inline" /> Recent Alerts
|
<.icon name="hero-bell-alert" class="h-4 w-4 inline" /> Recent Alerts
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.alerts == [] do %>
|
<%= if @trace.alerts == [] do %>
|
||||||
|
|
@ -545,14 +571,17 @@
|
||||||
]}>
|
]}>
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
<span class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{format_alert_type(alert.alert_type)}
|
{format_alert_type(alert.alert_type)}
|
||||||
</span>
|
</span>
|
||||||
<span :if={alert.message} class="text-sm text-gray-500 dark:text-gray-400 ml-1">
|
<span
|
||||||
|
:if={alert.message}
|
||||||
|
class="text-sm text-cool-steel-500 dark:text-cool-steel-400 ml-1"
|
||||||
|
>
|
||||||
— {alert.message}
|
— {alert.message}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-gray-400 flex-shrink-0">
|
<div class="text-xs text-cool-steel-500 dark:text-cool-steel-400 flex-shrink-0">
|
||||||
{format_relative_time(alert.triggered_at)}
|
{format_relative_time(alert.triggered_at)}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|
@ -576,9 +605,9 @@
|
||||||
<%!-- Recent Activity: Config Changes + Insights --%>
|
<%!-- Recent Activity: Config Changes + Insights --%>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<%!-- Config Changes — hidden, feature temporarily disabled --%>
|
<%!-- Config Changes — hidden, feature temporarily disabled --%>
|
||||||
<div class="hidden card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="hidden card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-document-text" class="h-4 w-4 inline" /> Config Changes (24h)
|
<.icon name="hero-document-text" class="h-4 w-4 inline" /> Config Changes (24h)
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.config_changes == [] do %>
|
<%= if @trace.config_changes == [] do %>
|
||||||
|
|
@ -590,16 +619,16 @@
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div :for={change <- @trace.config_changes} class="text-sm">
|
<div :for={change <- @trace.config_changes} class="text-sm">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="font-medium text-gray-900 dark:text-white">
|
<span class="font-medium text-cool-steel-900 dark:text-white">
|
||||||
{Map.get(change, :changed_sections, []) |> Enum.join(", ")}
|
{Map.get(change, :changed_sections, []) |> Enum.join(", ")}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
<span class="text-xs text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
{format_relative_time(change.changed_at)}
|
{format_relative_time(change.changed_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={Map.get(change, :diff_summary)}
|
:if={Map.get(change, :diff_summary)}
|
||||||
class="text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-mono truncate"
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400 mt-0.5 font-mono truncate"
|
||||||
>
|
>
|
||||||
{change.diff_summary}
|
{change.diff_summary}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -610,9 +639,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- Active Insights --%>
|
<%!-- Active Insights --%>
|
||||||
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900">
|
<div class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-light-bulb" class="h-4 w-4 inline" /> Active Insights
|
<.icon name="hero-light-bulb" class="h-4 w-4 inline" /> Active Insights
|
||||||
</h3>
|
</h3>
|
||||||
<%= if @trace.insights == [] do %>
|
<%= if @trace.insights == [] do %>
|
||||||
|
|
@ -629,12 +658,12 @@
|
||||||
]}>
|
]}>
|
||||||
</span>
|
</span>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
<div class="text-sm font-medium text-cool-steel-900 dark:text-white">
|
||||||
{insight.title}
|
{insight.title}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:if={insight.description}
|
:if={insight.description}
|
||||||
class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2"
|
class="text-xs text-cool-steel-500 dark:text-cool-steel-400 line-clamp-2"
|
||||||
>
|
>
|
||||||
{insight.description}
|
{insight.description}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -649,16 +678,16 @@
|
||||||
<%!-- Inventory Items --%>
|
<%!-- Inventory Items --%>
|
||||||
<div
|
<div
|
||||||
:if={@trace.inventory_items != []}
|
:if={@trace.inventory_items != []}
|
||||||
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-gray-900"
|
class="card shadow-sm border border-cool-steel-200 dark:border-cool-steel-700 dark:border-white/10 dark:bg-cool-steel-900"
|
||||||
>
|
>
|
||||||
<div class="p-4 sm:p-6">
|
<div class="p-4 sm:p-6">
|
||||||
<h3 class="font-semibold text-sm uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3">
|
<h3 class="font-semibold text-sm uppercase tracking-wider text-cool-steel-500 dark:text-cool-steel-400 mb-3">
|
||||||
<.icon name="hero-cube" class="h-4 w-4 inline" /> Equipment (from Gaiia)
|
<.icon name="hero-cube" class="h-4 w-4 inline" /> Equipment (from Gaiia)
|
||||||
</h3>
|
</h3>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full text-sm">
|
<table class="min-w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-xs uppercase text-gray-500 dark:text-gray-400">
|
<tr class="text-xs uppercase text-cool-steel-500 dark:text-cool-steel-400">
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
<th>Model</th>
|
<th>Model</th>
|
||||||
|
|
@ -671,7 +700,7 @@
|
||||||
:for={item <- @trace.inventory_items}
|
:for={item <- @trace.inventory_items}
|
||||||
class="hover:bg-cool-steel-100/50 dark:hover:bg-cool-steel-800/50"
|
class="hover:bg-cool-steel-100/50 dark:hover:bg-cool-steel-800/50"
|
||||||
>
|
>
|
||||||
<td class="font-medium text-gray-900 dark:text-white">{item.name}</td>
|
<td class="font-medium text-cool-steel-900 dark:text-white">{item.name}</td>
|
||||||
<td class="font-mono text-sm">{item.ip_address}</td>
|
<td class="font-mono text-sm">{item.ip_address}</td>
|
||||||
<td>{item.model_name}</td>
|
<td>{item.model_name}</td>
|
||||||
<td class="font-mono text-xs">{item.serial_number}</td>
|
<td class="font-mono text-xs">{item.serial_number}</td>
|
||||||
|
|
@ -695,12 +724,12 @@
|
||||||
<div :if={@query == "" && !@trace} class="text-center py-16">
|
<div :if={@query == "" && !@trace} class="text-center py-16">
|
||||||
<.icon
|
<.icon
|
||||||
name="hero-magnifying-glass-circle"
|
name="hero-magnifying-glass-circle"
|
||||||
class="h-16 w-16 mx-auto mb-4 text-gray-300 dark:text-gray-600"
|
class="h-16 w-16 mx-auto mb-4 text-cool-steel-300 dark:text-cool-steel-600"
|
||||||
/>
|
/>
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
<h2 class="text-xl font-semibold text-cool-steel-900 dark:text-white mb-2">
|
||||||
{t("Subscriber Trace")}
|
{t("Subscriber Trace")}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-500 dark:text-gray-400 max-w-md mx-auto">
|
<p class="text-cool-steel-500 dark:text-cool-steel-400 max-w-md mx-auto">
|
||||||
{t(
|
{t(
|
||||||
"Search for a customer by name, IP address, account ID, site name, or device name to see their full network status at a glance."
|
"Search for a customer by name, IP address, account ID, site name, or device name to see their full network status at a glance."
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
Already registered?
|
Already registered?
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/log-in"}
|
navigate={~p"/users/log-in"}
|
||||||
class="font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="font-semibold text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Log in
|
Log in
|
||||||
</.link>
|
</.link>
|
||||||
|
|
@ -189,8 +189,8 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
</.header>
|
</.header>
|
||||||
|
|
||||||
<%= if @invitation do %>
|
<%= if @invitation do %>
|
||||||
<div class="mt-6 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4 border border-blue-200 dark:border-blue-800">
|
<div class="mt-6 rounded-lg bg-cerulean-50 dark:bg-cerulean-900/20 p-4 border border-cerulean-200 dark:border-cerulean-800">
|
||||||
<p class="text-sm text-blue-900 dark:text-blue-100">
|
<p class="text-sm text-cerulean-900 dark:text-cerulean-100">
|
||||||
You've been invited to join
|
You've been invited to join
|
||||||
<span class="font-semibold">{@invitation.organization.name}</span>
|
<span class="font-semibold">{@invitation.organization.name}</span>
|
||||||
as
|
as
|
||||||
|
|
@ -198,11 +198,11 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-6 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4 border border-blue-200 dark:border-blue-800">
|
<div class="mt-6 rounded-lg bg-cerulean-50 dark:bg-cerulean-900/20 p-4 border border-cerulean-200 dark:border-cerulean-800">
|
||||||
<p class="text-sm font-medium text-blue-900 dark:text-blue-100">
|
<p class="text-sm font-medium text-cerulean-900 dark:text-cerulean-100">
|
||||||
Free tier includes:
|
Free tier includes:
|
||||||
</p>
|
</p>
|
||||||
<ul class="mt-2 text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
<ul class="mt-2 text-sm text-cerulean-800 dark:text-cerulean-200 space-y-1">
|
||||||
<li>✓ Monitor up to 10 devices</li>
|
<li>✓ Monitor up to 10 devices</li>
|
||||||
<li>✓ Real-time alerts and notifications</li>
|
<li>✓ Real-time alerts and notifications</li>
|
||||||
<li>✓ Performance charts and historical data</li>
|
<li>✓ Performance charts and historical data</li>
|
||||||
|
|
@ -250,16 +250,16 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @password_breach_count && @password_breach_count > 0 do %>
|
<%= if @password_breach_count && @password_breach_count > 0 do %>
|
||||||
<div class="mt-2 rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
|
<div class="mt-2 rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-yellow-400" />
|
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-wheat-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
<h3 class="text-sm font-medium text-wheat-800 dark:text-wheat-200">
|
||||||
Password found in data breaches
|
Password found in data breaches
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
<div class="mt-2 text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<p>
|
<p>
|
||||||
This password has appeared {@password_breach_count}
|
This password has appeared {@password_breach_count}
|
||||||
{if @password_breach_count == 1, do: "time", else: "times"} in known data breaches. We strongly recommend choosing a different,
|
{if @password_breach_count == 1, do: "time", else: "times"} in known data breaches. We strongly recommend choosing a different,
|
||||||
|
|
@ -291,7 +291,7 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={@privacy_policy_consent}
|
checked={@privacy_policy_consent}
|
||||||
required
|
required
|
||||||
class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
class="h-4 w-4 rounded border-zinc-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-cerulean-500 dark:checked:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 text-sm">
|
<div class="ml-3 text-sm">
|
||||||
|
|
@ -303,12 +303,12 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
<.link
|
<.link
|
||||||
href={~p"/privacy"}
|
href={~p"/privacy"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</.link>
|
</.link>
|
||||||
<span class="text-red-600 dark:text-red-400">*</span>
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -321,7 +321,7 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={@terms_of_service_consent}
|
checked={@terms_of_service_consent}
|
||||||
required
|
required
|
||||||
class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-blue-500 dark:checked:border-blue-500"
|
class="h-4 w-4 rounded border-zinc-300 text-cerulean-600 focus:ring-cerulean-500 dark:border-zinc-600 dark:bg-zinc-800 dark:checked:bg-cerulean-500 dark:checked:border-cerulean-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 text-sm">
|
<div class="ml-3 text-sm">
|
||||||
|
|
@ -333,12 +333,12 @@ defmodule ToweropsWeb.UserRegistrationLive do
|
||||||
<.link
|
<.link
|
||||||
href={~p"/terms"}
|
href={~p"/terms"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="font-medium text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
onclick="event.stopPropagation()"
|
onclick="event.stopPropagation()"
|
||||||
>
|
>
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</.link>
|
</.link>
|
||||||
<span class="text-red-600 dark:text-red-400">*</span>
|
<span class="text-sweet-salmon-600 dark:text-sweet-salmon-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -123,16 +123,16 @@ defmodule ToweropsWeb.UserResetPasswordLive do
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<%= if @password_breach_count && @password_breach_count > 0 do %>
|
<%= if @password_breach_count && @password_breach_count > 0 do %>
|
||||||
<div class="mt-2 rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
|
<div class="mt-2 rounded-md bg-wheat-50 dark:bg-wheat-900/20 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-yellow-400" />
|
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-wheat-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
<h3 class="text-sm font-medium text-wheat-800 dark:text-wheat-200">
|
||||||
Password found in data breaches
|
Password found in data breaches
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
<div class="mt-2 text-sm text-wheat-700 dark:text-wheat-300">
|
||||||
<p>
|
<p>
|
||||||
This password has appeared {@password_breach_count}
|
This password has appeared {@password_breach_count}
|
||||||
{if @password_breach_count == 1, do: "time", else: "times"} in known data breaches. We strongly recommend choosing a different,
|
{if @password_breach_count == 1, do: "time", else: "times"} in known data breaches. We strongly recommend choosing a different,
|
||||||
|
|
@ -161,7 +161,7 @@ defmodule ToweropsWeb.UserResetPasswordLive do
|
||||||
<div class="mt-6 text-center">
|
<div class="mt-6 text-center">
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/users/log-in"}
|
navigate={~p"/users/log-in"}
|
||||||
class="text-sm font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-sm font-semibold text-cerulean-600 hover:text-cerulean-700 dark:text-cerulean-400 dark:hover:text-cerulean-300"
|
||||||
>
|
>
|
||||||
Back to log in
|
Back to log in
|
||||||
</.link>
|
</.link>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue