defmodule ExESDBDashboard.ClusterLive do @moduledoc """ Main cluster dashboard LiveView component. Displays real-time cluster information including: - Connected nodes and their status - Available stores and statistics - Overall cluster health - Real-time updates via PubSub """ use Phoenix.LiveView alias ExESDBDashboard alias ExESDBGater.Messages.{HealthMessages, LifecycleMessages, SystemMessages} @impl true def mount(_params, _session, socket) do # Subscribe to structured message topics if connected?(socket) do # Subscribe to health updates Phoenix.PubSub.subscribe(:ex_esdb_health, "cluster_health") Phoenix.PubSub.subscribe(:ex_esdb_health, "node_health") # Subscribe to lifecycle events Phoenix.PubSub.subscribe(:ex_esdb_lifecycle, "cluster_membership") Phoenix.PubSub.subscribe(:ex_esdb_lifecycle, "node_lifecycle") # Subscribe to system events Phoenix.PubSub.subscribe(:ex_esdb_system, "lifecycle") # Set up periodic refresh as fallback :timer.send_interval(30_000, self(), :refresh_data) end # Load initial cluster data cluster_data = ExESDBDashboard.get_cluster_data() socket = socket |> assign(:cluster_data, cluster_data) |> assign(:loading, false) |> assign(:last_updated, DateTime.utc_now()) |> assign(:page_title, "Cluster Dashboard") {:ok, socket} end @impl true def handle_params(params, _url, socket) do action = Map.get(params, "live_action", :cluster) {:noreply, assign(socket, :live_action, action)} end @impl true def handle_info(:refresh_data, socket) do cluster_data = ExESDBDashboard.get_cluster_data() socket = socket |> assign(:cluster_data, cluster_data) |> assign(:last_updated, DateTime.utc_now()) {:noreply, socket} end # Handle structured health messages @impl true def handle_info({:secure_message, _signature, {:cluster_health_updated, _payload}} = message, socket) do case HealthMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:secure_message, _signature, {:node_health_updated, _payload}} = message, socket) do case HealthMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end # Handle structured lifecycle messages @impl true def handle_info({:secure_message, _signature, {:cluster_membership_changed, _payload}} = message, socket) do case LifecycleMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:secure_message, _signature, {:node_lifecycle_event, _payload}} = message, socket) do case LifecycleMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end # Handle structured system messages @impl true def handle_info({:secure_message, _signature, {:system_lifecycle_event, _payload}} = message, socket) do case SystemMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end # Handle unsecured messages (dev/test environments) @impl true def handle_info({:unsecured_message, {:cluster_health_updated, _payload}} = message, socket) do case HealthMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:unsecured_message, {:node_health_updated, _payload}} = message, socket) do case HealthMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:unsecured_message, {:cluster_membership_changed, _payload}} = message, socket) do case LifecycleMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:unsecured_message, {:node_lifecycle_event, _payload}} = message, socket) do case LifecycleMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:unsecured_message, {:system_lifecycle_event, _payload}} = message, socket) do case SystemMessages.validate_secure_message(message) do {:ok, _} -> handle_cluster_update(socket) {:error, _} -> {:noreply, socket} end end # Handle legacy messages during transition period @impl true def handle_info({:cluster_state_changed, _new_state}, socket) do handle_cluster_update(socket) end # Ignore unknown messages @impl true def handle_info(_msg, socket) do {:noreply, socket} end # Common cluster update handler defp handle_cluster_update(socket) do cluster_data = ExESDBDashboard.get_cluster_data() socket = socket |> assign(:cluster_data, cluster_data) |> assign(:last_updated, DateTime.utc_now()) {:noreply, socket} end @impl true def handle_event("refresh", _params, socket) do cluster_data = ExESDBDashboard.get_cluster_data() socket = socket |> assign(:cluster_data, cluster_data) |> assign(:last_updated, DateTime.utc_now()) |> put_flash(:info, "Cluster data refreshed") {:noreply, socket} end @impl true def render(assigns) do ~H"""
<%= render_slot(@subtitle) %>
No stores available. Ensure ExESDB nodes are connected and running.