lib/phoenix/node_dashboard/component.ex

defmodule Phoenix.NodeDashboard.Component do
  use Phoenix.LiveComponent
  use Phoenix.HTML
  require Logger
  alias Phoenix.NodeDashboard.Instance

  @impl true
  def mount(socket) do
    nodes =
      Enum.map([Node.self()] ++ Node.list(), fn node ->
        %{node: node, name: node, status: "loading"}
      end)

    {:ok, assign(socket, :nodes, nodes)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div style="display: flex; flex-direction: column;">
      <.title text="Nodes" />
      <.node_grid nodes={@nodes} />
    </div>
    """
  end

  @impl true
  def update(%{selected_node: selected_node}, socket) do
    socket =
      if connected?(socket) do
        assign(socket, :nodes, fetch_nodes())
      else
        socket
      end

    {:ok, assign(socket, selected_node: selected_node)}
  end

  defp title(assigns) do
    ~H"""
    <h5 style="font-size: 1.25rem; margin-bottom: 0.75rem;">
      <%= @text %>
    </h5>
    """
  end

  defp node_grid(assigns) do
    ~H"""
    <div style="display: grid; gap: 1rem; grid-template-columns: repeat(3, minmax(0, 1fr));">
      <%= for node <- @nodes do %>
        <.node_card node={node} />
      <% end %>
    </div>
    """
  end

  defp node_card(assigns) do
    ~H"""
    <div style="display: flex; flex-direction: row; background-color: white; border-radius: 0.25rem; padding: 1rem;">
      <.node_detail node={@node} />
    </div>
    """
  end

  defp node_detail(%{node: %{status: "loaded"}} = assigns) do
    ~H"""
    <%= @node.node %>
    <%= @node.uptime %>
    <%= if @node.node == Node.self() do %>
      <span class="">*Current*</span>
    <% end %>
    """
  end

  defp node_detail(%{node: %{status: "loading"}} = assigns) do
    ~H"""
    <div style="display: flex; flex-direction: row; align-items: center; gap: 0.5rem">
      <svg
        style="width: 1rem; height: 1rem"
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
      >
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
        </circle>
        <path
          class="opacity-75"
          fill="currentColor"
          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
        >
        </path>
      </svg>
      <%= @node.name %>
    </div>
    """
  end

  defp fetch_nodes() do
    ([Node.self()] ++ Node.list())
    |> Enum.map(fn node ->
      Logger.debug("#{__MODULE__}#update/2 checking node #{node}")
      Instance.rpc(node)
    end)
  end
end