defmodule DalaDev.Server.ObserverLive.System do @moduledoc "LiveView for system information display." use Phoenix.LiveView, layout: {DalaDev.Server.Layouts, :app} alias DalaDev.Observer @refresh_interval 5_000 def mount(_params, _session, socket) do if connected?(socket), do: :timer.send_interval(@refresh_interval, self(), :refresh) socket = socket |> assign(:node, Node.self()) |> assign(:available_nodes, [Node.self() | Node.list()]) |> assign(:system_info, %{}) |> assign(:error, nil) |> assign(:loading, false) {:ok, fetch_system(socket)} end def handle_params(%{"node" => node_str}, _uri, socket) do try do node = String.to_existing_atom(":#{node_str}") {:noreply, assign(socket, :node, node) |> fetch_system()} rescue _ -> {:noreply, assign(socket, :error, "Invalid node name: #{node_str}")} end end def handle_params(_params, _uri, socket), do: {:noreply, socket} def handle_info(:refresh, socket), do: {:noreply, fetch_system(socket)} def handle_event("refresh", _params, socket), do: {:noreply, fetch_system(socket)} def handle_event("select_node", %{"node" => node_str}, socket) do try do node = String.to_existing_atom(node_str) {:noreply, assign(socket, :node, node) |> fetch_system()} rescue _ -> {:noreply, assign(socket, :error, "Invalid node: #{node_str}")} end end def render(assigns) do ~H"""
← Back to Dashboard

System Info: <%= @node %>

<%= if @error do %>

<%= @error %>

<% end %>

Basic Information

System Version
<%= @system_info[:system_version] || "N/A" %>
Uptime
<%= format_uptime(@system_info[:uptime_ms]) %>
Word Size
<%= @system_info[:wordsize] || "N/A" %> bits
Process Count
<%= @system_info[:process_count] || "N/A" %>
ETS Tables
<%= @system_info[:ets_tables_count] || "N/A" %>

Memory Usage

<%= if memory = @system_info[:memory] do %>
<%= for {key, label} <- [total: "Total", processes: "Processes", atom: "Atom", binary: "Binary", code: "Code", ets: "ETS"] do %>
<%= label %>
<%= format_bytes(memory[key]) %>
<% end %>
<% end %>
""" end defp fetch_system(socket) do node = socket.assigns[:node] socket = socket |> assign(:loading, true) |> assign(:available_nodes, [Node.self() | Node.list()]) case Observer.system_info(node) do info when is_map(info) -> assign(socket, :system_info, info) |> assign(:error, nil) |> assign(:loading, false) _ -> assign(socket, :error, "Failed to get system info for #{inspect(node)}") |> assign(:loading, false) end end defp format_bytes(nil), do: "0 B" defp format_bytes(bytes) when bytes < 1024, do: "#{bytes} B" defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 1)} KB" defp format_bytes(bytes) when bytes < 1024 * 1024 * 1024, do: "#{Float.round(bytes / (1024 * 1024), 1)} MB" defp format_bytes(bytes), do: "#{Float.round(bytes / (1024 * 1024 * 1024), 1)} GB" defp format_uptime(nil), do: "N/A" defp format_uptime(ms) do seconds = div(ms, 1000) minutes = div(seconds, 60) hours = div(minutes, 60) days = div(hours, 24) cond do days > 0 -> "#{days}d #{rem(hours, 24)}h" hours > 0 -> "#{hours}h #{rem(minutes, 60)}m" minutes > 0 -> "#{minutes}m #{rem(seconds, 60)}s" true -> "#{seconds}s" end end end