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 />
+
+
+ <.text_field field={f[:erl_flags]} label="Erl flags" />
+
+
+ <.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}}