defmodule LiveDebuggerWeb.ChannelDashboardLive do @moduledoc false use LiveDebuggerWeb, :live_view require Logger alias LiveDebugger.Structs.LvProcess alias Phoenix.LiveView.JS alias LiveDebugger.Utils.URL alias Phoenix.LiveView.AsyncResult alias LiveDebuggerWeb.Components.Error alias LiveDebugger.Structs.TreeNode alias LiveDebugger.Services.LiveViewDiscoveryService alias LiveDebugger.Utils.Parsers alias LiveDebuggerWeb.Helpers.RoutesHelper alias LiveDebuggerWeb.StateLive alias LiveDebuggerWeb.TracesLive alias LiveDebuggerWeb.SidebarLive alias LiveDebugger.Utils.PubSub, as: PubSubUtils @impl true def mount(params, _session, socket) do socket |> assign(:socket_id, params["socket_id"]) |> start_async_assign_lv_process(params) |> ok() end @impl true def handle_params(params, url, socket) do socket |> assign_node_id(params) |> assign(:url, URL.to_relative(url)) |> noreply() end @impl true def render(assigns) do ~H"""
<.navbar return_link?={true}>
<.nav_icon :if={@lv_process.ok?} phx-click={JS.push("open-sidebar", target: "#sidebar")} class="flex lg:hidden" icon="icon-menu-hamburger" />
<.async_result :let={lv_process} assign={@lv_process}> <:loading>
<.spinner size="xl" />
<:failed :let={reason}>
""" end @impl true def handle_async(:fetch_lv_process, {:ok, nil}, socket) do with %{debugged_module: module} when not is_nil(module) <- socket.assigns, %LvProcess{socket_id: socket_id, transport_pid: transport_pid} <- LiveViewDiscoveryService.successor_lv_process(module) do socket |> push_navigate(to: RoutesHelper.channel_dashboard(socket_id, transport_pid)) |> noreply() else _ -> socket |> assign( :lv_process, AsyncResult.failed(socket.assigns.lv_process, :not_found) ) |> noreply() end end def handle_async(:fetch_lv_process, {:ok, fetched_lv_process}, socket) do subscribe_process_state(fetched_lv_process.pid) socket |> assign(:lv_process, AsyncResult.ok(fetched_lv_process)) |> assign(:debugged_module, fetched_lv_process.module) |> patch_transport_pid(fetched_lv_process) |> noreply() end def handle_async(:fetch_lv_process, {:exit, reason}, socket) do Logger.error( "LiveDebugger encountered unexpected error while fetching information for process: #{inspect(reason)}" ) socket |> assign( :lv_process, AsyncResult.failed(socket.assigns.lv_process, reason) ) |> noreply() end @impl true def handle_info({:process_status, :dead}, socket) do socket |> push_patch(to: URL.remove_query_param(socket.assigns.url, "node_id")) |> start_async_assign_lv_process(%{"socket_id" => socket.assigns.socket_id}) |> noreply() end defp assign_node_id(socket, %{"node_id" => node_id}) do case TreeNode.id_from_string(node_id) do {:ok, id} -> socket.id |> PubSubUtils.node_changed_topic() |> PubSubUtils.broadcast({:node_changed, id}) assign(socket, :node_id, id) :error -> Logger.error("Invalid node_id: #{inspect(node_id)}") socket |> push_flash("Invalid `node_id` param") |> assign(:node_id, nil) end end defp assign_node_id(socket, _params) do assign(socket, :node_id, nil) end defp start_async_assign_lv_process(socket, %{ "socket_id" => socket_id, "transport_pid" => transport_pid }) do case Parsers.string_to_pid(transport_pid) do {:ok, pid} -> socket |> assign(:lv_process, AsyncResult.loading()) |> start_async(:fetch_lv_process, fn -> fetch_lv_process(socket_id, pid) end) :error -> assign( socket, :lv_process, AsyncResult.failed(AsyncResult.loading(), :invalid_transport_pid) ) end end defp start_async_assign_lv_process(socket, %{"socket_id" => socket_id}) do socket |> assign(:lv_process, AsyncResult.loading()) |> start_async(:fetch_lv_process, fn -> fetch_lv_process(socket_id) end) end defp fetch_lv_process(socket_id, transport_pid \\ nil) do fetch_after = fn milliseconds -> Process.sleep(milliseconds) LiveViewDiscoveryService.lv_process(socket_id, transport_pid) end with nil <- fetch_after.(200), nil <- fetch_after.(800) do fetch_after.(1000) end end defp patch_transport_pid(socket, lv_process) do path = RoutesHelper.channel_dashboard(lv_process.socket_id, lv_process.transport_pid) url = URL.update_path(socket.assigns.url, path) push_patch(socket, to: url) end defp subscribe_process_state(pid) do pid |> PubSubUtils.process_status_topic() |> PubSubUtils.subscribe!() end end