Updated all references to use the more technically accurate term 'ICMP Latency' instead of 'Ping Latency' in: - Graph titles and labels - HTML comments - Test assertions
700 lines
34 KiB
Text
700 lines
34 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_scope={@current_scope}
|
|
current_organization={@current_organization}
|
|
active_page="devices"
|
|
>
|
|
<div class="mb-6">
|
|
<!-- Breadcrumbs -->
|
|
<nav class="flex mb-4" aria-label="Breadcrumb">
|
|
<ol class="inline-flex items-center space-x-1 text-sm text-zinc-600 dark:text-zinc-400">
|
|
<li class="inline-flex items-center">
|
|
<.link
|
|
navigate={~p"/orgs/#{@current_organization.slug}/sites/#{@device.site.id}"}
|
|
class="hover:text-zinc-900 dark:hover:text-zinc-200"
|
|
>
|
|
{@device.site.name}
|
|
</.link>
|
|
</li>
|
|
<li aria-current="page">
|
|
<div class="flex items-center">
|
|
<.icon name="hero-chevron-right" class="h-4 w-4 mx-1" />
|
|
<span class="text-zinc-900 dark:text-zinc-100 font-medium">{@device.name}</span>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">{@device.name}</h1>
|
|
<p class="text-sm text-zinc-600 dark:text-zinc-400 font-mono">{@device.ip_address}</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<.button navigate={~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/edit"}>
|
|
<.icon name="hero-pencil" class="h-4 w-4" /> Edit
|
|
</.button>
|
|
</div>
|
|
</div>
|
|
<!-- Tabs -->
|
|
<div class="border-b border-zinc-200 dark:border-zinc-700">
|
|
<nav class="-mb-px flex space-x-8">
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}?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-zinc-500 hover:text-zinc-700 hover:border-zinc-300 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
end
|
|
]}
|
|
>
|
|
Overview
|
|
</.link>
|
|
|
|
<%= if @snmp_device do %>
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}?tab=ports"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "ports" do
|
|
"border-blue-500 text-blue-600 dark:text-blue-400"
|
|
else
|
|
"border-transparent text-zinc-500 hover:text-zinc-700 hover:border-zinc-300 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
end
|
|
]}
|
|
>
|
|
Ports
|
|
</.link>
|
|
<% end %>
|
|
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}?tab=neighbors"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "neighbors" do
|
|
"border-blue-500 text-blue-600 dark:text-blue-400"
|
|
else
|
|
"border-transparent text-zinc-500 hover:text-zinc-700 hover:border-zinc-300 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
end
|
|
]}
|
|
>
|
|
Neighbors
|
|
</.link>
|
|
|
|
<.link
|
|
patch={~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}?tab=logs"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "logs" do
|
|
"border-blue-500 text-blue-600 dark:text-blue-400"
|
|
else
|
|
"border-transparent text-zinc-500 hover:text-zinc-700 hover:border-zinc-300 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
end
|
|
]}
|
|
>
|
|
Logs
|
|
</.link>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
<!-- Tab Content -->
|
|
<div class="mt-6">
|
|
<%= case @active_tab do %>
|
|
<% "overview" -> %>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
|
<!-- Left Column: Device Information & Traffic -->
|
|
<div class="space-y-6">
|
|
<!-- Device Information -->
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Device Information
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= if @snmp_device do %>
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">System Name</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{@snmp_device.sys_name || @device.name}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Resolved IP</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100 font-mono">
|
|
{@device.ip_address}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Hardware</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{@snmp_device.model || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Operating System</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{if @snmp_device.manufacturer && @snmp_device.firmware_version do
|
|
"#{@snmp_device.manufacturer} #{@snmp_device.firmware_version}"
|
|
else
|
|
@snmp_device.sys_descr || "N/A"
|
|
end}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Object ID</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100 font-mono">
|
|
{@snmp_device.sys_object_id || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Contact</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{@snmp_device.sys_contact || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Location</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{@snmp_device.sys_location || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Uptime</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{format_uptime(@snmp_device.sys_uptime)}
|
|
</dd>
|
|
</div>
|
|
<% else %>
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Name</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{@device.name}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">IP Address</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100 font-mono">
|
|
{@device.ip_address}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Device Added</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{format_device_age(@device.inserted_at)}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">Last Discovered</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{if @device.last_discovery_at do
|
|
format_device_age(@device.last_discovery_at)
|
|
else
|
|
"Never"
|
|
end}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<!-- Overall Traffic Chart -->
|
|
<%= if @traffic_chart_data do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/traffic"
|
|
}
|
|
class="block px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-750 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Overall Traffic
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-zinc-400" />
|
|
</div>
|
|
</.link>
|
|
<div class="p-4">
|
|
<div
|
|
id="traffic-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@traffic_chart_data}
|
|
data-unit="bps"
|
|
data-auto-scale="true"
|
|
data-show-zero-line="true"
|
|
style="height: 300px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- ICMP Latency Chart -->
|
|
<%= if @latency_chart_data do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/latency"
|
|
}
|
|
class="block px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-750 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
ICMP Latency
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-zinc-400" />
|
|
</div>
|
|
</.link>
|
|
<div class="p-4">
|
|
<div
|
|
id="latency-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@latency_chart_data}
|
|
data-unit="ms"
|
|
data-auto-scale="true"
|
|
style="height: 300px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<!-- Right Column: Graphs and Metrics -->
|
|
<div class="space-y-6">
|
|
<!-- CPU Usage Chart -->
|
|
<%= if @cpu_chart_data do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/processors"
|
|
}
|
|
class="block px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-750 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Processors
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-zinc-400" />
|
|
</div>
|
|
</.link>
|
|
<div class="p-4">
|
|
<div
|
|
id="cpu-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@cpu_chart_data}
|
|
style="height: 300px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Memory Usage Chart -->
|
|
<%= if @memory_chart_data do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/memory"
|
|
}
|
|
class="block px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-750 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Memory Usage
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-zinc-400" />
|
|
</div>
|
|
</.link>
|
|
<div class="p-4">
|
|
<div
|
|
id="memory-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@memory_chart_data}
|
|
style="height: 300px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Storage Usage -->
|
|
<%= if @storage_sensors && length(@storage_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Storage Usage
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for sensor <- @storage_sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/storage"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
<%= if sensor.latest_reading do %>
|
|
{format_sensor_value(
|
|
sensor.latest_reading.value,
|
|
sensor.sensor_divisor
|
|
)}
|
|
{sensor.sensor_unit}
|
|
<% else %>
|
|
N/A
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Temperature Sensors -->
|
|
<%= if @temperature_sensors && length(@temperature_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Temperature
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for sensor <- @temperature_sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/temperature"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
<%= if sensor.latest_reading do %>
|
|
{format_sensor_value(
|
|
sensor.latest_reading.value,
|
|
sensor.sensor_divisor
|
|
)}
|
|
{sensor.sensor_unit}
|
|
<% else %>
|
|
N/A
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Voltage Sensors -->
|
|
<%= if @voltage_sensors && length(@voltage_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">Voltage</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for sensor <- @voltage_sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-zinc-100 dark:border-zinc-800">
|
|
<dt class="text-sm text-zinc-600 dark:text-zinc-400">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/voltage"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
<%= if sensor.latest_reading do %>
|
|
{format_sensor_value(
|
|
sensor.latest_reading.value,
|
|
sensor.sensor_divisor
|
|
)}
|
|
{sensor.sensor_unit}
|
|
<% else %>
|
|
N/A
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% "ports" -> %>
|
|
<%= if @snmp_interfaces && length(@snmp_interfaces) > 0 do %>
|
|
<div class="space-y-6">
|
|
<%= for {category, interfaces} <- @interfaces_by_type do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
{category} Interfaces
|
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400">
|
|
({length(interfaces)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
|
|
<thead class="bg-zinc-50 dark:bg-zinc-900">
|
|
<tr class="text-xs text-zinc-600 dark:text-zinc-400">
|
|
<th class="px-4 py-3 text-left font-medium">#</th>
|
|
<th class="px-4 py-3 text-left font-medium">Name</th>
|
|
<th class="px-4 py-3 text-left font-medium">Status</th>
|
|
<th class="px-4 py-3 text-left font-medium">Speed</th>
|
|
<th class="px-4 py-3 text-left font-medium">MAC</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
|
|
<%= for interface <- interfaces do %>
|
|
<tr class="text-sm hover:bg-zinc-50 dark:hover:bg-zinc-900">
|
|
<td class="px-4 py-3 text-zinc-900 dark:text-zinc-100">
|
|
{interface.if_index}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{@device.id}/graph/traffic?interface_id=#{interface.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400"
|
|
>
|
|
<div class="font-medium text-zinc-900 dark:text-zinc-100 hover:underline">
|
|
{interface.if_name || interface.if_descr}
|
|
</div>
|
|
<%= if interface.if_alias do %>
|
|
<div class="text-xs text-zinc-500 dark:text-zinc-400">
|
|
{interface.if_alias}
|
|
</div>
|
|
<% end %>
|
|
</.link>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
|
interface.if_oper_status == "up" &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
|
interface.if_oper_status == "down" &&
|
|
"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
|
|
interface.if_oper_status not in ["up", "down"] &&
|
|
"bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200"
|
|
]}>
|
|
{String.upcase(interface.if_oper_status || "unknown")}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-zinc-900 dark:text-zinc-100">
|
|
{format_speed(interface.if_speed)}
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-xs text-zinc-600 dark:text-zinc-400">
|
|
{interface.if_phys_address || "-"}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<div class="text-center py-12">
|
|
<p class="text-zinc-500 dark:text-zinc-400">No interfaces discovered</p>
|
|
</div>
|
|
<% end %>
|
|
<% "neighbors" -> %>
|
|
<%= if @neighbors && length(@neighbors) > 0 do %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
Discovered Neighbors
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
|
|
<thead class="bg-zinc-50 dark:bg-zinc-900">
|
|
<tr class="text-xs text-zinc-600 dark:text-zinc-400">
|
|
<th class="px-4 py-3 text-left font-medium">Protocol</th>
|
|
<th class="px-4 py-3 text-left font-medium">Local Port</th>
|
|
<th class="px-4 py-3 text-left font-medium">Remote Device</th>
|
|
<th class="px-4 py-3 text-left font-medium">Remote Port</th>
|
|
<th class="px-4 py-3 text-left font-medium">Platform</th>
|
|
<th class="px-4 py-3 text-left font-medium">Capabilities</th>
|
|
<th class="px-4 py-3 text-left font-medium">Last Seen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
|
|
<%= for neighbor <- @neighbors do %>
|
|
<tr class="text-sm hover:bg-zinc-50 dark:hover:bg-zinc-900">
|
|
<td class="px-4 py-3">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium uppercase",
|
|
neighbor.protocol == "lldp" &&
|
|
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
|
neighbor.protocol == "cdp" &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
|
]}>
|
|
{neighbor.protocol}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-zinc-900 dark:text-zinc-100">
|
|
{neighbor.interface.if_name || neighbor.interface.if_descr}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<%= if neighbor.matched_device do %>
|
|
<.link
|
|
navigate={
|
|
~p"/orgs/#{@current_organization.slug}/devices/#{neighbor.matched_device.id}"
|
|
}
|
|
class="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{neighbor.matched_device.name}
|
|
</.link>
|
|
<% else %>
|
|
<div class="text-zinc-900 dark:text-zinc-100">
|
|
<%= if neighbor.remote_system_name do %>
|
|
{neighbor.remote_system_name |> String.split("/") |> List.first()}
|
|
<% else %>
|
|
Unknown
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="text-zinc-900 dark:text-zinc-100">
|
|
{neighbor.remote_port_id || "-"}
|
|
</div>
|
|
<%= if !neighbor.matched_device && neighbor.remote_chassis_id do %>
|
|
<div class="text-xs font-mono text-zinc-500 dark:text-zinc-400">
|
|
{neighbor.remote_chassis_id}
|
|
</div>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-zinc-600 dark:text-zinc-400">
|
|
{neighbor.remote_platform || "-"}
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-zinc-600 dark:text-zinc-400">
|
|
<%= if neighbor.remote_capabilities && length(neighbor.remote_capabilities) > 0 do %>
|
|
{Enum.join(neighbor.remote_capabilities, ", ")}
|
|
<% else %>
|
|
-
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-zinc-500 dark:text-zinc-400">
|
|
{time_ago(neighbor.last_discovered_at)}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-server-stack" class="mx-auto h-12 w-12 text-zinc-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
No neighbors discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
|
This device doesn't have any LLDP or CDP neighbors, or neighbor discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% "logs" -> %>
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
|
|
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">Event Logs</h3>
|
|
<p class="text-xs text-zinc-600 dark:text-zinc-400 mt-1">
|
|
Showing the most recent 100 events
|
|
</p>
|
|
</div>
|
|
<%= if Enum.any?(@events) do %>
|
|
<div class="divide-y divide-zinc-200 dark:divide-zinc-700">
|
|
<%= for event <- @events do %>
|
|
<div class="px-4 py-3 hover:bg-zinc-50 dark:hover:bg-zinc-750">
|
|
<div class="flex items-start gap-3">
|
|
<!-- Severity Icon -->
|
|
<div class={[
|
|
"flex-shrink-0 mt-0.5",
|
|
event.severity == "info" && "text-blue-500",
|
|
event.severity == "warning" && "text-yellow-500",
|
|
event.severity == "critical" && "text-red-500"
|
|
]}>
|
|
<%= if event.severity == "info" do %>
|
|
<.icon name="hero-information-circle" class="w-5 h-5" />
|
|
<% end %>
|
|
<%= if event.severity == "warning" do %>
|
|
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
|
|
<% end %>
|
|
<%= if event.severity == "critical" do %>
|
|
<.icon name="hero-x-circle" class="w-5 h-5" />
|
|
<% end %>
|
|
</div>
|
|
<!-- Event Details -->
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-start justify-between gap-2">
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
|
{event.message}
|
|
</p>
|
|
<div class="mt-1 flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-400">
|
|
<span>{format_event_type(event.event_type)}</span>
|
|
<span>•</span>
|
|
<span class="capitalize">{event.severity}</span>
|
|
</div>
|
|
</div>
|
|
<time class="flex-shrink-0 text-xs text-zinc-500 dark:text-zinc-400">
|
|
{time_ago(event.occurred_at)}
|
|
</time>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<div class="p-8 text-center">
|
|
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-zinc-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100">
|
|
No events yet
|
|
</h3>
|
|
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
|
Events will appear here as interface states change, speeds change, or other monitored changes occur.
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% _ -> %>
|
|
<div class="text-center py-12">
|
|
<p class="text-zinc-500 dark:text-zinc-400">Tab not found</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</Layouts.authenticated>
|