<.breadcrumb items={
[
%{label: "Dashboard", navigate: ~p"/dashboard"},
%{label: "Devices", navigate: ~p"/devices"}
] ++
if(@device.site,
do: [%{label: @device.site.name, navigate: ~p"/sites/#{@device.site.id}"}],
else: []
) ++
[%{label: @device.name}]
} />
{@device.name}
"bg-green-100 text-green-800 dark:bg-green-950/50 dark:text-green-400 ring-1 ring-green-600/20 dark:ring-green-500/30"
:down ->
"bg-red-100 text-red-800 dark:bg-red-950/50 dark:text-red-400 ring-1 ring-red-600/20 dark:ring-red-500/30"
:unknown ->
"bg-gray-100 text-gray-800 dark:bg-gray-800/50 dark:text-gray-400 ring-1 ring-gray-600/20 dark:ring-gray-500/30"
end
]}>
"bg-green-600 dark:bg-green-500"
:down -> "bg-red-600 dark:bg-red-500"
:unknown -> "bg-gray-500 dark:bg-gray-400"
end
]}>
{case @device.status do
:up -> "Online"
:down -> "Offline"
:unknown -> "Unknown"
end}
{@device.ip_address}
<.icon
name="hero-clipboard-document"
class="h-3 w-3 inline ml-0.5 opacity-0 group-hover/ip:opacity-100 transition-opacity text-gray-400"
/>
·
{t("Agent:")}
<%= if @agent_info.type == :cloud do %>
<.icon name="hero-cloud" class="h-3 w-3" />
<%= if @agent_info.agent_token_id do %>
{t("Cloud")} ({@agent_info.name})
<% else %>
{t("Cloud")}
<% end %>
<% else %>
<%= if @agent_info.is_offline do %>
<.icon name="hero-exclamation-triangle" class="h-3 w-3" />
{@agent_info.name} · Offline
<% else %>
<.icon name="hero-server" class="h-3 w-3" />
{@agent_info.name}
<% end %>
<% end %>
<.button navigate={~p"/devices/#{@device.id}/edit"}>
<.icon name="hero-pencil" class="h-4 w-4" />
{t("Edit")}
<%= if @subscriber_impact && @subscriber_impact.subscriber_count > 0 do %>
<.icon name="hero-users" class="h-5 w-5 text-indigo-600 dark:text-indigo-400" />
{@subscriber_impact.subscriber_count} {if @subscriber_impact.subscriber_count == 1,
do: "subscriber",
else: "subscribers"}
<%= if @can_view_financials && @subscriber_impact.mrr do %>
<.icon
name="hero-currency-dollar"
class="h-5 w-5 text-indigo-600 dark:text-indigo-400"
/>
{format_mrr(@subscriber_impact.mrr)}/mo
<% end %>
<% end %>
<.link
patch={~p"/devices/#{@device.id}?tab=overview"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "overview" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Overview")}
<%= if @snmp_device do %>
<.link
patch={~p"/devices/#{@device.id}?tab=ports"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "ports" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Ports")}
<% end %>
<%= if assigns[:wireless_clients_available] do %>
<.link
patch={~p"/devices/#{@device.id}?tab=wireless"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors inline-flex items-center gap-1.5",
if @active_tab == "wireless" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Wireless")}
{@wireless_client_count}
<% end %>
<%= if assigns[:transceivers_available] do %>
<.link
patch={~p"/devices/#{@device.id}?tab=transceivers"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "transceivers" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Transceivers")}
<% end %>
<%= if assigns[:printer_supplies_available] do %>
<.link
patch={~p"/devices/#{@device.id}?tab=printer_supplies"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "printer_supplies" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Printer Supplies")}
<% end %>
<%= if assigns[:hardware_inventory_available] do %>
<.link
patch={~p"/devices/#{@device.id}?tab=hardware"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "hardware" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Hardware Inventory")}
<% end %>
<%= if @device.snmp_enabled do %>
<.link
patch={~p"/devices/#{@device.id}?tab=neighbors"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "neighbors" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Neighbors")}
<% end %>
<%= 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 text-sm transition-colors",
if @active_tab == "arp" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("ARP Table")}
<% 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 text-sm transition-colors",
if @active_tab == "mac" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("MAC Table")}
<% 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 text-sm transition-colors",
if @active_tab == "vlans" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("VLANs")}
<% 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 text-sm transition-colors",
if @active_tab == "ip_addresses" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("IP Addresses")}
<% end %>
<%= if false and @device.mikrotik_enabled do %>
<.link
patch={~p"/devices/#{@device.id}?tab=backups"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "backups" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
<.icon name="hero-beaker" class="h-3.5 w-3.5 text-blue-500 dark:text-blue-400" />
{t("Backups")}
<% end %>
<%= if false and @device.mikrotik_enabled do %>
<.link
navigate={~p"/devices/#{@device.id}/config-timeline"}
class="whitespace-nowrap py-4 px-1 border-b-2 border-transparent font-medium text-sm text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300"
>
<.icon name="hero-clock" class="h-3.5 w-3.5 text-orange-500 dark:text-orange-400" />
{t("Config Timeline")}
<% end %>
<%= if @preseem_access_point do %>
<.link
patch={~p"/devices/#{@device.id}?tab=preseem"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if(@active_tab == "preseem",
do: "border-blue-500 text-blue-600 dark:text-blue-400 font-semibold",
else:
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
)
]}
>
<.icon name="hero-signal" class="h-3.5 w-3.5 text-purple-500 dark:text-purple-400" />
{t("Preseem")}
<% end %>
<%= if @gaiia_item do %>
<.link
patch={~p"/devices/#{@device.id}?tab=gaiia"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if(@active_tab == "gaiia",
do: "border-blue-500 text-blue-600 dark:text-blue-400 font-semibold",
else:
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
)
]}
>
<.icon
name="hero-building-office"
class="h-3.5 w-3.5 text-emerald-500 dark:text-emerald-400"
/> Gaiia
<% end %>
<.link
patch={~p"/devices/#{@device.id}?tab=checks"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "checks" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Checks")}
<.link
patch={~p"/devices/#{@device.id}?tab=logs"}
class={[
"whitespace-nowrap py-4 px-1 border-b-2 text-sm transition-colors",
if @active_tab == "logs" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Events")}
<%= 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 text-sm transition-colors",
if @active_tab == "debug" do
"border-blue-500 text-blue-600 dark:text-blue-400 font-semibold"
else
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
end
]}
>
{t("Raw Data")}
<% end %>
<%= case @active_tab do %>
<% "overview" -> %>
{t("Device Information")}
<%= if @snmp_device do %>
System Name
{@snmp_device.sys_name || @device.name}
{t("Resolved IP")}
{@device.ip_address}
<.icon
name="hero-clipboard-document"
class="h-3.5 w-3.5 inline ml-1 opacity-0 group-hover/rip:opacity-100 transition-opacity text-gray-400"
/>
Hardware
{@snmp_device.model || "N/A"}
Operating System
{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}
<%= if firmware_update_available?(@snmp_device, @available_firmware) do %>
<.icon
name="hero-arrow-up-circle"
class="h-5 w-5 text-blue-500 cursor-help dark:text-blue-400"
/>
<.icon
name="hero-arrow-up-circle"
class="h-5 w-5 text-blue-500 dark:text-blue-400 flex-shrink-0 mt-0.5"
/>
{t("Firmware Update Available")}
Current:
{@snmp_device.firmware_version}
Latest:
{@available_firmware.version}
<%= if @available_firmware.release_date do %>
(released {format_date(
@available_firmware.release_date
)})
<% end %>
<% end %>
Serial Number
{@snmp_device.serial_number}
Object ID
{@snmp_device.sys_object_id || "N/A"}
Contact
{@snmp_device.sys_contact || "N/A"}
Location
{format_location(@snmp_device.sys_location)}
Uptime
{format_uptime(@snmp_device.sys_uptime)}
<% else %>
Name
{@device.name}
IP Address
{@device.ip_address}
<% end %>
Device Added
{format_device_age(@device.inserted_at)}
Last Discovered
{if @device.last_discovery_at do
format_device_age(@device.last_discovery_at)
else
"Never"
end}
Last Polled
{if @device.last_snmp_poll_at do
format_device_age(@device.last_snmp_poll_at)
else
"Never"
end}
<%= if @traffic_chart_data do %>
<.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"
>
{t("Overall Traffic")}
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-400" />
<% end %>
<%= if @latency_chart_data do %>
<.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"
>
{t("ICMP Latency")}
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-400" />
<% end %>
<%!-- Recent Config Changes mini-card --%>
<%= if false && @device.mikrotik_enabled && assigns[:recent_config_changes] && Enum.any?(@recent_config_changes) do %>
<.icon name="hero-clock" class="h-4 w-4 text-orange-500" />
{t("Recent Config Changes")}
<.link
navigate={~p"/devices/#{@device.id}/config-timeline"}
class="text-xs text-blue-500 hover:underline"
>
View Timeline →
<%= for event <- Enum.take(@recent_config_changes, 5) do %>
{Calendar.strftime(event.changed_at, "%b %d %H:%M")}
{s}
{event.change_size} lines
<% end %>
<% end %>
<%= 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 %>
<.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"
>
<%= if processor_count > 0 do %>
{processor_count} {if processor_count == 1,
do: "Processor",
else: "Processors"}
<% else %>
{t("Processors")}
<% end %>
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-400" />
<%= if @processor_chart_data do %>
<% end %>
<%= if avg_load do %>
{t("Average Load")}
= 50 && avg_load < 80 && "bg-yellow-500",
avg_load >= 80 && "bg-red-500"
]}
style={"width: #{min(avg_load, 100)}%"}
>
{Float.round(avg_load, 0)}%
<% end %>
<% end %>
<%= if @memory_chart_data do %>
<.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"
>
{t("Memory Usage")}
<.icon name="hero-arrow-right" class="h-4 w-4 text-gray-400" />
<% end %>
<%= if @storage_sensors && length(@storage_sensors) > 0 do %>
{t("Storage Usage")}
<%= for sensor <- @storage_sensors do %>
<.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}
<%= if sensor.latest_reading do %>
{format_sensor_value(
sensor.latest_reading.value,
sensor.sensor_divisor
)}
{sensor.sensor_unit}
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<%= if @storage && length(@storage) > 0 do %>
{t("Storage Volumes")}
({length(@storage)})
<%= if @storage_volume_chart_data do %>
<% end %>
<%= 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"
>
{storage.description || "Storage #{storage.storage_index}"}
{format_bytes(storage.used_bytes)} / {format_bytes(storage.total_bytes)}
= 70 && usage_percent < 90 && "bg-yellow-500",
usage_percent >= 90 && "bg-red-500"
]}
style={"width: #{min(usage_percent, 100)}%"}
>
{usage_percent}%
{String.replace(storage.storage_type || "other", "_", " ")
|> String.capitalize()}
<% end %>
<% end %>
<%= if @temperature_sensors && length(@temperature_sensors) > 0 do %>
{t("Temperature")}
<%= for sensor <- @temperature_sensors do %>
<.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}
<%= if sensor.latest_reading do %>
{format_sensor_value(
sensor.latest_reading.value,
sensor.sensor_divisor
)}
{sensor.sensor_unit || "°C"}
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<%= if @voltage_sensors && length(@voltage_sensors) > 0 do %>
{t("Voltage")}
<%= for sensor <- @voltage_sensors do %>
<.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}
<%= if sensor.latest_reading do %>
{format_sensor_value(
sensor.latest_reading.value,
sensor.sensor_divisor
)}
{sensor.sensor_unit}
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<%= if @grouped_transceivers && length(@grouped_transceivers) > 0 do %>
{t("Transceivers")}
<%= for {_transceiver_name, sensors} <- @grouped_transceivers do %>
<%= for sensor <- sensors do %>
<.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}
<%= if sensor.latest_reading do %>
{format_sensor_value(
sensor.latest_reading.value,
sensor.sensor_divisor
)}
{sensor.sensor_unit}
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<% end %>
<%= if (@general_counters && length(@general_counters) > 0) || (@firewall_counters && length(@firewall_counters) > 0) do %>
{t("Counters")}
<%= if @general_counters && length(@general_counters) > 0 do %>
<%= for sensor <- @general_counters do %>
<.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}
<%= if sensor.latest_reading && sensor.latest_reading.value do %>
{trunc(sensor.latest_reading.value)}
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
{sensor.sensor_unit}
<% end %>
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<%= if @firewall_counters && length(@firewall_counters) > 0 do %>
{t("Firewall")}
<%= for sensor <- @firewall_counters do %>
<.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}
<%= if sensor.latest_reading && sensor.latest_reading.value do %>
{trunc(sensor.latest_reading.value)}
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
{sensor.sensor_unit}
<% end %>
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<% end %>
<%= if @wireless_sensors && length(@wireless_sensors) > 0 do %>
{t("Wireless")}
<%= for sensor <- @wireless_sensors do %>
<.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}
<%= if sensor.latest_reading && sensor.latest_reading.value do %>
<%= if sensor.sensor_type in ["clients", "ccq", "utilization"] do %>
{trunc(sensor.latest_reading.value)}
<% else %>
{:erlang.float_to_binary(sensor.latest_reading.value * 1.0,
decimals: 1
)}
<% end %>
<%= if sensor.sensor_unit && sensor.sensor_unit != "" do %>
{sensor.sensor_unit}
<% end %>
<% else %>
{t("N/A")}
<% end %>
<% end %>
<% end %>
<%!-- Connected Devices --%>
<%= if @connected_devices != [] do %>
{t("Connected Devices")}
({length(@connected_devices)} links)
Device
IP Address
Interface
Link Type
Confidence
Status
<%= for link <- @connected_devices do %>
<% peer_device =
cond do
link.target_device && link.target_device.id != @device.id ->
link.target_device
link.source_device && link.source_device.id != @device.id ->
link.source_device
link.target_device ->
link.target_device
true ->
nil
end %>
<%= if peer_device do %>
<.link
navigate={~p"/devices/#{peer_device.id}"}
class="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
{peer_device.name}
<% else %>
{link.discovered_remote_name || link.discovered_remote_mac ||
"Unknown"}
<% end %>
<%= if peer_device do %>
{peer_device.ip_address}
<% else %>
{link.discovered_remote_ip || "-"}
<% end %>
<%= if link.source_interface do %>
{link.source_interface.if_name}
<% else %>
-
<% end %>
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
link.link_type == "mac_match" ->
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"
link.link_type == "arp_inference" ->
"bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"
true ->
"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200"
end
]}>
{link.link_type}
= 0.9 -> "bg-green-500"
link.confidence >= 0.7 -> "bg-blue-500"
link.confidence >= 0.5 -> "bg-yellow-500"
true -> "bg-red-500"
end
]}
style={"width: #{trunc(link.confidence * 100)}%"}
>
{trunc(link.confidence * 100)}%
<%= if peer_device do %>
{t("Managed")}
<% else %>
{t("Discovered")}
<% end %>
<% end %>
<% end %>
<% "ports" -> %>
<%= if @snmp_interfaces && length(@snmp_interfaces) > 0 do %>
<%= for {category, interfaces} <- @interfaces_by_type do %>
{category} Interfaces
({length(interfaces)})
#
Name
Status
Speed
IP Address
MAC
Capacity
Utilization
In
Out
Errors
<%= for interface <- interfaces do %>
{interface.if_index}
<.link
navigate={
~p"/devices/#{@device.id}/graph/traffic?interface_id=#{interface.id}"
}
class="hover:text-blue-600 dark:hover:text-blue-400"
>
{interface.if_name || interface.if_descr}
<%= if interface.if_alias do %>
{interface.if_alias}
<% end %>
{String.upcase(interface.if_oper_status || "unknown")}
{format_speed(interface.if_speed)}
<% interface_ips =
Map.get(@ip_addresses_by_interface, interface.id, []) %>
<%= if Enum.empty?(interface_ips) do %>
-
<% else %>
<%= for ip <- interface_ips do %>
{ip.ip_address}
<%= if ip.prefix_length do %>
/{ip.prefix_length}
<% end %>
<.icon
name="hero-clipboard-document"
class="h-3 w-3 inline ml-0.5 opacity-0 group-hover/iip:opacity-100 transition-opacity text-gray-400"
/>
<% end %>
<% end %>
{interface.if_phys_address}
<.icon
name="hero-clipboard-document"
class="h-3 w-3 inline ml-0.5 opacity-0 group-hover/mac:opacity-100 transition-opacity text-gray-400"
/>
-
<%= if interface.configured_capacity_bps do %>
{format_speed(interface.configured_capacity_bps)}
{capacity_source_label(interface.capacity_source)}
<.icon name="hero-x-mark" class="h-3 w-3" />
<% else %>
{t("Set")}
<% end %>
<%= if interface.utilization do %>
{Float.round(interface.utilization.utilization_pct, 1)}%
<% else %>
-
<% end %>
{if interface.latest_stat && interface.latest_stat.if_in_octets,
do: format_bytes(interface.latest_stat.if_in_octets),
else: "-"}
{if interface.latest_stat && interface.latest_stat.if_out_octets,
do: format_bytes(interface.latest_stat.if_out_octets),
else: "-"}
<% total_errors =
interface.latest_stat &&
(interface.latest_stat.if_in_errors || 0) +
(interface.latest_stat.if_out_errors || 0) +
(interface.latest_stat.if_in_discards || 0) +
(interface.latest_stat.if_out_discards || 0) %>
<%= if total_errors && total_errors > 0 do %>
<.link
navigate={
~p"/devices/#{@device.id}/graph/interface_errors?interface_id=#{interface.id}"
}
class="text-red-600 dark:text-red-400 hover:underline"
>
{total_errors}
<% else %>
-
<% end %>
<% end %>
<% end %>
<% else %>
<% end %>
<% "wireless" -> %>
<%= if @wireless_clients && length(@wireless_clients) > 0 do %>
{t("Connected Wireless Clients")}
{length(@wireless_clients)} clients — {@wireless_matched_count} subscribers matched
<%= if @wireless_last_updated do %>
· Last updated {format_relative_time(@wireless_last_updated)}
<% end %>
MAC Address
IP Address
Subscriber
Signal
SNR
Distance
TX Rate
RX Rate
Uptime
<%= for client <- @wireless_clients do %>
<% subscriber_link =
Map.get(@wireless_client_subscribers, String.downcase(client.mac_address)) %>
{client.mac_address}
<.icon
name="hero-clipboard-document"
class="h-3 w-3 inline ml-0.5 opacity-0 group-hover/mac:opacity-100 transition-opacity text-gray-400"
/>
<%= if client.ip_address do %>
{client.ip_address}
<.icon
name="hero-clipboard-document"
class="h-3 w-3 inline ml-0.5 opacity-0 group-hover/ip:opacity-100 transition-opacity text-gray-400"
/>
<% else %>
—
<% end %>
<%= if subscriber_link && subscriber_link.gaiia_account do %>
{subscriber_link.gaiia_account.account_name}
<% else %>
—
<% end %>
<%= if client.signal_strength do %>
<.signal_badge value={client.signal_strength} />
<% else %>
—
<% end %>
<%= if client.snr do %>
<.snr_badge value={client.snr} />
<% else %>
—
<% end %>
<%= if client.distance do %>
{client.distance}m
<% else %>
—
<% end %>
{format_rate(client.tx_rate)}
{format_rate(client.rx_rate)}
<%= if client.uptime_seconds do %>
{format_uptime(client.uptime_seconds * 100)}
<% else %>
—
<% end %>
<% end %>
<% else %>
<.icon
name="hero-signal"
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600"
/>
{t("No wireless clients")}
{t("No clients are currently connected to this access point.")}
<% end %>
<% "transceivers" -> %>
<%= if @transceivers && length(@transceivers) > 0 do %>
{t("Optical Transceivers")}
{length(@transceivers)} {if length(@transceivers) == 1,
do: "transceiver",
else: "transceivers"} installed
Port
Type
Vendor
Part Number
Serial Number
Wavelength
DOM
<%= for transceiver <- @transceivers do %>
{transceiver.port_index}
{transceiver.transceiver_type || "Unknown"}
{transceiver.vendor_name || "—"}
{transceiver.vendor_part_number || "—"}
{transceiver.vendor_serial_number || "—"}
<%= if transceiver.wavelength_nm do %>
{transceiver.wavelength_nm} nm
<% else %>
—
<% end %>
<%= if transceiver.supports_dom do %>
DOM
<% else %>
—
<% end %>
<% end %>
<% else %>
<.icon
name="hero-cpu-chip"
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600"
/>
{t("No transceivers found")}
{t("No optical transceivers detected on this device.")}
<% end %>
<% "printer_supplies" -> %>
<%= if @printer_supplies && length(@printer_supplies) > 0 do %>
{t("Printer Supplies")}
{length(@printer_supplies)} {if length(@printer_supplies) == 1,
do: "supply",
else: "supplies"} monitored
Type
Description
Color
Part Number
Level
<%= for supply <- @printer_supplies do %>
<% percent =
if supply.max_capacity && supply.max_capacity > 0 do
round(supply.current_level * 100 / supply.max_capacity)
else
nil
end %>
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
"ink" ->
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
"drum" ->
"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
_ ->
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
end
]}>
{supply.supply_type || "Unknown"}
{supply.supply_description || "—"}
{supply.color_name || "—"}
{supply.part_number || "—"}
<%= if percent do %>
"bg-red-500"
percent < 40 -> "bg-yellow-500"
true -> "bg-green-500"
end
]}
style={"width: #{percent}%"}
>
"text-red-600 dark:text-red-400"
percent < 40 -> "text-yellow-600 dark:text-yellow-400"
true -> "text-green-600 dark:text-green-400"
end
]}>
{percent}%
<% else %>
—
<% end %>
<% end %>
<% else %>
<.icon name="hero-printer" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600" />
{t("No printer supplies found")}
{t("No printer supplies detected on this device.")}
<% end %>
<% "hardware" -> %>
<%= if @hardware_inventory && length(@hardware_inventory) > 0 do %>
{t("Hardware Inventory")}
{length(@hardware_inventory)} {if length(@hardware_inventory) == 1,
do: "component",
else: "components"} detected
Class
Name
Description
Serial Number
Model
Manufacturer
<%= for entity <- @hardware_inventory do %>
{entity.entity_class || "Unknown"}
{entity.name || "—"}
{entity.description || "—"}
{entity.serial_number || "—"}
{entity.model || "—"}
{entity.manufacturer || "—"}
<% end %>
<% else %>
<.icon
name="hero-cpu-chip"
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-600"
/>
{t("No hardware inventory found")}
{t("No hardware components detected on this device.")}
<% end %>
<% "neighbors" -> %>
<%= if @neighbors && length(@neighbors) > 0 do %>
{t("Discovered Neighbors")}
Protocol
Local Port
Remote Device
Remote Port
IP Address
Platform
Capabilities
Last Seen
<%= for neighbor <- @neighbors do %>
{neighbor.protocol}
<.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}
<%= 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}
<% else %>
<%= if neighbor.remote_system_name do %>
{neighbor.remote_system_name |> String.split("/") |> List.first()}
<% else %>
{t("Unknown")}
<% end %>
<% end %>
{neighbor.remote_port_id || "-"}
<%= if !neighbor.matched_device && neighbor.remote_chassis_id do %>
{neighbor.remote_chassis_id}
<% end %>
{neighbor.remote_address || "-"}
{neighbor.remote_platform || "-"}
<%= if neighbor.remote_capabilities && length(neighbor.remote_capabilities) > 0 do %>
{Enum.join(neighbor.remote_capabilities, ", ")}
<% else %>
-
<% end %>
{time_ago(neighbor.last_discovered_at)}
<% end %>
<% else %>
<.icon name="hero-server-stack" class="mx-auto h-12 w-12 text-gray-400" />
{t("No neighbors discovered")}
{t(
"This device doesn't have any LLDP or CDP neighbors, or neighbor discovery hasn't run yet."
)}
<% end %>
<% "arp" -> %>
<%= if @arp_entries && length(@arp_entries) > 0 do %>
{t("ARP Table")}
({length(@arp_entries)} entries)
IP Address
MAC Address
Interface
Type
Last Seen
<%= for arp <- @arp_entries do %>
{arp.ip_address}
{arp.mac_address}
<%= 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}
<% else %>
ifIndex {arp.if_index}
<% end %>
{arp.entry_type || "unknown"}
{time_ago(arp.last_seen_at)}
<% end %>
<% else %>
<.icon name="hero-table-cells" class="mx-auto h-12 w-12 text-gray-400" />
{t("No ARP entries discovered")}
{t("This device doesn't have any ARP entries, or ARP discovery hasn't run yet.")}
<% end %>
<% "mac" -> %>
<%= if @mac_addresses && length(@mac_addresses) > 0 do %>
{t("MAC Address Table")}
({length(@mac_addresses)} entries)
MAC Address
VLAN
Interface
Status
Last Seen
<%= for mac <- @mac_addresses do %>
{mac.mac_address}
<%= if mac.vlan_id do %>
{mac.vlan_id}
<% else %>
-
<% end %>
<%= 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}
<% else %>
Port {mac.port_index}
<% end %>
{mac.entry_status || "unknown"}
{time_ago(mac.last_seen_at)}
<% end %>
<% else %>
<.icon name="hero-table-cells" class="mx-auto h-12 w-12 text-gray-400" />
{t("No MAC addresses discovered")}
{t(
"This device doesn't have any MAC forwarding entries, or MAC discovery hasn't run yet."
)}
<% end %>
<% "vlans" -> %>
<%= if @vlans && length(@vlans) > 0 do %>
{t("Configured VLANs")}
({length(@vlans)})
VLAN ID
Name
Type
Status
Last Checked
<%= for vlan <- @vlans do %>
{vlan.vlan_id}
{vlan.vlan_name || "-"}
{vlan.vlan_type || "ethernet"}
{String.upcase(vlan.status || "unknown")}
{time_ago(vlan.last_checked_at)}
<% end %>
<% else %>
<.icon name="hero-square-3-stack-3d" class="mx-auto h-12 w-12 text-gray-400" />
{t("No VLANs discovered")}
{t(
"This device doesn't have any VLANs configured, or VLAN discovery hasn't run yet."
)}
<% end %>
<% "ip_addresses" -> %>
<%= if length(@ipv4_addresses) > 0 do %>
{t("IPv4")}
{t("Addresses")}
({length(@ipv4_addresses)})
IP Address
Subnet
Interface
Primary
<%= for ip <- @ipv4_addresses do %>
{ip.ip_address}
<%= if ip.prefix_length do %>
/{ip.prefix_length}
<% else %>
{ip.subnet_mask || "-"}
<% end %>
{(ip.snmp_interface &&
(ip.snmp_interface.if_name || ip.snmp_interface.if_descr)) || "-"}
<%= if ip.is_primary do %>
{t("Primary")}
<% else %>
-
<% end %>
<% end %>
<% end %>
<%= if length(@ipv6_addresses) > 0 do %>
{t("IPv6")}
{t("Addresses")}
({length(@ipv6_addresses)})
IP Address
Prefix
Interface
<%= for ip <- @ipv6_addresses do %>
{ip.ip_address}
/{ip.prefix_length || "-"}
{(ip.snmp_interface &&
(ip.snmp_interface.if_name || ip.snmp_interface.if_descr)) || "-"}
<% end %>
<% end %>
<%= if length(@ipv4_addresses) == 0 && length(@ipv6_addresses) == 0 do %>
<.icon name="hero-globe-alt" class="mx-auto h-12 w-12 text-gray-400" />
{t("No IP addresses discovered")}
{t(
"This device doesn't have any IP addresses discovered, or IP discovery hasn't run yet."
)}
<% end %>
<% "backups" -> %>
<%= if false and @device.mikrotik_enabled do %>
{t("Configuration Backups")}
<%= if Enum.any?(@mikrotik_backups) do %>
({length(@mikrotik_backups)})
<% end %>
<.icon name="hero-beaker" class="h-3 w-3" /> Experimental
<%= if @agent_info.agent_token_id do %>
<.icon name="hero-arrow-down-tray" class="h-4 w-4" /> Backup Now
<% else %>
{t("Assign an agent to enable backups")}
<% end %>
<%= if Enum.any?(@mikrotik_backups) do %>
<%!-- Instructions for comparing backups --%>
<%= if length(@mikrotik_backups) > 1 do %>
<.icon
name="hero-information-circle"
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
/>
Compare configurations:
{t("Select any 2 backups using the checkboxes to compare their differences")}
<% end %>
<%!-- Pagination --%>
<%= if assigns[:pagination] do %>
<.pagination
meta={@pagination}
path={~p"/devices/#{@device.id}"}
params={%{"tab" => "backups"}}
/>
<% end %>
<%!-- Floating Compare Button --%>
<%= if MapSet.size(@selected_backup_ids) > 0 do %>
<%!-- Selection status --%>
<.icon
name="hero-check-circle"
class="h-5 w-5 text-blue-600 dark:text-blue-400"
/>
{MapSet.size(@selected_backup_ids)}
of 2 backups selected
<%!-- Instructions when only 1 selected --%>
<%= if MapSet.size(@selected_backup_ids) == 1 do %>
{t("Select one more backup to compare")}
<% end %>
<%!-- Action buttons --%>
{t("Clear")}
<.icon name="hero-arrows-right-left" class="h-4 w-4" />
{if MapSet.size(@selected_backup_ids) == 2,
do: "Compare Selected",
else: "Compare"}
<% end %>
<% else %>
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-gray-400" />
{t("No backups yet")}
<%= if @agent_info.agent_token_id do %>
Automatic backups run daily at {ToweropsWeb.TimeHelpers.format_utc_hour(
7,
@timezone
)}.
<% else %>
{t("Assign an agent to enable automatic backups.")}
<% end %>
<% end %>
<% else %>
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400" />
{t("MikroTik API not enabled")}
{t("Enable MikroTik API in device settings to enable configuration backups.")}
<.button navigate={~p"/devices/#{@device.id}/edit"}>
{t("Edit Device Settings")}
<% end %>
<% "logs" -> %>
{t("Event Logs")}
{t("Showing the most recent 100 events")}
<%= if Enum.any?(@events) do %>
<%= for event <- @events do %>
<%= 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 %>
{event.message}
{format_event_type(event.event_type)}
•
{event.severity}
{time_ago(event.occurred_at)}
<% end %>
<% else %>
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-gray-400" />
{t("No events yet")}
{t(
"Events will appear here as interface states change, speeds change, or other monitored changes occur."
)}
<% end %>
<% "debug" -> %>
{t("Raw Discovery Data")}
{t("Raw SNMP OID values collected during the last discovery run at")}
<%= if @snmp_device.last_discovery_at do %>
{ToweropsWeb.TimeHelpers.format_iso8601(
@snmp_device.last_discovery_at,
@timezone
)}
<% else %>
unknown time
<% end %>.
<%= if @snmp_device.raw_discovery_data do %>
{t("System OIDs")}
{t("Field")}
{t("OID")}
{t("Value")}
<%= for {key, data} <- Map.get(@snmp_device.raw_discovery_data, "system_oids", %{}) do %>
{key}
{data["oid"]}
{inspect(data["value"])}
<% end %>
<%= if Map.get(@snmp_device.raw_discovery_data, "health_sensor_table") do %>
{t("Health Sensor Table Walk")}
{t("OID")}
{t("Value")}
<%= for {oid, value} <- Map.get(@snmp_device.raw_discovery_data, "health_sensor_table", %{}) do %>
{oid}
{inspect(value)}
<% end %>
<% end %>
<%= if Map.get(@snmp_device.raw_discovery_data, "discovered_sensors") do %>
{t("Discovered Sensors")}
{t("Type")}
{t("Description")}
{t("OID")}
{t("Value")}
{t("State")}
<%= for sensor <- Map.get(@snmp_device.raw_discovery_data, "discovered_sensors", []) do %>
{sensor["type"]}
{sensor["descr"]}
{sensor["oid"]}
{sensor["value"]}
<%= if sensor["unit"] && sensor["unit"] != "" do %>
{sensor["unit"]}
<% end %>
<%= if sensor["state_descr"] do %>
{sensor["state_descr"]}
<% else %>
-
<% end %>
<% end %>
<% end %>
{t("Copyable Raw Data")}
Click the text box to select all data, then copy with Cmd+C or Ctrl+C
<% else %>
{t(
"No raw discovery data available. Run discovery again to collect debug data."
)}
<% end %>
<% "checks" -> %>
<%= if Enum.empty?(@checks) do %>
<.icon name="hero-clipboard-document-check" class="mx-auto h-12 w-12 text-gray-400" />
{t("No checks configured")}
{t(
"Run discovery to automatically detect sensors, interfaces, and other monitorable items, or add a service check manually."
)}
<%= if @device.snmp_enabled do %>
<.button type="button" phx-click="run_discovery">
<.icon name="hero-magnifying-glass" class="h-4 w-4" /> Run Discovery
<% end %>
<.button type="button" phx-click="add_check">
<.icon name="hero-plus" class="h-4 w-4" /> Add Check
<% else %>
Checks ({length(@checks)})
<.button type="button" phx-click="add_check">
<.icon name="hero-plus" class="h-4 w-4" /> Add Check
<%= for {group_key, group_checks} <- @grouped_checks do %>
{group_title(group_key)} ({length(group_checks)})
{t("Name")}
{t("Status")}
{t("Value")}
{t("Last Checked")}
Actions
<%= for check <- group_checks do %>
{check.name}
{render_status_badge(check.current_state)}
<%= if check.last_check_at do %>
{get_latest_value(check)}
<% else %>
Pending
<% end %>
<%= if check.last_check_at do %>
{format_relative_time(check.last_check_at)}
<% else %>
Never
<% end %>
<.link
navigate={
~p"/devices/#{@device.id}/graph/check?check_id=#{check.id}&range=24h"
}
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium"
>
{t("Graph")}
<%= if check.check_type in ["http", "tcp", "dns", "ssl"] do %>
<.icon name="hero-pencil-square" class="h-4 w-4" />
<.icon name="hero-trash" class="h-4 w-4" />
<% end %>
<% end %>
<% end %>
<% end %>
<% "gaiia" -> %>
<%= if @gaiia_item do %>
<%!-- Inventory Item Details --%>
{t("Gaiia Inventory Item")}
Name
{@gaiia_item.name || "---"}
Gaiia ID
{@gaiia_item.gaiia_id}
Status
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
"decommissioned" ->
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
_ ->
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
end
]}>
{@gaiia_item.status || "unknown"}
<%= if @gaiia_item.model_name do %>
Model
{@gaiia_item.model_name}
<% end %>
<%= if @gaiia_item.manufacturer_name do %>
{t("Manufacturer")}
{@gaiia_item.manufacturer_name}
<% end %>
<%= if @gaiia_item.category do %>
{t("Category")}
{@gaiia_item.category}
<% end %>
<%= if @gaiia_item.serial_number do %>
{t("Serial Number")}
{@gaiia_item.serial_number}
<% end %>
<%= if @gaiia_item.mac_address do %>
{t("MAC Address")}
{@gaiia_item.mac_address}
<% end %>
<%= if @gaiia_item.ip_address do %>
{t("IP Address (Gaiia)")}
{@gaiia_item.ip_address}
<%= if @device.ip_address && @gaiia_item.ip_address != @device.ip_address do %>
<.icon name="hero-exclamation-triangle-mini" class="mr-0.5 h-3 w-3" />
{t("Mismatch")}
<% end %>
<% end %>
<%!-- Network Site --%>
<%= if @gaiia_network_site do %>
{t("Gaiia Network Site")}
{t("Site Name")}
{@gaiia_network_site.name}
<%= if @gaiia_network_site.account_count do %>
{t("Subscribers")}
{@gaiia_network_site.account_count}
<% end %>
<%= if @gaiia_network_site.total_mrr do %>
{t("Monthly Revenue")}
${Decimal.round(@gaiia_network_site.total_mrr, 2)}
<% end %>
<% end %>
<%!-- Linked Subscriber --%>
<%= if @gaiia_account do %>
{t("Linked Subscriber")}
Name
{@gaiia_account.name}
<%= if @gaiia_account.readable_id do %>
{t("Account ID")}
{@gaiia_account.readable_id}
<% end %>
Status
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
"suspended" ->
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
_ ->
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
end
]}>
{@gaiia_account.status || "unknown"}
<%= if @gaiia_account.mrr do %>
MRR
${Decimal.round(@gaiia_account.mrr, 2)}
<% end %>
<% end %>
<%!-- Billing Subscriptions --%>
<%= if @gaiia_subscriptions != [] do %>
{t("Billing Subscriptions")}
{t("Plan")}
{t("Status")}
{t("MRR")}
<%= for sub <- @gaiia_subscriptions do %>
{sub.product_name || "---"}
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
"suspended" ->
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
_ ->
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
end
]}>
{sub.status || "unknown"}
<%= if sub.mrr_amount do %>
${Decimal.round(sub.mrr_amount, 2)}
{sub.currency || ""}
<% else %>
---
<% end %>
<% end %>
<% end %>
<% else %>
{t("No Gaiia inventory item linked to this device.")}
<% end %>
<% "preseem" -> %>
<%= if @preseem_access_point do %>
<%!-- Insights section --%>
<%= if @preseem_insights != [] do %>
<%= for insight <- @preseem_insights do %>
"bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800"
"warning" ->
"bg-yellow-50 border-yellow-200 dark:bg-yellow-900/20 dark:border-yellow-800"
_ ->
"bg-blue-50 border-blue-200 dark:bg-blue-900/20 dark:border-blue-800"
end
]}>
"text-red-500"
"warning" -> "text-yellow-500"
_ -> "text-blue-500"
end
]}>
<%= case insight.urgency do %>
<% "critical" -> %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5" />
<% "warning" -> %>
<.icon name="hero-exclamation-circle" class="h-5 w-5" />
<% _ -> %>
<.icon name="hero-information-circle" class="h-5 w-5" />
<% end %>
{insight.title}
{insight.description}
<.icon name="hero-x-mark" class="h-4 w-4" />
<% end %>
<% end %>
<%!-- Score cards --%>
QoE Score
{if @preseem_access_point.qoe_score,
do: Float.round(@preseem_access_point.qoe_score, 1),
else: "---"}
{t("Capacity Score")}
{if @preseem_access_point.capacity_score,
do: Float.round(@preseem_access_point.capacity_score, 1),
else: "---"}
RF Score
{if @preseem_access_point.rf_score,
do: Float.round(@preseem_access_point.rf_score, 1),
else: "---"}
<%!-- Details card --%>
{t("Access Point Details")}
Name
{@preseem_access_point.name || "---"}
{t("Preseem ID")}
{@preseem_access_point.preseem_id}
{t("Subscribers")}
{@preseem_access_point.subscriber_count || "---"}
{t("Busy Hours")}
<%= if @preseem_access_point.busy_hours do %>
{@preseem_access_point.busy_hours} hrs/day
<% else %>
---
<% end %>
{t("Airtime Utilization")}
<%= if @preseem_access_point.airtime_utilization do %>
{Float.round(@preseem_access_point.airtime_utilization, 1)}%
<% else %>
---
<% end %>
{t("Match Type")}
{@preseem_access_point.match_confidence}
<%!-- Recent metrics table --%>
<%= if @preseem_metrics != [] do %>
{t("Recent QoE Metrics")}
{t("Time")}
{t("Latency (ms)")}
{t("P95 Latency")}
{t("Jitter (ms)")}
{t("Loss (%)")}
{t("Throughput")}
{t("Subscribers")}
<%= for metric <- @preseem_metrics do %>
{ToweropsWeb.TimeHelpers.format_iso8601(
metric.recorded_at,
@current_scope.timezone,
@current_scope.time_format
)}
{if metric.avg_latency,
do: Float.round(metric.avg_latency, 1),
else: "---"}
{if metric.p95_latency,
do: Float.round(metric.p95_latency, 1),
else: "---"}
{if metric.avg_jitter,
do: Float.round(metric.avg_jitter, 1),
else: "---"}
{if metric.avg_loss, do: Float.round(metric.avg_loss, 2), else: "---"}
{if metric.avg_throughput,
do: "#{Float.round(metric.avg_throughput, 1)} Mbps",
else: "---"}
{metric.subscriber_count || "---"}
<% end %>
<% end %>
<% else %>
{t("No Preseem data linked to this device.")}
<% end %>
<% _ -> %>
<% end %>
<%= if @show_check_form do %>
<.live_component
module={FormComponent}
id={if @edit_check, do: @edit_check.id, else: :new}
action={if @edit_check, do: :edit, else: :new}
check={@edit_check}
device={@device}
/>
<% end %>