towerops/lib/towerops_web/live/agent_live/show.html.heex
2026-02-09 12:44:37 -06:00

348 lines
14 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
active_page="agents"
>
<.header>
{@page_title}
<:subtitle>Agent Details and Health</:subtitle>
<:actions>
<.link
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"
>
<.icon name="hero-pencil" class="h-5 w-5" /> Edit Agent
</.link>
<.link
navigate={~p"/agents"}
class="text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
>
<.icon name="hero-arrow-left" class="h-5 w-5" /> Back to Agents
</.link>
</:actions>
</.header>
<!-- Tab Navigation (Superuser Only) -->
<div
:if={@current_scope.user.is_superuser}
class="mt-6 border-b border-gray-200 dark:border-white/10"
>
<nav class="-mb-px flex space-x-8">
<button
type="button"
phx-click="switch_tab"
phx-value-tab="overview"
class={[
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
if(@active_tab == "overview",
do: "border-blue-500 text-blue-600 dark:text-blue-400",
else:
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
)
]}
>
Overview
</button>
<button
type="button"
phx-click="switch_tab"
phx-value-tab="debug"
class={[
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
if(@active_tab == "debug",
do: "border-blue-500 text-blue-600 dark:text-blue-400",
else:
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
)
]}
>
Debug Logs
</button>
</nav>
</div>
<!-- Debug Tab Content -->
<div :if={@current_scope.user.is_superuser && @active_tab == "debug"} class="mt-6">
<div class={[
"rounded-md p-4 mb-4",
if(@agent_token.allow_remote_debug,
do: "bg-green-50 dark:bg-green-900/20",
else: "bg-gray-50 dark:bg-gray-900/20"
)
]}>
<p class="text-sm font-medium text-gray-900 dark:text-white">
Debug Status: {if @agent_token.allow_remote_debug,
do: "Enabled ✓",
else: "Disabled"}
</p>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
<%= if @agent_token.allow_remote_debug do %>
Verbose logging is enabled. All SNMP data and agent messages are being logged.
<% else %>
Verbose logging is disabled. Enable it in the agent edit page to troubleshoot issues.
<% end %>
</p>
</div>
<div class="space-y-4">
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
View Debug Logs
</h3>
<div class="rounded-md bg-gray-50 dark:bg-gray-900 p-4">
<p class="text-sm mb-2 font-medium text-gray-900 dark:text-white">
Production (kubectl):
</p>
<code class="text-xs text-gray-800 dark:text-gray-200 block">
kubectl logs -n towerops deployment/towerops --tail=100 | grep "agent_token_id: {@agent_token.id}"
</code>
</div>
<div class="rounded-md bg-gray-50 dark:bg-gray-900 p-4">
<p class="text-sm mb-2 font-medium text-gray-900 dark:text-white">
Development (tail):
</p>
<code class="text-xs text-gray-800 dark:text-gray-200 block">
tail -f _build/dev/lib/towerops/priv/log/dev.log | grep "{@agent_token.id}"
</code>
</div>
<div class="mt-4">
<p class="text-sm font-medium mb-2 text-gray-900 dark:text-white">
What Gets Logged:
</p>
<ul class="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
<li>Agent connection/disconnection events</li>
<li>Job submissions (device IDs and job types)</li>
<li>SNMP discovery results (full OID maps)</li>
<li>Polling results (sensor readings, interface stats)</li>
<li>Error messages with full context</li>
<li>Ping submissions</li>
</ul>
</div>
</div>
</div>
<!-- Overview Tab Content -->
<div :if={!@current_scope.user.is_superuser || @active_tab == "overview"}>
<!-- Agent Status Overview -->
<div class="mt-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
<!-- 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="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Status</p>
<% {status, label} = agent_status(@agent_token) %>
<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={"h-1.5 w-1.5 rounded-full #{status_dot_class(status)}"} />
{label}
</span>
</div>
</div>
<.icon name="hero-signal" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
</div>
</div>
<!-- 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="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Device</p>
<p class="mt-2 text-3xl font-semibold text-gray-900 dark:text-white">
{length(@polling_targets)}
</p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{@direct_assignments} direct
</p>
</div>
<.icon name="hero-server" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
</div>
</div>
<!-- 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="flex items-center justify-between">
<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="mt-2 text-lg font-semibold text-gray-900 dark:text-white">
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
</p>
<%= if @agent_token.last_seen_at do %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
</p>
<% end %>
<%= if @agent_token.last_ip do %>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 font-mono">
{@agent_token.last_ip}
</p>
<% end %>
</div>
<.icon name="hero-clock" class="h-8 w-8 text-gray-400 dark:text-gray-500 flex-shrink-0" />
</div>
</div>
<!-- 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="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Uptime</p>
<%= if @agent_token.metadata["uptime_seconds"] do %>
<p class="mt-2 text-lg font-semibold text-gray-900 dark:text-white">
{format_uptime(@agent_token.metadata["uptime_seconds"])}
</p>
<% else %>
<p class="mt-2 text-lg font-semibold text-gray-500 dark:text-gray-400">
Unknown
</p>
<% end %>
</div>
<.icon name="hero-clock" class="h-8 w-8 text-gray-400 dark:text-gray-500" />
</div>
</div>
</div>
<!-- Agent Metadata -->
<%= 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">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Agent Metadata
</h3>
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<%= if @agent_token.metadata["version"] do %>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Version</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white font-mono">
{@agent_token.metadata["version"]}
</dd>
</div>
<% end %>
<%= if @agent_token.metadata["hostname"] do %>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Hostname</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white font-mono">
{@agent_token.metadata["hostname"]}
</dd>
</div>
<% end %>
<%= if @agent_token.metadata["uptime_seconds"] do %>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Uptime</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
{format_uptime(@agent_token.metadata["uptime_seconds"])}
</dd>
</div>
<% end %>
</dl>
</div>
<% end %>
<!-- 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">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Timestamps
</h3>
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Created</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
<.timestamp datetime={@agent_token.inserted_at} timezone={@timezone} />
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Last Updated</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
<.timestamp datetime={@agent_token.updated_at} timezone={@timezone} />
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">
Last Seen (Heartbeat)
</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
</dd>
</div>
<%= if @agent_token.last_ip do %>
<div>
<dt class="text-sm font-medium text-gray-600 dark:text-gray-400">Last IP Address</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-white font-mono">
{@agent_token.last_ip}
</dd>
</div>
<% end %>
</dl>
</div>
<!-- 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="p-6 border-b border-gray-200 dark:border-white/10">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Polling Targets
</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Device that this agent is responsible for polling
</p>
</div>
<%= if @polling_targets == [] do %>
<div class="p-12 text-center">
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />
<h3 class="mt-4 text-sm font-semibold text-gray-900 dark:text-white">
No devices assigned
</h3>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
This agent is not currently polling any device.
</p>
</div>
<% else %>
<div class="divide-y divide-gray-200 dark:divide-white/10">
<%= for device <- @polling_targets do %>
<.link
navigate={~p"/devices/#{device.id}"}
class="block hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<div class="p-6">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3">
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
{device.name}
</p>
<% {source, source_label} = assignment_source(device, @agent_token.id) %>
<span class={"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium #{source_badge_class(source)}"}>
{source_label}
</span>
</div>
<div class="mt-1 flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<span class="flex items-center gap-1">
<.icon name="hero-globe-alt" class="h-4 w-4" />
{device.ip_address}
</span>
<%= if device.site do %>
<span class="flex items-center gap-1">
<.icon name="hero-building-office" class="h-4 w-4" />
{device.site.name}
</span>
<% end %>
</div>
</div>
<.icon
name="hero-chevron-right"
class="h-5 w-5 text-gray-400 dark:text-gray-500"
/>
</div>
</div>
</.link>
<% end %>
</div>
<% end %>
</div>
</div>
<!-- End Overview Tab -->
</Layouts.authenticated>