diff --git a/lib/livebook/runtime/standalone.ex b/lib/livebook/runtime/standalone.ex index 1fa1c7dd3..f6ad34e32 100644 --- a/lib/livebook/runtime/standalone.ex +++ b/lib/livebook/runtime/standalone.ex @@ -1,5 +1,5 @@ defmodule Livebook.Runtime.Standalone do - defstruct [:node, :server_pid] + defstruct [:erl_flags, :node, :server_pid] # A runtime backed by a standalone Elixir node managed by Livebook. # @@ -31,16 +31,23 @@ defmodule Livebook.Runtime.Standalone do alias Livebook.Utils @type t :: %__MODULE__{ + erl_flags: String.t() | nil, node: node() | nil, server_pid: pid() | nil } @doc """ Returns a new runtime instance. + + ## Options + + * `:erl_flags` - erl flags to specify when starting the node + """ - @spec new() :: t() - def new() do - %__MODULE__{} + @spec new(keyword()) :: t() + def new(opts \\ []) do + opts = Keyword.validate!(opts, [:erl_flags]) + %__MODULE__{erl_flags: opts[:erl_flags]} end def __connect__(runtime) do @@ -66,7 +73,7 @@ defmodule Livebook.Runtime.Standalone do ] with {:ok, elixir_path} <- find_elixir_executable(), - port = start_elixir_node(elixir_path, child_node), + port = start_elixir_node(elixir_path, child_node, runtime.erl_flags), {:ok, server_pid} <- parent_init_sequence(child_node, port, init_opts) do runtime = %{runtime | node: child_node, server_pid: server_pid} send(caller, {:runtime_connect_done, self(), {:ok, runtime}}) @@ -84,7 +91,7 @@ defmodule Livebook.Runtime.Standalone do end end - defp start_elixir_node(elixir_path, node_name) do + defp start_elixir_node(elixir_path, node_name, erl_flags) do # Here we create a port to start the system process in a non-blocking way. Port.open({:spawn_executable, elixir_path}, [ :binary, @@ -93,7 +100,7 @@ defmodule Livebook.Runtime.Standalone do # to the terminal :nouse_stdio, :hide, - args: elixir_flags(node_name) + args: elixir_flags(node_name, erl_flags) ]) end @@ -167,7 +174,7 @@ defmodule Livebook.Runtime.Standalone do |> Base.encode64() end - defp elixir_flags(node_name) do + defp elixir_flags(node_name, erl_flags) do parent_name = node() parent_port = Livebook.EPMD.dist_port() @@ -190,7 +197,8 @@ defmodule Livebook.Runtime.Standalone do # startup # "+sbwt none +sbwtdcpu none +sbwtdio none +sssdio 128 -elixir ansi_enabled true -noinput " <> - "-epmd_module Elixir.Livebook.Runtime.EPMD", + "-epmd_module Elixir.Livebook.Runtime.EPMD " <> + (erl_flags || ""), # Add the location of Livebook.Runtime.EPMD "-pa", epmd_module_path!(), @@ -247,8 +255,8 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.Standalone do :ok = RuntimeServer.stop(runtime.server_pid) end - def duplicate(_runtime) do - Livebook.Runtime.Standalone.new() + def duplicate(runtime) do + Livebook.Runtime.Standalone.new(erl_flags: runtime.erl_flags) end def evaluate_code(runtime, language, code, locator, parent_locators, opts \\ []) do diff --git a/lib/livebook_web/live/apps_dashboard_live.ex b/lib/livebook_web/live/apps_dashboard_live.ex index d978f8385..24a30926d 100644 --- a/lib/livebook_web/live/apps_dashboard_live.ex +++ b/lib/livebook_web/live/apps_dashboard_live.ex @@ -60,7 +60,7 @@ defmodule LivebookWeb.AppsDashboardLive do
@@ -68,11 +68,11 @@ defmodule LivebookWeb.AppsDashboardLive do
<.app_group_tag app_spec={app.app_spec} /> - <.remix_icon icon="arrow-drop-down-line" class="text-3xl text-gray-400 toggle" /> - <.remix_icon icon="arrow-drop-right-line" class="text-3xl text-gray-400 hidden toggle" /> + <.remix_icon icon="arrow-down-s-line" class="text-xl text-gray-700" data-toggle /> + <.remix_icon icon="arrow-right-s-line" class="text-xl text-gray-700 hidden" data-toggle />
-
+
<.message_box :for={warning <- app.warnings} kind={:warning} message={warning} />
diff --git a/lib/livebook_web/live/session_live/attached_runtime_component.ex b/lib/livebook_web/live/session_live/attached_runtime_component.ex index ba5e55828..9ad3ad596 100644 --- a/lib/livebook_web/live/session_live/attached_runtime_component.ex +++ b/lib/livebook_web/live/session_live/attached_runtime_component.ex @@ -78,7 +78,7 @@ defmodule LivebookWeb.SessionLive.AttachedRuntimeComponent do autocomplete="off" spellcheck="false" > -
+
<.text_field field={f[:name]} label="Name" placeholder={test_node()} /> <.text_field field={f[:cookie]} label="Cookie" placeholder="mycookie" />
diff --git a/lib/livebook_web/live/session_live/standalone_runtime_component.ex b/lib/livebook_web/live/session_live/standalone_runtime_component.ex index d0ed848d0..50f797830 100644 --- a/lib/livebook_web/live/session_live/standalone_runtime_component.ex +++ b/lib/livebook_web/live/session_live/standalone_runtime_component.ex @@ -1,6 +1,8 @@ defmodule LivebookWeb.SessionLive.StandaloneRuntimeComponent do use LivebookWeb, :live_component + import Ecto.Changeset + alias Livebook.{Session, Runtime} @impl true @@ -12,30 +14,115 @@ defmodule LivebookWeb.SessionLive.StandaloneRuntimeComponent do {:ok, socket} end + @impl true + def update(assigns, socket) do + changeset = + case socket.assigns[:changeset] do + nil -> + changeset(assigns.runtime) + + changeset when socket.assigns.runtime == assigns.runtime -> + changeset + + changeset -> + changeset(assigns.runtime, changeset.params) + end + + socket = + socket + |> assign(assigns) + |> assign(:changeset, changeset) + + {:ok, socket} + end + + defp changeset(runtime, attrs \\ %{}) do + data = + case runtime do + %Runtime.Standalone{erl_flags: erl_flags} -> + %{erl_flags: erl_flags} + + _ -> + %{erl_flags: nil} + end + + types = %{erl_flags: :string} + + cast({data, types}, attrs, [:erl_flags]) + end + @impl true def render(assigns) do ~H""" -
+

Start a new local Elixir node to evaluate code. Whenever you reconnect this runtime, a fresh node is started.

- <.button phx-click="init" phx-target={@myself} disabled={@runtime_status == :connecting}> - <%= label(@runtime, @runtime_status) %> - + <.form + :let={f} + for={@changeset} + as={:data} + phx-submit="init" + phx-change="validate" + phx-target={@myself} + autocomplete="off" + spellcheck="false" + > +
+
+ Advanced configuration + <.remix_icon icon="arrow-down-s-line" class="text-xl hidden" data-toggle /> + <.remix_icon icon="arrow-right-s-line" class="text-xl" data-toggle /> +
+ +
+ <.button type="submit" disabled={@runtime_status == :connecting or not @changeset.valid?}> + <%= label(@changeset, @runtime_status) %> + +
""" end - defp label(%Runtime.Standalone{}, :connecting), do: "Connecting..." - defp label(%Runtime.Standalone{}, :connected), do: "Reconnect" - defp label(_runtime, _runtime_status), do: "Connect" + defp label(changeset, runtime_status) do + reconnecting? = changeset.valid? and changeset.data == apply_changes(changeset) + + case {reconnecting?, runtime_status} do + {true, :connected} -> "Reconnect" + {true, :connecting} -> "Connecting..." + _ -> "Connect" + end + end @impl true - def handle_event("init", _params, socket) do - runtime = Runtime.Standalone.new() - Session.set_runtime(socket.assigns.session.pid, runtime) - Session.connect_runtime(socket.assigns.session.pid) - {:noreply, socket} + def handle_event("validate", %{"data" => data}, socket) do + changeset = + socket.assigns.runtime + |> changeset(data) + |> Map.replace!(:action, :validate) + + {:noreply, assign(socket, changeset: changeset)} + end + + def handle_event("init", %{"data" => data}, socket) do + socket.assigns.runtime + |> changeset(data) + |> apply_action(:insert) + |> case do + {:ok, data} -> + runtime = Runtime.Standalone.new(erl_flags: data.erl_flags) + Session.set_runtime(socket.assigns.session.pid, runtime) + Session.connect_runtime(socket.assigns.session.pid) + {:noreply, socket} + + {:error, changeset} -> + {:noreply, assign(socket, changeset: changeset)} + end end end diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index 92c4c4b41..cc4560302 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -907,8 +907,8 @@ defmodule LivebookWeb.SessionLiveTest do |> render_click() view - |> element("#runtime-settings-modal button", "Connect") - |> render_click() + |> element("#runtime-settings-modal form") + |> render_submit(%{data: %{}}) assert_receive {:operation, {:set_runtime, _pid, %Runtime.Standalone{}}} assert_receive {:operation, {:runtime_connected, _pid, %Runtime.Standalone{} = runtime}}