+
+
<.icon name="hero-server" class="h-5 w-5 text-green-600" />
@@ -89,17 +105,121 @@ defmodule DashboardWeb.HostsLive do
-
-
Online
+
+
+
+ Online
+
-
-
-
Raw Data:
-
- <%= Jason.encode!(@data, pretty: true) %>
-
-
+
+ <%= if @show_raw do %>
+
+
+
Raw Data:
+
+ <%= Jason.encode!(@data, pretty: true) %>
+
+
+ <% else %>
+
+
+
+ <.metric_card
+ title="CPU Load Average"
+ icon="hero-cpu-chip"
+ data={@data["cpu"]}
+ type={:load_average}
+ />
+
+
+ <.metric_card
+ title="Memory Usage"
+ icon="hero-circle-stack"
+ data={@data["memory"]}
+ type={:memory}
+ />
+
+
+ <.metric_card
+ title="Disk Usage"
+ icon="hero-hard-drive"
+ data={@data["disk"]}
+ type={:disk}
+ />
+
+
+ <%= if @data["gpu"] do %>
+ <.metric_card
+ title="GPU Status"
+ icon="hero-tv"
+ data={@data["gpu"]}
+ type={:gpu}
+ />
+ <% end %>
+
+
+ <%= if @data["network"] && length(@data["network"]) > 0 do %>
+ <.metric_card
+ title="Network Interfaces"
+ icon="hero-signal"
+ data={@data["network"]}
+ type={:network}
+ />
+ <% end %>
+
+
+ <%= if @data["temperature"] do %>
+ <.metric_card
+ title="Temperature"
+ icon="hero-fire"
+ data={@data["temperature"]}
+ type={:temperature}
+ />
+ <% end %>
+
+
+
+ <%= if @data["processes"] do %>
+
+ <.metric_card
+ title="Top Processes"
+ icon="hero-list-bullet"
+ data={@data["processes"]}
+ type={:processes}
+ />
+
+ <% end %>
+
+
+
+
System Information
+
+
+ Uptime:
+ <%= format_uptime(@data["system"]["uptime_seconds"]) %>
+
+
+ Erlang:
+ <%= @data["system"]["erlang_version"] %>
+
+
+ OTP:
+ <%= @data["system"]["otp_release"] %>
+
+
+ Schedulers:
+ <%= @data["system"]["schedulers"] %>
+
+
+
+ <% end %>
"""
end
@@ -109,4 +229,377 @@ defmodule DashboardWeb.HostsLive do
end
defp format_datetime(_), do: "Unknown"
+
+ defp format_uptime(nil), do: "Unknown"
+ defp format_uptime(seconds) when is_integer(seconds) do
+ days = div(seconds, 86400)
+ hours = div(rem(seconds, 86400), 3600)
+ minutes = div(rem(seconds, 3600), 60)
+
+ cond do
+ days > 0 -> "#{days}d #{hours}h #{minutes}m"
+ hours > 0 -> "#{hours}h #{minutes}m"
+ true -> "#{minutes}m"
+ end
+ end
+ defp format_uptime(_), do: "Unknown"
+
+ attr :title, :string, required: true
+ attr :icon, :string, required: true
+ attr :data, :map, required: true
+ attr :type, :atom, required: true
+
+ defp metric_card(assigns) do
+ ~H"""
+
+
+ <.icon name={@icon} class="h-5 w-5 text-zinc-600" />
+
<%= @title %>
+
+
+ <%= case @type do %>
+ <% :load_average -> %>
+ <.load_average_display data={@data} />
+ <% :memory -> %>
+ <.memory_display data={@data} />
+ <% :disk -> %>
+ <.disk_display data={@data} />
+ <% :gpu -> %>
+ <.gpu_display data={@data} />
+ <% :network -> %>
+ <.network_display data={@data} />
+ <% :temperature -> %>
+ <.temperature_display data={@data} />
+ <% :processes -> %>
+ <.processes_display data={@data} />
+ <% _ -> %>
+
No data available
+ <% end %>
+
+ """
+ end
+
+ defp load_average_display(assigns) do
+ ~H"""
+ <%= if @data do %>
+
+
+ 1 min
+ <%= format_float(@data["avg1"]) %>
+
+ <.progress_bar value={@data["avg1"]} max={4.0} color={load_color(@data["avg1"])} />
+
+
+ 5 min
+ <%= format_float(@data["avg5"]) %>
+
+ <.progress_bar value={@data["avg5"]} max={4.0} color={load_color(@data["avg5"])} />
+
+
+ 15 min
+ <%= format_float(@data["avg15"]) %>
+
+ <.progress_bar value={@data["avg15"]} max={4.0} color={load_color(@data["avg15"])} />
+
+ <% else %>
+
No load data available
+ <% end %>
+ """
+ end
+
+ defp memory_display(assigns) do
+ ~H"""
+ <%= if @data && @data["total_kb"] do %>
+
+
+ Used
+ <%= @data["used_percent"] %>%
+
+ <.progress_bar value={@data["used_percent"]} max={100} color={memory_color(@data["used_percent"])} />
+
+
+
+ Total:
+ <%= format_kb(@data["total_kb"]) %>
+
+
+ Used:
+ <%= format_kb(@data["used_kb"]) %>
+
+
+ Available:
+ <%= format_kb(@data["available_kb"]) %>
+
+
+
+ <% else %>
+
No memory data available
+ <% end %>
+ """
+ end
+
+ defp disk_display(assigns) do
+ ~H"""
+ <%= if @data && @data["disks"] do %>
+
+ <%= for disk <- @data["disks"] do %>
+
+
+ <%= disk["mounted_on"] %>
+ <%= disk["use_percent"] %>%
+
+ <.progress_bar value={disk["use_percent"]} max={100} color={disk_color(disk["use_percent"])} />
+
+ <%= disk["used"] %> used
+ <%= disk["available"] %> free
+
+
+ <% end %>
+
+ <% else %>
+
No disk data available
+ <% end %>
+ """
+ end
+
+ attr :value, :any, required: true
+ attr :max, :any, required: true
+ attr :color, :string, default: "bg-blue-500"
+
+ defp progress_bar(assigns) do
+ assigns = assign(assigns, :percentage, min(assigns.value / assigns.max * 100, 100))
+
+ ~H"""
+
+ """
+ end
+
+ # Helper functions for formatting and colors
+ defp format_float(nil), do: "N/A"
+ defp format_float(value) when is_float(value), do: :erlang.float_to_binary(value, decimals: 2)
+ defp format_float(value), do: to_string(value)
+
+ defp format_kb(nil), do: "N/A"
+ defp format_kb(kb) when is_integer(kb) do
+ cond do
+ kb >= 1_048_576 -> "#{Float.round(kb / 1_048_576, 1)} GB"
+ kb >= 1_024 -> "#{Float.round(kb / 1_024, 1)} MB"
+ true -> "#{kb} KB"
+ end
+ end
+
+ defp load_color(load) when is_float(load) do
+ cond do
+ load >= 2.0 -> "bg-red-500"
+ load >= 1.0 -> "bg-yellow-500"
+ true -> "bg-green-500"
+ end
+ end
+ defp load_color(_), do: "bg-zinc-400"
+
+ defp memory_color(percent) when is_float(percent) do
+ cond do
+ percent >= 90 -> "bg-red-500"
+ percent >= 75 -> "bg-yellow-500"
+ true -> "bg-blue-500"
+ end
+ end
+ defp memory_color(_), do: "bg-zinc-400"
+
+ defp disk_color(percent) when is_integer(percent) do
+ cond do
+ percent >= 90 -> "bg-red-500"
+ percent >= 80 -> "bg-yellow-500"
+ true -> "bg-green-500"
+ end
+ end
+ defp disk_color(_), do: "bg-zinc-400"
+
+ # GPU Display Component
+ defp gpu_display(assigns) do
+ ~H"""
+ <%= if @data do %>
+
+
+ <%= if @data["nvidia"] && length(@data["nvidia"]) > 0 do %>
+
NVIDIA
+ <%= for gpu <- @data["nvidia"] do %>
+
+
+ <%= gpu["name"] %>
+ <%= gpu["utilization_percent"] %>%
+
+ <.progress_bar value={gpu["utilization_percent"]} max={100} color={gpu_color(gpu["utilization_percent"])} />
+
+ <%= gpu["temperature_c"] %>°C
+ <%= format_mb(gpu["memory_used_mb"]) %>/<%= format_mb(gpu["memory_total_mb"]) %>
+
+
+ <% end %>
+ <% end %>
+
+
+ <%= if @data["amd"] && length(@data["amd"]) > 0 do %>
+
AMD
+ <%= for gpu <- @data["amd"] do %>
+
+
+ <%= gpu["name"] %>
+ <%= gpu["utilization_percent"] || "N/A" %>%
+
+ <.progress_bar value={gpu["utilization_percent"] || 0} max={100} color={gpu_color(gpu["utilization_percent"])} />
+
+ <%= format_float(gpu["temperature_c"]) %>°C
+
+
+ <% end %>
+ <% end %>
+
+ <%= if (length(@data["nvidia"] || []) + length(@data["amd"] || [])) == 0 do %>
+
No GPUs detected
+ <% end %>
+
+ <% else %>
+
No GPU data available
+ <% end %>
+ """
+ end
+
+ # Network Display Component
+ defp network_display(assigns) do
+ ~H"""
+ <%= if @data && length(@data) > 0 do %>
+
+ <%= for interface <- Enum.take(@data, 3) do %>
+
+
+ <%= interface["interface"] %>
+
+
+
+ ↓ <%= format_bytes(interface["rx_bytes"]) %>
+
+
+ ↑ <%= format_bytes(interface["tx_bytes"]) %>
+
+
+ <%= if (interface["rx_errors"] + interface["tx_errors"]) > 0 do %>
+
+ Errors: RX <%= interface["rx_errors"] %>, TX <%= interface["tx_errors"] %>
+
+ <% end %>
+
+ <% end %>
+
+ <% else %>
+
No network interfaces
+ <% end %>
+ """
+ end
+
+ # Temperature Display Component
+ defp temperature_display(assigns) do
+ ~H"""
+ <%= if @data do %>
+
+
+ <%= if @data["cpu"] do %>
+
+
+ CPU
+ <%= format_float(@data["cpu"]) %>°C
+
+
+ <% end %>
+
+
+ <%= if @data["sensors"] && map_size(@data["sensors"]) > 0 do %>
+ <%= for {chip_name, temps} <- Enum.take(@data["sensors"], 3) do %>
+
<%= chip_name %>
+ <%= for {sensor, temp} <- Enum.take(temps, 2) do %>
+
+ <%= sensor %>
+ <%= format_float(temp) %>°C
+
+ <% end %>
+ <% end %>
+ <% end %>
+
+ <%= if !@data["cpu"] && (!@data["sensors"] || map_size(@data["sensors"]) == 0) do %>
+
No temperature sensors
+ <% end %>
+
+ <% else %>
+
No temperature data
+ <% end %>
+ """
+ end
+
+ # Processes Display Component
+ defp processes_display(assigns) do
+ ~H"""
+ <%= if @data && length(@data) > 0 do %>
+
+ <%= for process <- Enum.take(@data, 8) do %>
+
+
+
<%= process["command"] %>
+
<%= process["user"] %> (PID <%= process["pid"] %>)
+
+
+
<%= format_float(process["cpu_percent"]) %>%
+
<%= format_float(process["memory_percent"]) %>%
+
+
+ <% end %>
+
+ <% else %>
+
No process data
+ <% end %>
+ """
+ end
+
+ # Additional helper functions
+ defp format_mb(nil), do: "N/A"
+ defp format_mb(mb) when is_integer(mb) do
+ cond do
+ mb >= 1024 -> "#{Float.round(mb / 1024, 1)} GB"
+ true -> "#{mb} MB"
+ end
+ end
+
+ defp format_bytes(bytes) when is_integer(bytes) do
+ cond do
+ bytes >= 1_073_741_824 -> "#{Float.round(bytes / 1_073_741_824, 1)} GB"
+ bytes >= 1_048_576 -> "#{Float.round(bytes / 1_048_576, 1)} MB"
+ bytes >= 1_024 -> "#{Float.round(bytes / 1_024, 1)} KB"
+ true -> "#{bytes} B"
+ end
+ end
+ defp format_bytes(_), do: "N/A"
+
+ defp gpu_color(util) when is_integer(util) do
+ cond do
+ util >= 80 -> "bg-red-500"
+ util >= 50 -> "bg-yellow-500"
+ true -> "bg-green-500"
+ end
+ end
+ defp gpu_color(_), do: "bg-zinc-400"
+
+ defp temp_color(temp) when is_number(temp) do
+ cond do
+ temp >= 80 -> "red-600"
+ temp >= 70 -> "yellow-600"
+ temp >= 60 -> "yellow-500"
+ true -> "green-600"
+ end
+ end
+ defp temp_color(_), do: "zinc-500"
end
\ No newline at end of file