1589 lines
79 KiB
Text
1589 lines
79 KiB
Text
<Layouts.authenticated
|
|
flash={@flash}
|
|
current_scope={@current_scope}
|
|
active_page="devices"
|
|
>
|
|
<div class="mb-6 -mt-2">
|
|
<!-- Breadcrumbs -->
|
|
<nav class="flex mb-4" aria-label="Breadcrumb">
|
|
<ol class="inline-flex items-center space-x-1 text-sm text-gray-600 dark:text-gray-400">
|
|
<li class="inline-flex items-center">
|
|
<.link
|
|
navigate={~p"/sites/#{@device.site.id}"}
|
|
class="hover:text-gray-900 dark:hover:text-white"
|
|
>
|
|
{@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-gray-900 dark:text-white 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-gray-900 dark:text-white">{@device.name}</h1>
|
|
<div class="flex items-center gap-3 mt-0.5">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
|
{@device.ip_address}
|
|
</span>
|
|
<span class="text-gray-300 dark:text-gray-600">•</span>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
|
Polled by:
|
|
</span>
|
|
<%= if @agent_info.type == :cloud do %>
|
|
<span class="inline-flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400">
|
|
<.icon name="hero-cloud" class="h-3.5 w-3.5" /> Cloud
|
|
</span>
|
|
<% else %>
|
|
<span class="inline-flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
|
|
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
|
{@agent_info.name}
|
|
</span>
|
|
<%= if @agent_info.last_seen_at do %>
|
|
<span class="text-xs text-gray-400 dark:text-gray-500">
|
|
({format_relative_time(@agent_info.last_seen_at)})
|
|
</span>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<.button navigate={~p"/devices/#{@device.id}/edit"}>
|
|
<.icon name="hero-pencil" class="h-4 w-4" /> Edit
|
|
</.button>
|
|
</div>
|
|
</div>
|
|
<!-- Tabs -->
|
|
<div class="border-b border-gray-200 dark:border-white/10">
|
|
<nav class="-mb-px flex space-x-8">
|
|
<.link
|
|
patch={~p"/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-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
|
end
|
|
]}
|
|
>
|
|
Overview
|
|
</.link>
|
|
|
|
<%= if @snmp_device do %>
|
|
<.link
|
|
patch={~p"/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-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
|
end
|
|
]}
|
|
>
|
|
Ports
|
|
</.link>
|
|
<% end %>
|
|
|
|
<.link
|
|
patch={~p"/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-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
|
end
|
|
]}
|
|
>
|
|
Neighbors
|
|
</.link>
|
|
|
|
<%= if @arp_entries && length(@arp_entries) > 0 do %>
|
|
<.link
|
|
patch={~p"/devices/#{@device.id}?tab=arp"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "arp" 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"
|
|
end
|
|
]}
|
|
>
|
|
ARP Table
|
|
</.link>
|
|
<% end %>
|
|
|
|
<%= if @mac_addresses && length(@mac_addresses) > 0 do %>
|
|
<.link
|
|
patch={~p"/devices/#{@device.id}?tab=mac"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "mac" 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"
|
|
end
|
|
]}
|
|
>
|
|
MAC Table
|
|
</.link>
|
|
<% end %>
|
|
|
|
<%= if @vlans && length(@vlans) > 0 do %>
|
|
<.link
|
|
patch={~p"/devices/#{@device.id}?tab=vlans"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "vlans" 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"
|
|
end
|
|
]}
|
|
>
|
|
VLANs
|
|
</.link>
|
|
<% end %>
|
|
|
|
<%= if length(@ipv4_addresses) > 0 || length(@ipv6_addresses) > 0 do %>
|
|
<.link
|
|
patch={~p"/devices/#{@device.id}?tab=ip_addresses"}
|
|
class={[
|
|
"whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm",
|
|
if @active_tab == "ip_addresses" 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"
|
|
end
|
|
]}
|
|
>
|
|
IP Addresses
|
|
</.link>
|
|
<% end %>
|
|
|
|
<.link
|
|
patch={~p"/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-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
|
|
end
|
|
]}
|
|
>
|
|
Logs
|
|
</.link>
|
|
|
|
<%= if @snmp_device && @snmp_device.raw_discovery_data do %>
|
|
<.link
|
|
patch={~p"/devices/#{@device.id}?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"
|
|
end
|
|
]}
|
|
>
|
|
Debug
|
|
</.link>
|
|
<% end %>
|
|
</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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
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-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">System Name</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{@snmp_device.sys_name || @device.name}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Resolved IP</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white font-mono">
|
|
{@device.ip_address}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Hardware</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{@snmp_device.model || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Operating System</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{cond do
|
|
@snmp_device.manufacturer && @snmp_device.firmware_version ->
|
|
"#{@snmp_device.manufacturer} #{@snmp_device.firmware_version}"
|
|
|
|
@snmp_device.manufacturer ->
|
|
@snmp_device.manufacturer
|
|
|
|
true ->
|
|
@snmp_device.sys_descr || "N/A"
|
|
end}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Object ID</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white font-mono">
|
|
{@snmp_device.sys_object_id || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Contact</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{@snmp_device.sys_contact || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Location</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{@snmp_device.sys_location || "N/A"}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Uptime</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{format_uptime(@snmp_device.sys_uptime)}
|
|
</dd>
|
|
</div>
|
|
<% else %>
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Name</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{@device.name}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">IP Address</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white font-mono">
|
|
{@device.ip_address}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Device Added</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{format_device_age(@device.inserted_at)}
|
|
</dd>
|
|
</div>
|
|
|
|
<div class="flex justify-between py-1.5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">Last Discovered</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<.link
|
|
navigate={~p"/devices/#{@device.id}/graph/traffic"}
|
|
class="block px-4 py-3 border-b border-gray-200 dark:border-white/10 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Overall Traffic
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<.link
|
|
navigate={~p"/devices/#{@device.id}/graph/latency"}
|
|
class="block px-4 py-3 border-b border-gray-200 dark:border-white/10 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
ICMP Latency
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-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 / Processors -->
|
|
<%= if @processor_chart_data || (@processors && length(@processors) > 0) do %>
|
|
<% processor_count = if @processors, do: length(@processors), else: 0
|
|
|
|
avg_load =
|
|
if processor_count > 0 do
|
|
loads = @processors |> Enum.map(& &1.load_percent) |> Enum.reject(&is_nil/1)
|
|
if length(loads) > 0, do: Enum.sum(loads) / length(loads), else: nil
|
|
else
|
|
nil
|
|
end %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<.link
|
|
navigate={~p"/devices/#{@device.id}/graph/processors"}
|
|
class="block px-4 py-3 border-b border-gray-200 dark:border-white/10 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
<%= if processor_count > 0 do %>
|
|
{processor_count} {if processor_count == 1,
|
|
do: "Processor",
|
|
else: "Processors"}
|
|
<% else %>
|
|
Processors
|
|
<% end %>
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-400" />
|
|
</div>
|
|
</.link>
|
|
<!-- Show chart if we have time-series data -->
|
|
<%= if @processor_chart_data do %>
|
|
<div class="p-4">
|
|
<div
|
|
id="processor-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@processor_chart_data}
|
|
data-unit="%"
|
|
style="height: 200px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Show combined processor load -->
|
|
<%= if avg_load do %>
|
|
<div class={[
|
|
"p-4",
|
|
@processor_chart_data && "border-t border-gray-200 dark:border-white/10"
|
|
]}>
|
|
<div class="flex items-center justify-between gap-4">
|
|
<span class="text-sm text-gray-700 dark:text-gray-300">
|
|
Average Load
|
|
</span>
|
|
<div class="flex items-center gap-3 flex-1 max-w-md">
|
|
<div class="flex-1 bg-gray-200 rounded-full h-3 dark:bg-gray-700">
|
|
<div
|
|
class={[
|
|
"h-3 rounded-full transition-all",
|
|
avg_load < 50 && "bg-green-500",
|
|
avg_load >= 50 && avg_load < 80 && "bg-yellow-500",
|
|
avg_load >= 80 && "bg-red-500"
|
|
]}
|
|
style={"width: #{min(avg_load, 100)}%"}
|
|
>
|
|
</div>
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white w-12 text-right">
|
|
{Float.round(avg_load, 0)}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
<!-- Memory Usage Chart -->
|
|
<%= if @memory_chart_data do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<.link
|
|
navigate={~p"/devices/#{@device.id}/graph/memory"}
|
|
class="block px-4 py-3 border-b border-gray-200 dark:border-white/10 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Memory Usage
|
|
</h3>
|
|
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
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-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/storage?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
<%= 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 %>
|
|
<!-- Physical Storage Volumes (from HOST-RESOURCES-MIB) -->
|
|
<%= if @storage && length(@storage) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Storage Volumes
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@storage)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<!-- Storage Volume Chart -->
|
|
<%= if @storage_volume_chart_data do %>
|
|
<div class="p-4 border-b border-gray-200 dark:border-white/10">
|
|
<div
|
|
id="storage-volume-chart"
|
|
phx-hook="SensorChart"
|
|
data-chart={@storage_volume_chart_data}
|
|
data-unit="%"
|
|
style="height: 200px;"
|
|
>
|
|
<canvas></canvas>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<div class="p-4 space-y-4">
|
|
<%= for storage <- @storage do %>
|
|
<% usage_percent =
|
|
if storage.total_bytes && storage.total_bytes > 0 do
|
|
Float.round(storage.used_bytes / storage.total_bytes * 100, 1)
|
|
else
|
|
0.0
|
|
end %>
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/storage_volume?storage_id=#{storage.id}"
|
|
}
|
|
class="block hover:bg-gray-50 dark:hover:bg-gray-700/50 -mx-2 px-2 py-1 rounded transition-colors"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-sm text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:underline">
|
|
{storage.description || "Storage #{storage.storage_index}"}
|
|
</span>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{format_bytes(storage.used_bytes)} / {format_bytes(storage.total_bytes)}
|
|
</span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex-1 bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
|
<div
|
|
class={[
|
|
"h-2 rounded-full transition-all",
|
|
usage_percent < 70 && "bg-green-500",
|
|
usage_percent >= 70 && usage_percent < 90 && "bg-yellow-500",
|
|
usage_percent >= 90 && "bg-red-500"
|
|
]}
|
|
style={"width: #{min(usage_percent, 100)}%"}
|
|
>
|
|
</div>
|
|
</div>
|
|
<span class="text-xs font-medium text-gray-600 dark:text-gray-400 w-12 text-right">
|
|
{usage_percent}%
|
|
</span>
|
|
</div>
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
{String.replace(storage.storage_type || "other", "_", " ")
|
|
|> String.capitalize()}
|
|
</div>
|
|
</.link>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Temperature Sensors -->
|
|
<%= if @temperature_sensors && length(@temperature_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
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-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/temperature?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
<%= if sensor.latest_reading do %>
|
|
{format_sensor_value(
|
|
sensor.latest_reading.value,
|
|
sensor.sensor_divisor
|
|
)}
|
|
{sensor.sensor_unit || "°C"}
|
|
<% 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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">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-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/voltage?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
<%= 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 %>
|
|
<!-- SFP Transceivers -->
|
|
<%= if @grouped_transceivers && length(@grouped_transceivers) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Transceivers
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for {_transceiver_name, sensors} <- @grouped_transceivers do %>
|
|
<%= for sensor <- sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/dbm?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white font-mono">
|
|
<%= 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 %>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Count Sensors (DHCP Leases, Connections, etc.) -->
|
|
<%= if @count_sensors && length(@count_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Counters</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for sensor <- @count_sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/#{sensor.sensor_type}?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
<%= if sensor.latest_reading do %>
|
|
{trunc(sensor.latest_reading.value)}
|
|
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
|
|
{sensor.sensor_unit}
|
|
<% end %>
|
|
<% else %>
|
|
N/A
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- Wireless Sensors (Frequency, Power, CCQ, etc.) -->
|
|
<%= if @wireless_sensors && length(@wireless_sensors) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Wireless</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<dl class="space-y-1">
|
|
<%= for sensor <- @wireless_sensors do %>
|
|
<div class="flex justify-between py-1.5 border-b border-gray-100 dark:border-white/5">
|
|
<dt class="text-sm text-gray-600 dark:text-gray-400">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/wireless?sensor_id=#{sensor.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400 hover:underline"
|
|
>
|
|
{sensor.sensor_descr}
|
|
</.link>
|
|
</dt>
|
|
<dd class="text-sm font-medium text-gray-900 dark:text-white">
|
|
<%= if sensor.latest_reading do %>
|
|
<%= if sensor.sensor_type in ["clients", "ccq", "utilization"] do %>
|
|
{trunc(sensor.latest_reading.value)}
|
|
<% else %>
|
|
{Float.round(sensor.latest_reading.value, 1)}
|
|
<% end %>
|
|
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
|
|
{sensor.sensor_unit}
|
|
<% end %>
|
|
<% 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-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
{category} Interfaces
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(interfaces)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-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">IP Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">MAC</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for interface <- interfaces do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 text-gray-900 dark:text-white">
|
|
{interface.if_index}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/traffic?interface_id=#{interface.id}"
|
|
}
|
|
class="hover:text-blue-600 dark:hover:text-blue-400"
|
|
>
|
|
<div class="font-medium text-gray-900 dark:text-white hover:underline">
|
|
{interface.if_name || interface.if_descr}
|
|
</div>
|
|
<%= if interface.if_alias do %>
|
|
<div class="text-xs text-gray-500 dark:text-gray-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-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
]}>
|
|
{String.upcase(interface.if_oper_status || "unknown")}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-900 dark:text-white">
|
|
{format_speed(interface.if_speed)}
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-xs text-gray-600 dark:text-gray-400">
|
|
<% interface_ips =
|
|
Map.get(@ip_addresses_by_interface, interface.id, []) %>
|
|
<%= if Enum.empty?(interface_ips) do %>
|
|
<span class="text-gray-400">-</span>
|
|
<% else %>
|
|
<div class="space-y-0.5">
|
|
<%= for ip <- interface_ips do %>
|
|
<div>
|
|
{ip.ip_address}
|
|
<%= if ip.prefix_length do %>
|
|
/{ip.prefix_length}
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-xs text-gray-600 dark:text-gray-400">
|
|
{interface.if_phys_address || "-"}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<div class="text-center py-12">
|
|
<p class="text-gray-500 dark:text-gray-400">No interfaces discovered</p>
|
|
</div>
|
|
<% end %>
|
|
<% "neighbors" -> %>
|
|
<%= if @neighbors && length(@neighbors) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Discovered Neighbors
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-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">IP Address</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-gray-200 dark:divide-white/10">
|
|
<%= for neighbor <- @neighbors do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<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">
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/traffic?interface_id=#{neighbor.interface.id}"
|
|
}
|
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{neighbor.interface.if_name || neighbor.interface.if_descr}
|
|
</.link>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<%= if neighbor.matched_device do %>
|
|
<.link
|
|
navigate={~p"/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-gray-900 dark:text-white">
|
|
<%= 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-gray-900 dark:text-white">
|
|
{neighbor.remote_port_id || "-"}
|
|
</div>
|
|
<%= if !neighbor.matched_device && neighbor.remote_chassis_id do %>
|
|
<div class="text-xs font-mono text-gray-500 dark:text-gray-400">
|
|
{neighbor.remote_chassis_id}
|
|
</div>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-sm text-gray-900 dark:text-white">
|
|
{neighbor.remote_address || "-"}
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-gray-600 dark:text-gray-400">
|
|
{neighbor.remote_platform || "-"}
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-gray-600 dark:text-gray-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-gray-500 dark:text-gray-400">
|
|
{time_ago(neighbor.last_discovered_at)}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-server-stack" class="mx-auto h-12 w-12 text-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No neighbors discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
This device doesn't have any LLDP or CDP neighbors, or neighbor discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% "arp" -> %>
|
|
<%= if @arp_entries && length(@arp_entries) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
ARP Table
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@arp_entries)} entries)
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
|
<th class="px-4 py-3 text-left font-medium">IP Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">MAC Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">Interface</th>
|
|
<th class="px-4 py-3 text-left font-medium">Type</th>
|
|
<th class="px-4 py-3 text-left font-medium">Last Seen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for arp <- @arp_entries do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 font-mono text-gray-900 dark:text-white">
|
|
{arp.ip_address}
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-xs text-gray-600 dark:text-gray-400">
|
|
{arp.mac_address}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
<%= if arp.interface do %>
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/traffic?interface_id=#{arp.interface.id}"
|
|
}
|
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{arp.interface.if_name || arp.interface.if_descr}
|
|
</.link>
|
|
<% else %>
|
|
<span class="text-gray-400">ifIndex {arp.if_index}</span>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
|
arp.entry_type == "dynamic" &&
|
|
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
|
arp.entry_type == "static" &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
|
arp.entry_type not in ["dynamic", "static"] &&
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
]}>
|
|
{arp.entry_type || "unknown"}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
|
|
{time_ago(arp.last_seen_at)}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-table-cells" class="mx-auto h-12 w-12 text-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No ARP entries discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
This device doesn't have any ARP entries, or ARP discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% "mac" -> %>
|
|
<%= if @mac_addresses && length(@mac_addresses) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
MAC Address Table
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@mac_addresses)} entries)
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
|
<th class="px-4 py-3 text-left font-medium">MAC Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">VLAN</th>
|
|
<th class="px-4 py-3 text-left font-medium">Interface</th>
|
|
<th class="px-4 py-3 text-left font-medium">Status</th>
|
|
<th class="px-4 py-3 text-left font-medium">Last Seen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for mac <- @mac_addresses do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 font-mono text-xs text-gray-900 dark:text-white">
|
|
{mac.mac_address}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
<%= if mac.vlan_id do %>
|
|
{mac.vlan_id}
|
|
<% else %>
|
|
<span class="text-gray-400">-</span>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
<%= if mac.interface do %>
|
|
<.link
|
|
navigate={
|
|
~p"/devices/#{@device.id}/graph/traffic?interface_id=#{mac.interface.id}"
|
|
}
|
|
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
{mac.interface.if_name || mac.interface.if_descr}
|
|
</.link>
|
|
<% else %>
|
|
<span class="text-gray-400">Port {mac.port_index}</span>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
|
mac.entry_status == "learned" &&
|
|
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
|
|
mac.entry_status == "self" &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
|
mac.entry_status == "mgmt" &&
|
|
"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200",
|
|
mac.entry_status not in ["learned", "self", "mgmt"] &&
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
]}>
|
|
{mac.entry_status || "unknown"}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
|
|
{time_ago(mac.last_seen_at)}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-table-cells" class="mx-auto h-12 w-12 text-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No MAC addresses discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
This device doesn't have any MAC forwarding entries, or MAC discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% "vlans" -> %>
|
|
<%= if @vlans && length(@vlans) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Configured VLANs
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@vlans)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
|
<th class="px-4 py-3 text-left font-medium">VLAN ID</th>
|
|
<th class="px-4 py-3 text-left font-medium">Name</th>
|
|
<th class="px-4 py-3 text-left font-medium">Type</th>
|
|
<th class="px-4 py-3 text-left font-medium">Status</th>
|
|
<th class="px-4 py-3 text-left font-medium">Last Checked</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for vlan <- @vlans do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 text-gray-900 dark:text-white font-mono">
|
|
{vlan.vlan_id}
|
|
</td>
|
|
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">
|
|
{vlan.vlan_name || "-"}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
{vlan.vlan_type || "ethernet"}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class={[
|
|
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
|
|
vlan.status == "active" &&
|
|
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
|
|
vlan.status == "suspended" &&
|
|
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
|
|
vlan.status not in ["active", "suspended"] &&
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
]}>
|
|
{String.upcase(vlan.status || "unknown")}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
|
|
{time_ago(vlan.last_checked_at)}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-square-3-stack-3d" class="mx-auto h-12 w-12 text-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No VLANs discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
This device doesn't have any VLANs configured, or VLAN discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% "ip_addresses" -> %>
|
|
<div class="space-y-6">
|
|
<!-- IPv4 Addresses -->
|
|
<%= if length(@ipv4_addresses) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
<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 mr-2">
|
|
IPv4
|
|
</span>
|
|
Addresses
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@ipv4_addresses)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
|
<th class="px-4 py-3 text-left font-medium">IP Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">Subnet</th>
|
|
<th class="px-4 py-3 text-left font-medium">Interface</th>
|
|
<th class="px-4 py-3 text-left font-medium">Primary</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for ip <- @ipv4_addresses do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 text-gray-900 dark:text-white font-mono">
|
|
{ip.ip_address}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400 font-mono">
|
|
<%= if ip.prefix_length do %>
|
|
/{ip.prefix_length}
|
|
<% else %>
|
|
{ip.subnet_mask || "-"}
|
|
<% end %>
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
{(ip.snmp_interface &&
|
|
(ip.snmp_interface.if_name || ip.snmp_interface.if_descr)) || "-"}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<%= if ip.is_primary do %>
|
|
<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">
|
|
Primary
|
|
</span>
|
|
<% else %>
|
|
<span class="text-gray-400 dark:text-gray-500">-</span>
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- IPv6 Addresses -->
|
|
<%= if length(@ipv6_addresses) > 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
<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 mr-2">
|
|
IPv6
|
|
</span>
|
|
Addresses
|
|
<span class="ml-2 text-xs font-normal text-gray-500 dark:text-gray-400">
|
|
({length(@ipv6_addresses)})
|
|
</span>
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr class="text-xs text-gray-600 dark:text-gray-400">
|
|
<th class="px-4 py-3 text-left font-medium">IP Address</th>
|
|
<th class="px-4 py-3 text-left font-medium">Prefix</th>
|
|
<th class="px-4 py-3 text-left font-medium">Interface</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for ip <- @ipv6_addresses do %>
|
|
<tr class="text-sm hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td class="px-4 py-3 text-gray-900 dark:text-white font-mono text-xs">
|
|
{ip.ip_address}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400 font-mono">
|
|
/{ip.prefix_length || "-"}
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
|
{(ip.snmp_interface &&
|
|
(ip.snmp_interface.if_name || ip.snmp_interface.if_descr)) || "-"}
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<!-- No IP addresses found -->
|
|
<%= if length(@ipv4_addresses) == 0 && length(@ipv6_addresses) == 0 do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-12">
|
|
<div class="text-center">
|
|
<.icon name="hero-globe-alt" class="mx-auto h-12 w-12 text-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No IP addresses discovered
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
This device doesn't have any IP addresses discovered, or IP discovery hasn't run yet.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% "logs" -> %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Event Logs</h3>
|
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
Showing the most recent 100 events
|
|
</p>
|
|
</div>
|
|
<%= if Enum.any?(@events) do %>
|
|
<div class="divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for event <- @events do %>
|
|
<div class="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<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-gray-900 dark:text-white">
|
|
{event.message}
|
|
</p>
|
|
<div class="mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-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-gray-500 dark:text-gray-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-gray-400" />
|
|
<h3 class="mt-2 text-sm font-semibold text-gray-900 dark:text-white">
|
|
No events yet
|
|
</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
Events will appear here as interface states change, speeds change, or other monitored changes occur.
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% "debug" -> %>
|
|
<div class="bg-white dark:bg-gray-800/50 shadow sm:rounded-lg">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white">
|
|
Raw Discovery Data
|
|
</h3>
|
|
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
|
<p>
|
|
Raw SNMP OID values collected during the last discovery run at
|
|
<%= if @snmp_device.last_discovery_at do %>
|
|
{Calendar.strftime(@snmp_device.last_discovery_at, "%Y-%m-%d %H:%M:%S UTC")}
|
|
<% else %>
|
|
unknown time
|
|
<% end %>.
|
|
</p>
|
|
</div>
|
|
<%= if @snmp_device.raw_discovery_data do %>
|
|
<div class="mt-5">
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-3">
|
|
System OIDs
|
|
</h4>
|
|
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Field
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
OID
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Value
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-800/50 divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for {key, data} <- Map.get(@snmp_device.raw_discovery_data, "system_oids", %{}) do %>
|
|
<tr>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
{key}
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono">
|
|
{data["oid"]}
|
|
</td>
|
|
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
|
<code class="text-xs">{inspect(data["value"])}</code>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<%= if Map.get(@snmp_device.raw_discovery_data, "health_sensor_table") do %>
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mt-6 mb-3">
|
|
Health Sensor Table Walk
|
|
</h4>
|
|
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
OID
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Value
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-800/50 divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for {oid, value} <- Map.get(@snmp_device.raw_discovery_data, "health_sensor_table", %{}) do %>
|
|
<tr>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm font-mono text-gray-500 dark:text-gray-400">
|
|
{oid}
|
|
</td>
|
|
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
|
<code class="text-xs">{inspect(value)}</code>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%= if Map.get(@snmp_device.raw_discovery_data, "discovered_sensors") do %>
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mt-6 mb-3">
|
|
Discovered Sensors
|
|
</h4>
|
|
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-white/10">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
|
<tr>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Type
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Description
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
OID
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
Value
|
|
</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
State
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-800/50 divide-y divide-gray-200 dark:divide-white/10">
|
|
<%= for sensor <- Map.get(@snmp_device.raw_discovery_data, "discovered_sensors", []) do %>
|
|
<tr>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
{sensor["type"]}
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
|
{sensor["descr"]}
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono">
|
|
{sensor["oid"]}
|
|
</td>
|
|
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
|
<code class="text-xs">{sensor["value"]}</code>
|
|
<%= if sensor["unit"] && sensor["unit"] != "" do %>
|
|
<span class="text-gray-500 dark:text-gray-400 ml-1">
|
|
{sensor["unit"]}
|
|
</span>
|
|
<% end %>
|
|
</td>
|
|
<td class="px-3 py-4 text-sm text-gray-900 dark:text-white">
|
|
<%= if sensor["state_descr"] do %>
|
|
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
|
{sensor["state_descr"]}
|
|
</span>
|
|
<% else %>
|
|
<span class="text-gray-400">-</span>
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% end %>
|
|
|
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mt-6 mb-3">
|
|
Copyable Debug Data
|
|
</h4>
|
|
<div class="rounded-lg border border-gray-200 dark:border-white/10">
|
|
<textarea
|
|
readonly
|
|
rows="10"
|
|
class="w-full p-3 font-mono text-xs bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white border-0 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
onclick="this.select()"
|
|
><%= Jason.encode!(@snmp_device.raw_discovery_data, pretty: true) %></textarea>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
|
Click the text box to select all data, then copy with Cmd+C or Ctrl+C
|
|
</p>
|
|
</div>
|
|
<% else %>
|
|
<div class="mt-5 p-4 bg-gray-50 dark:bg-gray-900 rounded-md">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
No raw discovery data available. Run discovery again to collect debug data.
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% _ -> %>
|
|
<div class="text-center py-12">
|
|
<p class="text-gray-500 dark:text-gray-400">Tab not found</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</Layouts.authenticated>
|