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"""
<%= if @error do %>
<% 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