defmodule LivebookWeb.SessionLive.AttachedLive do use LivebookWeb, :live_view import Ecto.Changeset alias Livebook.{Session, Runtime} @impl true def mount( _params, %{"session_pid" => session_pid, "current_runtime" => current_runtime}, socket ) do session = Session.get_by_pid(session_pid) unless Livebook.Config.runtime_enabled?(Livebook.Runtime.Attached) do raise "runtime module not allowed" end if connected?(socket) do Session.subscribe(session.id) end {:ok, assign(socket, session: session, current_runtime: current_runtime, error_message: nil, changeset: changeset(current_runtime) )} end defp changeset(runtime, attrs \\ %{}) do data = case runtime do %Runtime.Attached{node: node, cookie: cookie} -> %{name: Atom.to_string(node), cookie: Atom.to_string(cookie)} _ -> %{name: nil, cookie: nil} end types = %{name: :string, cookie: :string} cast({data, types}, attrs, [:name, :cookie]) |> validate_required([:name, :cookie]) end @impl true def render(assigns) do ~H"""
<%= @error_message %>

Connect the session to an already running node and evaluate code in the context of that node. The node must run Erlang/OTP <%= :erlang.system_info(:otp_release) %> and Elixir <%= System.version() %> (or later). Make sure to give the node a name and a cookie, for example:

iex --name <%= test_node() %> --cookie mycookie -S mix

Then enter the connection information below:

<.form :let={f} for={@changeset} as={:data} phx-submit="init" phx-change="validate" autocomplete="off" spellcheck="false" >
<.text_field field={f[:name]} label="Name" placeholder={test_node()} /> <.text_field field={f[:cookie]} label="Cookie" placeholder="mycookie" />
<.button type="submit" disabled={not @changeset.valid?}> <%= if(reconnecting?(@changeset), do: "Reconnect", else: "Connect") %>
""" end @impl true def handle_event("validate", %{"data" => data}, socket) do changeset = socket.assigns.current_runtime |> changeset(data) |> Map.replace!(:action, :validate) {:noreply, assign(socket, changeset: changeset)} end def handle_event("init", %{"data" => data}, socket) do socket.assigns.current_runtime |> changeset(data) |> apply_action(:insert) |> case do {:ok, data} -> node = String.to_atom(data.name) cookie = String.to_atom(data.cookie) runtime = Runtime.Attached.new(node, cookie) case Runtime.connect(runtime) do {:ok, runtime} -> Session.set_runtime(socket.assigns.session.pid, runtime) {:noreply, assign(socket, changeset: changeset(runtime), error_message: nil)} {:error, message} -> {:noreply, assign(socket, changeset: changeset(socket.assigns.current_runtime, data), error_message: Livebook.Utils.upcase_first(message) )} end {:error, changeset} -> {:noreply, assign(socket, changeset: changeset)} end end @impl true def handle_info({:operation, {:set_runtime, _pid, runtime}}, socket) do {:noreply, assign(socket, current_runtime: runtime)} end def handle_info(_message, socket), do: {:noreply, socket} defp reconnecting?(changeset) do changeset.valid? and changeset.data == apply_changes(changeset) end defp test_node() do "test@#{Livebook.Utils.node_host()}" end end