<.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" />
<%= 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 %>
<%= 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" />
<% 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" />
{t("Loading chart...")}
<% 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" />
{t("Loading chart...")}
<% 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)

<%= 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 %> <% end %>
Device IP Address Interface Link Type Confidence Status
<%= 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 %> <% "ports" -> %> <%= if @snmp_interfaces && length(@snmp_interfaces) > 0 do %>
<%= for {category, interfaces} <- @interfaces_by_type do %>

{category} Interfaces ({length(interfaces)})

<%= for interface <- interfaces do %> <% end %>
# Name Status Speed IP Address MAC Capacity Utilization In Out
{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)}
<% else %> <% 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: "-"}
<% end %>
<% else %>

No interfaces discovered

<% 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 %>

<%= for client <- @wireless_clients do %> <% subscriber_link = Map.get(@wireless_client_subscribers, String.downcase(client.mac_address)) %> <% end %>
MAC Address IP Address Subscriber Signal SNR Distance TX Rate RX Rate Uptime
{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 %>
<% 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 %> <% "neighbors" -> %> <%= if @neighbors && length(@neighbors) > 0 do %>

{t("Discovered Neighbors")}

<%= for neighbor <- @neighbors do %> <% end %>
Protocol Local Port Remote Device Remote Port IP Address Platform Capabilities Last Seen
{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)}
<% 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)

<%= for arp <- @arp_entries do %> <% end %>
IP Address MAC Address Interface Type Last Seen
{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)}
<% 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)

<%= for mac <- @mac_addresses do %> <% end %>
MAC Address VLAN Interface Status Last Seen
{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)}
<% 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)})

<%= for vlan <- @vlans do %> <% end %>
VLAN ID Name Type Status Last Checked
{vlan.vlan_id} {vlan.vlan_name || "-"} {vlan.vlan_type || "ethernet"} {String.upcase(vlan.status || "unknown")} {time_ago(vlan.last_checked_at)}
<% 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)})

<%= for ip <- @ipv4_addresses do %> <% end %>
IP Address Subnet Interface Primary
{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 %> <%= if length(@ipv6_addresses) > 0 do %>

{t("IPv6")} {t("Addresses")} ({length(@ipv6_addresses)})

<%= for ip <- @ipv6_addresses do %> <% end %>
IP Address Prefix Interface
{ip.ip_address} /{ip.prefix_length || "-"} {(ip.snmp_interface && (ip.snmp_interface.if_name || ip.snmp_interface.if_descr)) || "-"}
<% 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 %> <% 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 %>
<%= for backup <- @mikrotik_backups do %> <% compression_ratio = if backup.config_size_bytes && backup.config_size_bytes > 0 do Float.round( (1 - backup.compressed_size_bytes / backup.config_size_bytes) * 100, 1 ) else 0.0 end %> <% end %>
<.icon name="hero-check-circle" class="h-4 w-4 mx-auto" /> Date Original Size Compressed Size Ratio Source Actions
= 2 } title={ if MapSet.member?(@selected_backup_ids, backup.id), do: "Selected for comparison", else: if(MapSet.size(@selected_backup_ids) >= 2, do: "Maximum 2 backups can be selected", else: "Select to compare" ) } class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600 disabled:opacity-50 disabled:cursor-not-allowed dark:border-gray-600 dark:bg-gray-700" /> {ToweropsWeb.TimeHelpers.format_iso8601(backup.backed_up_at, @timezone)} {format_bytes(backup.config_size_bytes)} {format_bytes(backup.compressed_size_bytes)} {compression_ratio}% {String.replace(backup.trigger_source, "_", " ") |> String.capitalize()}
<%= if ToweropsWeb.Permissions.owner?(@current_scope) do %> <.button phx-click="delete_backup" phx-value-id={backup.id} data-confirm={ t( "Are you sure you want to delete this backup? This action cannot be undone." ) } variant="danger" > <.icon name="hero-trash" class="h-4 w-4" /> Delete <% 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 --%>
<% 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}
<% 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")}

<%= for {key, data} <- Map.get(@snmp_device.raw_discovery_data, "system_oids", %{}) do %> <% end %>
{t("Field")} {t("OID")} {t("Value")}
{key} {data["oid"]} {inspect(data["value"])}
<%= if Map.get(@snmp_device.raw_discovery_data, "health_sensor_table") do %>

{t("Health Sensor Table Walk")}

<%= for {oid, value} <- Map.get(@snmp_device.raw_discovery_data, "health_sensor_table", %{}) do %> <% end %>
{t("OID")} {t("Value")}
{oid} {inspect(value)}
<% end %> <%= if Map.get(@snmp_device.raw_discovery_data, "discovered_sensors") do %>

{t("Discovered Sensors")}

<%= for sensor <- Map.get(@snmp_device.raw_discovery_data, "discovered_sensors", []) do %> <% end %>
{t("Type")} {t("Description")} {t("OID")} {t("Value")} {t("State")}
{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 %>

{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)})

<%= for check <- group_checks do %> <% end %>
{t("Name")} {t("Status")} {t("Value")} {t("Last Checked")} Actions
{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 %> <% 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")}

<%= for sub <- @gaiia_subscriptions do %> <% end %>
{t("Plan")} {t("Status")} {t("MRR")}
{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 %>
<% 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}

<% 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")}

<%= for metric <- @preseem_metrics do %> <% end %>
{t("Time")} {t("Latency (ms)")} {t("P95 Latency")} {t("Jitter (ms)")} {t("Loss (%)")} {t("Throughput")} {t("Subscribers")}
{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 %>
<% else %>

{t("No Preseem data linked to this device.")}

<% end %> <% _ -> %>

Tab not found

<% 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 %>