mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-07 05:24:40 +08:00
List connected distribution nodes in the runtime panel (#2636)
This commit is contained in:
parent
87daabaf60
commit
68fa363d2d
14 changed files with 256 additions and 42 deletions
|
@ -244,6 +244,11 @@ solely client-side operations.
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-el-session][data-js-side-panel-content="runtime-info"]
|
||||||
|
[data-el-runtime-indicator] {
|
||||||
|
@apply border-gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
[data-el-session]:not([data-js-side-panel-content="app-info"])
|
[data-el-session]:not([data-js-side-panel-content="app-info"])
|
||||||
[data-el-app-info] {
|
[data-el-app-info] {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
|
|
|
@ -765,6 +765,17 @@ defprotocol Livebook.Runtime do
|
||||||
|
|
||||||
@type proxy_handler_spec :: {module :: module(), function :: atom(), args :: list()}
|
@type proxy_handler_spec :: {module :: module(), function :: atom(), args :: list()}
|
||||||
|
|
||||||
|
@typedoc """
|
||||||
|
An information about Elixir nodes that the runtime is connected to.
|
||||||
|
|
||||||
|
Whenever the node list change, the runtime should send an updated
|
||||||
|
list as:
|
||||||
|
|
||||||
|
* `{:runtime_connected_nodes, connected_nodes()}`
|
||||||
|
|
||||||
|
"""
|
||||||
|
@type connected_nodes :: list(node())
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns relevant information about the runtime.
|
Returns relevant information about the runtime.
|
||||||
|
|
||||||
|
@ -1116,4 +1127,13 @@ defprotocol Livebook.Runtime do
|
||||||
"""
|
"""
|
||||||
@spec fetch_proxy_handler_spec(t()) :: {:ok, proxy_handler_spec()} | {:error, :not_found}
|
@spec fetch_proxy_handler_spec(t()) :: {:ok, proxy_handler_spec()} | {:error, :not_found}
|
||||||
def fetch_proxy_handler_spec(runtime)
|
def fetch_proxy_handler_spec(runtime)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Asks the runtime to disconnect from the given connected node.
|
||||||
|
|
||||||
|
The node should be one of `connected_nodes()` reported by the runtime
|
||||||
|
earlier.
|
||||||
|
"""
|
||||||
|
@spec disconnect_node(t(), node()) :: :ok
|
||||||
|
def disconnect_node(runtime, node)
|
||||||
end
|
end
|
||||||
|
|
|
@ -208,4 +208,8 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.Attached do
|
||||||
def fetch_proxy_handler_spec(runtime) do
|
def fetch_proxy_handler_spec(runtime) do
|
||||||
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disconnect_node(runtime, node) do
|
||||||
|
RuntimeServer.disconnect_node(runtime.server_pid, node)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -327,4 +327,8 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.ElixirStandalone do
|
||||||
def fetch_proxy_handler_spec(runtime) do
|
def fetch_proxy_handler_spec(runtime) do
|
||||||
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disconnect_node(runtime, node) do
|
||||||
|
RuntimeServer.disconnect_node(runtime.server_pid, node)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -175,6 +175,10 @@ defimpl Livebook.Runtime, for: Livebook.Runtime.Embedded do
|
||||||
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
RuntimeServer.fetch_proxy_handler_spec(runtime.server_pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disconnect_node(runtime, node) do
|
||||||
|
RuntimeServer.disconnect_node(runtime.server_pid, node)
|
||||||
|
end
|
||||||
|
|
||||||
defp config() do
|
defp config() do
|
||||||
Application.get_env(:livebook, Livebook.Runtime.Embedded, [])
|
Application.get_env(:livebook, Livebook.Runtime.Embedded, [])
|
||||||
end
|
end
|
||||||
|
|
|
@ -328,6 +328,10 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disconnect_node(pid, node) do
|
||||||
|
GenServer.cast(pid, {:disconnect_node, node})
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Stops the runtime server.
|
Stops the runtime server.
|
||||||
|
|
||||||
|
@ -362,10 +366,11 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
smart_cell_supervisor: nil,
|
smart_cell_supervisor: nil,
|
||||||
smart_cell_gl: nil,
|
smart_cell_gl: nil,
|
||||||
smart_cells: %{},
|
smart_cells: %{},
|
||||||
smart_cell_definitions: nil,
|
smart_cell_definitions: [],
|
||||||
smart_cell_definitions_module:
|
smart_cell_definitions_module:
|
||||||
Keyword.get(opts, :smart_cell_definitions_module, Kino.SmartCell),
|
Keyword.get(opts, :smart_cell_definitions_module, Kino.SmartCell),
|
||||||
extra_smart_cell_definitions: Keyword.get(opts, :extra_smart_cell_definitions, []),
|
extra_smart_cell_definitions: Keyword.get(opts, :extra_smart_cell_definitions, []),
|
||||||
|
connected_nodes: [],
|
||||||
memory_timer_ref: nil,
|
memory_timer_ref: nil,
|
||||||
last_evaluator: nil,
|
last_evaluator: nil,
|
||||||
base_env_path:
|
base_env_path:
|
||||||
|
@ -408,7 +413,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
def handle_info({:evaluation_finished, locator}, state) do
|
def handle_info({:evaluation_finished, locator}, state) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
state
|
state
|
||||||
|> report_smart_cell_definitions()
|
|> report_environment()
|
||||||
|> report_transient_state()
|
|> report_transient_state()
|
||||||
|> scan_binding_after_evaluation(locator)}
|
|> scan_binding_after_evaluation(locator)}
|
||||||
end
|
end
|
||||||
|
@ -480,7 +485,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
Process.monitor(owner)
|
Process.monitor(owner)
|
||||||
|
|
||||||
state = %{state | owner: owner, runtime_broadcast_to: opts[:runtime_broadcast_to]}
|
state = %{state | owner: owner, runtime_broadcast_to: opts[:runtime_broadcast_to]}
|
||||||
state = report_smart_cell_definitions(state)
|
state = report_environment(state)
|
||||||
report_memory_usage(state)
|
report_memory_usage(state)
|
||||||
|
|
||||||
{:ok, smart_cell_supervisor} = DynamicSupervisor.start_link(strategy: :one_for_one)
|
{:ok, smart_cell_supervisor} = DynamicSupervisor.start_link(strategy: :one_for_one)
|
||||||
|
@ -710,6 +715,11 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_cast({:disconnect_node, node}, state) do
|
||||||
|
Node.disconnect(node)
|
||||||
|
{:noreply, report_connected_nodes(state)}
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call({:read_file, path}, {from_pid, _}, state) do
|
def handle_call({:read_file, path}, {from_pid, _}, state) do
|
||||||
# Delegate reading to a separate task and let the caller
|
# Delegate reading to a separate task and let the caller
|
||||||
|
@ -817,6 +827,12 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
send(state.owner, {:runtime_memory_usage, Evaluator.memory()})
|
send(state.owner, {:runtime_memory_usage, Evaluator.memory()})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp report_environment(state) do
|
||||||
|
state
|
||||||
|
|> report_smart_cell_definitions()
|
||||||
|
|> report_connected_nodes()
|
||||||
|
end
|
||||||
|
|
||||||
defp report_smart_cell_definitions(state) do
|
defp report_smart_cell_definitions(state) do
|
||||||
smart_cell_definitions = get_smart_cell_definitions(state.smart_cell_definitions_module)
|
smart_cell_definitions = get_smart_cell_definitions(state.smart_cell_definitions_module)
|
||||||
|
|
||||||
|
@ -837,6 +853,19 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp report_connected_nodes(state) do
|
||||||
|
owner_node = node(state.owner)
|
||||||
|
nodes = Node.list(:connected) |> List.delete(owner_node) |> Enum.sort()
|
||||||
|
|
||||||
|
if nodes == state.connected_nodes do
|
||||||
|
state
|
||||||
|
else
|
||||||
|
send(state.owner, {:runtime_connected_nodes, nodes})
|
||||||
|
|
||||||
|
%{state | connected_nodes: nodes}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_smart_cell_definitions(module) do
|
defp get_smart_cell_definitions(module) do
|
||||||
if Code.ensure_loaded?(module) and function_exported?(module, :definitions, 0) do
|
if Code.ensure_loaded?(module) and function_exported?(module, :definitions, 0) do
|
||||||
module.definitions()
|
module.definitions()
|
||||||
|
|
|
@ -1735,6 +1735,11 @@ defmodule Livebook.Session do
|
||||||
{:noreply, state |> put_memory_usage(runtime_memory) |> notify_update()}
|
{:noreply, state |> put_memory_usage(runtime_memory) |> notify_update()}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({:runtime_connected_nodes, nodes}, state) do
|
||||||
|
operation = {:set_runtime_connected_nodes, @client_id, nodes}
|
||||||
|
{:noreply, handle_operation(state, operation)}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info({:runtime_smart_cell_definitions, definitions}, state) do
|
def handle_info({:runtime_smart_cell_definitions, definitions}, state) do
|
||||||
operation = {:set_smart_cell_definitions, @client_id, definitions}
|
operation = {:set_smart_cell_definitions, @client_id, definitions}
|
||||||
{:noreply, handle_operation(state, operation)}
|
{:noreply, handle_operation(state, operation)}
|
||||||
|
|
|
@ -28,6 +28,7 @@ defmodule Livebook.Session.Data do
|
||||||
:bin_entries,
|
:bin_entries,
|
||||||
:runtime,
|
:runtime,
|
||||||
:runtime_transient_state,
|
:runtime_transient_state,
|
||||||
|
:runtime_connected_nodes,
|
||||||
:smart_cell_definitions,
|
:smart_cell_definitions,
|
||||||
:clients_map,
|
:clients_map,
|
||||||
:users_map,
|
:users_map,
|
||||||
|
@ -55,6 +56,7 @@ defmodule Livebook.Session.Data do
|
||||||
bin_entries: list(cell_bin_entry()),
|
bin_entries: list(cell_bin_entry()),
|
||||||
runtime: Runtime.t(),
|
runtime: Runtime.t(),
|
||||||
runtime_transient_state: Runtime.transient_state(),
|
runtime_transient_state: Runtime.transient_state(),
|
||||||
|
runtime_connected_nodes: list(node()),
|
||||||
smart_cell_definitions: list(Runtime.smart_cell_definition()),
|
smart_cell_definitions: list(Runtime.smart_cell_definition()),
|
||||||
clients_map: %{client_id() => User.id()},
|
clients_map: %{client_id() => User.id()},
|
||||||
users_map: %{User.id() => User.t()},
|
users_map: %{User.id() => User.t()},
|
||||||
|
@ -214,6 +216,7 @@ defmodule Livebook.Session.Data do
|
||||||
| {:set_input_value, client_id(), input_id(), value :: term()}
|
| {:set_input_value, client_id(), input_id(), value :: term()}
|
||||||
| {:set_runtime, client_id(), Runtime.t()}
|
| {:set_runtime, client_id(), Runtime.t()}
|
||||||
| {:set_runtime_transient_state, client_id(), Runtime.transient_state()}
|
| {:set_runtime_transient_state, client_id(), Runtime.transient_state()}
|
||||||
|
| {:set_runtime_connected_nodes, client_id(), list(node())}
|
||||||
| {:set_smart_cell_definitions, client_id(), list(Runtime.smart_cell_definition())}
|
| {:set_smart_cell_definitions, client_id(), list(Runtime.smart_cell_definition())}
|
||||||
| {:set_file, client_id(), FileSystem.File.t() | nil}
|
| {:set_file, client_id(), FileSystem.File.t() | nil}
|
||||||
| {:set_autosave_interval, client_id(), non_neg_integer() | nil}
|
| {:set_autosave_interval, client_id(), non_neg_integer() | nil}
|
||||||
|
@ -303,6 +306,7 @@ defmodule Livebook.Session.Data do
|
||||||
bin_entries: [],
|
bin_entries: [],
|
||||||
runtime: default_runtime,
|
runtime: default_runtime,
|
||||||
runtime_transient_state: %{},
|
runtime_transient_state: %{},
|
||||||
|
runtime_connected_nodes: [],
|
||||||
smart_cell_definitions: [],
|
smart_cell_definitions: [],
|
||||||
clients_map: %{},
|
clients_map: %{},
|
||||||
users_map: %{},
|
users_map: %{},
|
||||||
|
@ -871,6 +875,13 @@ defmodule Livebook.Session.Data do
|
||||||
|> wrap_ok()
|
|> wrap_ok()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def apply_operation(data, {:set_runtime_connected_nodes, _client_id, nodes}) do
|
||||||
|
data
|
||||||
|
|> with_actions()
|
||||||
|
|> set_runtime_connected_nodes(nodes)
|
||||||
|
|> wrap_ok()
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(data, {:set_smart_cell_definitions, _client_id, definitions}) do
|
def apply_operation(data, {:set_smart_cell_definitions, _client_id, definitions}) do
|
||||||
data
|
data
|
||||||
|> with_actions()
|
|> with_actions()
|
||||||
|
@ -1955,7 +1966,13 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_runtime(data_actions, prev_data, runtime) do
|
defp set_runtime(data_actions, prev_data, runtime) do
|
||||||
{data, _} = data_actions = set!(data_actions, runtime: runtime, smart_cell_definitions: [])
|
{data, _} =
|
||||||
|
data_actions =
|
||||||
|
set!(data_actions,
|
||||||
|
runtime: runtime,
|
||||||
|
runtime_connected_nodes: [],
|
||||||
|
smart_cell_definitions: []
|
||||||
|
)
|
||||||
|
|
||||||
if not Runtime.connected?(prev_data.runtime) and Runtime.connected?(data.runtime) do
|
if not Runtime.connected?(prev_data.runtime) and Runtime.connected?(data.runtime) do
|
||||||
data_actions
|
data_actions
|
||||||
|
@ -2006,6 +2023,12 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp set_runtime_connected_nodes(data_actions, nodes) do
|
||||||
|
data_actions
|
||||||
|
|> set!(runtime_connected_nodes: nodes)
|
||||||
|
|> maybe_start_smart_cells()
|
||||||
|
end
|
||||||
|
|
||||||
defp set_smart_cell_definitions(data_actions, smart_cell_definitions) do
|
defp set_smart_cell_definitions(data_actions, smart_cell_definitions) do
|
||||||
data_actions
|
data_actions
|
||||||
|> set!(smart_cell_definitions: smart_cell_definitions)
|
|> set!(smart_cell_definitions: smart_cell_definitions)
|
||||||
|
|
|
@ -539,9 +539,11 @@ defmodule LivebookWeb.CoreComponents do
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">
|
||||||
<%= @label %>
|
<%= @label %>
|
||||||
</span>
|
</span>
|
||||||
<span class={
|
<span class={[
|
||||||
"text-gray-800 text-sm font-semibold #{if @one_line, do: "whitespace-nowrap overflow-auto tiny-scrollbar"}"
|
"text-gray-800 text-sm font-semibold",
|
||||||
}>
|
@one_line &&
|
||||||
|
"whitespace-nowrap overflow-hidden text-ellipsis hover:text-clip hover:overflow-auto hover:tiny-scrollbar"
|
||||||
|
]}>
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -578,6 +578,17 @@ defmodule LivebookWeb.SessionLive do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("runtime_disconnect_node", %{"node" => node}, socket) do
|
||||||
|
node = Enum.find(socket.private.data.runtime_connected_nodes, &(Atom.to_string(&1) == node))
|
||||||
|
runtime = socket.private.data.runtime
|
||||||
|
|
||||||
|
if node && Runtime.connected?(runtime) do
|
||||||
|
Runtime.disconnect_node(runtime, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_event("deploy_app", _, socket) do
|
def handle_event("deploy_app", _, socket) do
|
||||||
data = socket.private.data
|
data = socket.private.data
|
||||||
app_settings = data.notebook.app_settings
|
app_settings = data.notebook.app_settings
|
||||||
|
@ -1778,6 +1789,7 @@ defmodule LivebookWeb.SessionLive do
|
||||||
dirty: data.dirty,
|
dirty: data.dirty,
|
||||||
persistence_warnings: data.persistence_warnings,
|
persistence_warnings: data.persistence_warnings,
|
||||||
runtime: data.runtime,
|
runtime: data.runtime,
|
||||||
|
runtime_connected_nodes: Enum.sort(data.runtime_connected_nodes),
|
||||||
smart_cell_definitions: Enum.sort_by(data.smart_cell_definitions, & &1.name),
|
smart_cell_definitions: Enum.sort_by(data.smart_cell_definitions, & &1.name),
|
||||||
example_snippet_definitions:
|
example_snippet_definitions:
|
||||||
data.runtime
|
data.runtime
|
||||||
|
|
|
@ -18,7 +18,12 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
data-p-global-status={hook_prop(elem(@data_view.global_status, 0))}
|
data-p-global-status={hook_prop(elem(@data_view.global_status, 0))}
|
||||||
data-p-autofocus-cell-id={hook_prop(@autofocus_cell_id)}
|
data-p-autofocus-cell-id={hook_prop(@autofocus_cell_id)}
|
||||||
>
|
>
|
||||||
<.sidebar app={@app} session={@session} live_action={@live_action} current_user={@current_user} />
|
<.sidebar
|
||||||
|
session={@session}
|
||||||
|
live_action={@live_action}
|
||||||
|
current_user={@current_user}
|
||||||
|
runtime_connected_nodes={@data_view.runtime_connected_nodes}
|
||||||
|
/>
|
||||||
<.side_panel app={@app} session={@session} data_view={@data_view} client_id={@client_id} />
|
<.side_panel app={@app} session={@session} data_view={@data_view} client_id={@client_id} />
|
||||||
<div class="grow overflow-y-auto relative" data-el-notebook>
|
<div class="grow overflow-y-auto relative" data-el-notebook>
|
||||||
<div data-el-js-view-iframes phx-update="ignore" id="js-view-iframes"></div>
|
<div data-el-js-view-iframes phx-update="ignore" id="js-view-iframes"></div>
|
||||||
|
@ -333,11 +338,21 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
button_attrs={["data-el-clients-list-toggle": true]}
|
button_attrs={["data-el-clients-list-toggle": true]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.button_item
|
<div class="relative">
|
||||||
icon="cpu-line"
|
<.button_item
|
||||||
label="Runtime settings (sr)"
|
icon="cpu-line"
|
||||||
button_attrs={["data-el-runtime-info-toggle": true]}
|
label="Runtime settings (sr)"
|
||||||
/>
|
button_attrs={["data-el-runtime-info-toggle": true]}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
:if={@runtime_connected_nodes != []}
|
||||||
|
data-el-runtime-indicator
|
||||||
|
class={[
|
||||||
|
"absolute w-[12px] h-[12px] border-gray-900 border-2 rounded-full right-1.5 top-1.5 pointer-events-none",
|
||||||
|
"bg-blue-500"
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%!-- Hub functionality --%>
|
<%!-- Hub functionality --%>
|
||||||
|
|
||||||
|
@ -626,7 +641,7 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
</.icon_button>
|
</.icon_button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col mt-2 space-y-4">
|
<div class="flex flex-col mt-2">
|
||||||
<div class="flex flex-col space-y-3">
|
<div class="flex flex-col space-y-3">
|
||||||
<.labeled_text
|
<.labeled_text
|
||||||
:for={{label, value} <- Runtime.describe(@data_view.runtime)}
|
:for={{label, value} <- Runtime.describe(@data_view.runtime)}
|
||||||
|
@ -636,7 +651,7 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
<%= value %>
|
<%= value %>
|
||||||
</.labeled_text>
|
</.labeled_text>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||||
<%= if Runtime.connected?(@data_view.runtime) do %>
|
<%= if Runtime.connected?(@data_view.runtime) do %>
|
||||||
<.button phx-click="reconnect_runtime">
|
<.button phx-click="reconnect_runtime">
|
||||||
<.remix_icon icon="wireless-charging-line" />
|
<.remix_icon icon="wireless-charging-line" />
|
||||||
|
@ -651,21 +666,6 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}>
|
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}>
|
||||||
Configure
|
Configure
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col pt-6 space-y-2">
|
|
||||||
<%= if uses_memory?(@session.memory_usage) do %>
|
|
||||||
<.memory_info memory_usage={@session.memory_usage} />
|
|
||||||
<% else %>
|
|
||||||
<div class="text-sm text-gray-800 flex flex-col">
|
|
||||||
<span class="w-full uppercase font-semibold text-gray-500">Memory</span>
|
|
||||||
<p class="py-1">
|
|
||||||
<%= format_bytes(@session.memory_usage.system.free) %> available out of <%= format_bytes(
|
|
||||||
@session.memory_usage.system.total
|
|
||||||
) %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<.button
|
<.button
|
||||||
:if={Runtime.connected?(@data_view.runtime)}
|
:if={Runtime.connected?(@data_view.runtime)}
|
||||||
|
@ -673,27 +673,50 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
outlined
|
outlined
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="disconnect_runtime"
|
phx-click="disconnect_runtime"
|
||||||
|
class="col-span-2"
|
||||||
>
|
>
|
||||||
Disconnect
|
Disconnect
|
||||||
</.button>
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<.memory_usage_info memory_usage={@session.memory_usage} />
|
||||||
|
|
||||||
|
<.runtime_connected_nodes_info runtime_connected_nodes={@data_view.runtime_connected_nodes} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp memory_info(assigns) do
|
defp memory_usage_info(assigns) do
|
||||||
assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage))
|
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="flex flex-col justify-center">
|
<div class="mt-8 flex flex-col gap-2">
|
||||||
<div class="mb-1 text-sm text-gray-800 flex flex-row justify-between">
|
<div class="text-sm text-gray-800 flex flex-row justify-between">
|
||||||
<span class="text-gray-500 font-semibold uppercase">Memory</span>
|
<span class="text-gray-500 font-semibold uppercase">
|
||||||
<span class="text-right">
|
Memory
|
||||||
|
</span>
|
||||||
|
<span :if={uses_memory?(@memory_usage)}>
|
||||||
<%= format_bytes(@memory_usage.system.free) %> available
|
<%= format_bytes(@memory_usage.system.free) %> available
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-8 flex flex-row py-1 gap-0.5">
|
<%= if uses_memory?(@memory_usage) do %>
|
||||||
|
<.runtime_memory_info memory_usage={@memory_usage} />
|
||||||
|
<% else %>
|
||||||
|
<p class="text-sm text-gray-800">
|
||||||
|
<%= format_bytes(@memory_usage.system.free) %> available out of <%= format_bytes(
|
||||||
|
@memory_usage.system.total
|
||||||
|
) %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp runtime_memory_info(assigns) do
|
||||||
|
assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage))
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="w-full flex flex-row gap-0.5">
|
||||||
<div
|
<div
|
||||||
:for={{type, memory} <- @runtime_memory}
|
:for={{type, memory} <- @runtime_memory}
|
||||||
class={["h-6", memory_color(type)]}
|
class={["h-6", memory_color(type)]}
|
||||||
|
@ -701,15 +724,15 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col py-1">
|
<div class="flex flex-col">
|
||||||
<div :for={{type, memory} <- @runtime_memory} class="flex flex-row items-center">
|
<div :for={{type, memory} <- @runtime_memory} class="flex flex-row items-center">
|
||||||
<span class={["w-4 h-4 mr-2 rounded", memory_color(type)]}></span>
|
<span class={["w-4 h-4 mr-2 rounded", memory_color(type)]}></span>
|
||||||
<span class="capitalize text-gray-700"><%= type %></span>
|
<span class="capitalize text-gray-700"><%= type %></span>
|
||||||
<span class="text-gray-500 ml-auto"><%= memory.unit %></span>
|
<span class="text-gray-500 ml-auto"><%= memory.unit %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex rounded justify-center my-2 py-0.5 text-sm text-gray-800 bg-gray-200">
|
</div>
|
||||||
Total: <%= format_bytes(@memory_usage.runtime.total) %>
|
<div class="flex rounded justify-center py-0.5 text-sm text-gray-800 bg-gray-200">
|
||||||
</div>
|
Total: <%= format_bytes(@memory_usage.runtime.total) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
@ -735,6 +758,41 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp runtime_connected_nodes_info(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div class="mt-8 flex flex-col gap-2">
|
||||||
|
<span class="text-sm text-gray-500 font-semibold uppercase">
|
||||||
|
Connected nodes
|
||||||
|
</span>
|
||||||
|
<%= if @runtime_connected_nodes == [] do %>
|
||||||
|
<div class="text-sm text-gray-800 flex flex-col">
|
||||||
|
No connected nodes
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div
|
||||||
|
:for={node <- @runtime_connected_nodes}
|
||||||
|
class="flex flex-nowrap items-baseline py-1 pl-2 -ml-2 pr-1 hover:bg-gray-100 group rounded-lg"
|
||||||
|
>
|
||||||
|
<.remix_icon icon="circle-fill" class="mr-2 text-xs text-blue-500" />
|
||||||
|
<div class={[
|
||||||
|
"flex-grow text-sm text-gray-700 text-medium whitespace-nowrap text-ellipsis overflow-hidden",
|
||||||
|
"group-hover:overflow-visible group-hover:whitespace-normal group-hover:break-all"
|
||||||
|
]}>
|
||||||
|
<%= node %>
|
||||||
|
</div>
|
||||||
|
<span class="tooltip left" data-tooltip="Disconnect">
|
||||||
|
<.icon_button phx-click="runtime_disconnect_node" phx-value-node={node} small>
|
||||||
|
<.remix_icon icon="close-line" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
defp section_status(%{status: :evaluating} = assigns) do
|
defp section_status(%{status: :evaluating} = assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<button data-el-focus-cell-button data-target={@cell_id}>
|
<button data-el-focus-cell-button data-target={@cell_id}>
|
||||||
|
|
|
@ -3836,6 +3836,23 @@ defmodule Livebook.Session.DataTest do
|
||||||
|
|
||||||
assert new_data.section_infos["setup-section"].evaluation_queue == MapSet.new([])
|
assert new_data.section_infos["setup-section"].evaluation_queue == MapSet.new([])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "clears runtime-related state" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:set_smart_cell_definitions, @cid, @smart_cell_definitions},
|
||||||
|
{:set_runtime_connected_nodes, @cid, [:node@host]}
|
||||||
|
])
|
||||||
|
|
||||||
|
runtime = connected_noop_runtime()
|
||||||
|
operation = {:set_runtime, @cid, runtime}
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
smart_cell_definitions: [],
|
||||||
|
runtime_connected_nodes: []
|
||||||
|
}, []} = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "apply_operation/2 given :set_runtime_transient_state" do
|
describe "apply_operation/2 given :set_runtime_transient_state" do
|
||||||
|
|
|
@ -930,6 +930,32 @@ defmodule LivebookWeb.SessionLiveTest do
|
||||||
assert page =~ "Reconnect"
|
assert page =~ "Reconnect"
|
||||||
assert page =~ "Disconnect"
|
assert page =~ "Disconnect"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "disconnecting a connected node", %{conn: conn, session: session} do
|
||||||
|
{:ok, runtime} = Livebook.Runtime.NoopRuntime.new(self()) |> Livebook.Runtime.connect()
|
||||||
|
Session.set_runtime(session.pid, runtime)
|
||||||
|
|
||||||
|
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
||||||
|
|
||||||
|
Session.subscribe(session.id)
|
||||||
|
|
||||||
|
assert render(view) =~ "No connected nodes"
|
||||||
|
|
||||||
|
# Mimic the runtime reporting a connected node
|
||||||
|
node = :node@host
|
||||||
|
send(session.pid, {:runtime_connected_nodes, [node]})
|
||||||
|
|
||||||
|
assert_receive {:operation, {:set_runtime_connected_nodes, _pid, _nodes}}
|
||||||
|
|
||||||
|
refute render(view) =~ "No connected nodes"
|
||||||
|
assert render(view) =~ "#{node}"
|
||||||
|
|
||||||
|
view
|
||||||
|
|> element(~s{button[phx-click="runtime_disconnect_node"]})
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
assert_receive {:runtime_trace, :disconnect_node, [^node]}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "persistence settings" do
|
describe "persistence settings" do
|
||||||
|
|
|
@ -75,6 +75,11 @@ defmodule Livebook.Runtime.NoopRuntime do
|
||||||
def unregister_clients(_, _), do: :ok
|
def unregister_clients(_, _), do: :ok
|
||||||
def fetch_proxy_handler_spec(_), do: {:error, :not_found}
|
def fetch_proxy_handler_spec(_), do: {:error, :not_found}
|
||||||
|
|
||||||
|
def disconnect_node(runtime, node) do
|
||||||
|
trace(runtime, :disconnect_node, [node])
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
defp trace(runtime, fun, args) do
|
defp trace(runtime, fun, args) do
|
||||||
if runtime.trace_to do
|
if runtime.trace_to do
|
||||||
send(runtime.trace_to, {:runtime_trace, fun, args})
|
send(runtime.trace_to, {:runtime_trace, fun, args})
|
||||||
|
|
Loading…
Add table
Reference in a new issue