mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-02-03 04:38:11 +08:00
Redesign deploy and runtime panels (#2478)
Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
parent
c9d505a2b4
commit
f3206d9791
11 changed files with 277 additions and 204 deletions
|
@ -279,11 +279,6 @@ solely client-side operations.
|
||||||
@apply text-gray-50 bg-gray-700;
|
@apply text-gray-50 bg-gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-el-session][data-js-side-panel-content="app-info"]
|
|
||||||
[data-el-app-indicator] {
|
|
||||||
@apply border-gray-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-el-clients-list-item]:not([data-js-followed]) [data-meta="unfollow"] {
|
[data-el-clients-list-item]:not([data-js-followed]) [data-meta="unfollow"] {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ Example usage:
|
||||||
display: flex;
|
display: flex;
|
||||||
--distance: 4px;
|
--distance: 4px;
|
||||||
--arrow-size: 5px;
|
--arrow-size: 5px;
|
||||||
|
--arrow-side-offset: 10px;
|
||||||
--show-delay: 0.5s;
|
--show-delay: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +83,17 @@ otherwise there's a tiny space between them.
|
||||||
transform: translate(-50%, calc(1px - var(--arrow-size) - var(--distance)));
|
transform: translate(-50%, calc(1px - var(--arrow-size) - var(--distance)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.top:after {
|
.tooltip.top-right:before {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(
|
||||||
|
calc(0px - var(--arrow-side-offset)),
|
||||||
|
calc(1px - var(--arrow-size) - var(--distance))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.top:after,
|
||||||
|
.tooltip.top-right:after {
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, calc(0px - var(--distance)));
|
transform: translate(-50%, calc(0px - var(--distance)));
|
||||||
|
@ -100,8 +111,11 @@ otherwise there's a tiny space between them.
|
||||||
|
|
||||||
.tooltip.bottom-left:before {
|
.tooltip.bottom-left:before {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
right: 0;
|
left: 50%;
|
||||||
transform: translate(0%, calc(var(--arrow-size) - 1px + var(--distance)));
|
transform: translate(
|
||||||
|
calc(-100% + var(--arrow-side-offset)),
|
||||||
|
calc(var(--arrow-size) - 1px + var(--distance))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.bottom:after,
|
.tooltip.bottom:after,
|
||||||
|
|
|
@ -68,15 +68,15 @@ defmodule LivebookWeb.AppComponents do
|
||||||
@doc """
|
@doc """
|
||||||
Shows a confirmation modal and closes the app on confirm.
|
Shows a confirmation modal and closes the app on confirm.
|
||||||
"""
|
"""
|
||||||
def confirm_app_termination(socket, app_pid) do
|
def confirm_app_termination(socket, app_pid, title \\ "app") do
|
||||||
on_confirm = fn socket ->
|
on_confirm = fn socket ->
|
||||||
Livebook.App.close(app_pid)
|
Livebook.App.close(app_pid)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
confirm(socket, on_confirm,
|
confirm(socket, on_confirm,
|
||||||
title: "Terminate app",
|
title: "Terminate #{title}",
|
||||||
description: "All app sessions will be immediately terminated.",
|
description: "All #{title} sessions will be immediately terminated.",
|
||||||
confirm_text: "Terminate",
|
confirm_text: "Terminate",
|
||||||
confirm_icon: "delete-bin-6-line"
|
confirm_icon: "delete-bin-6-line"
|
||||||
)
|
)
|
||||||
|
|
|
@ -99,7 +99,7 @@ defmodule LivebookWeb.LayoutComponents do
|
||||||
</div>
|
</div>
|
||||||
<.sidebar_link title="Home" icon="home-6-line" to={~p"/"} current={@current_page} />
|
<.sidebar_link title="Home" icon="home-6-line" to={~p"/"} current={@current_page} />
|
||||||
<.sidebar_link
|
<.sidebar_link
|
||||||
title="Apps"
|
title="Local apps"
|
||||||
icon="rocket-line"
|
icon="rocket-line"
|
||||||
to={~p"/apps-dashboard"}
|
to={~p"/apps-dashboard"}
|
||||||
current={@current_page}
|
current={@current_page}
|
||||||
|
|
|
@ -26,15 +26,18 @@ defmodule LivebookWeb.AppsDashboardLive do
|
||||||
current_user={@current_user}
|
current_user={@current_user}
|
||||||
saved_hubs={@saved_hubs}
|
saved_hubs={@saved_hubs}
|
||||||
>
|
>
|
||||||
<div class="p-4 md:px-12 md:py-7 max-w-screen-lg mx-auto">
|
<div class="space-y-2 p-4 md:px-12 md:py-7 max-w-screen-lg mx-auto">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<LayoutComponents.title text="Apps" />
|
<LayoutComponents.title text="Local apps" />
|
||||||
<.link navigate={~p"/apps"} class="flex items-center text-blue-600">
|
<.link navigate={~p"/apps"} class="flex items-center text-blue-600">
|
||||||
<span class="font-semibold">Listing</span>
|
<span class="font-semibold">Listing</span>
|
||||||
<.remix_icon icon="arrow-right-line" class="align-middle ml-1" />
|
<.remix_icon icon="arrow-right-line" class="align-middle ml-1" />
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-10">
|
<p class="text-gray-700 text-sm">
|
||||||
|
An overview of all deployed applications and previews running on this instance.
|
||||||
|
</p>
|
||||||
|
<div class="pt-6">
|
||||||
<.app_list apps={@apps} />
|
<.app_list apps={@apps} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +49,7 @@ defmodule LivebookWeb.AppsDashboardLive do
|
||||||
~H"""
|
~H"""
|
||||||
<.no_entries>
|
<.no_entries>
|
||||||
You do not have any apps running. <br />
|
You do not have any apps running. <br />
|
||||||
You can deploy new apps by opening a notebook and clicking
|
You can preview and deploy new apps by opening a notebook and clicking
|
||||||
<.remix_icon icon="rocket-line" class="align-top text-lg" /> in the sidebar.
|
<.remix_icon icon="rocket-line" class="align-top text-lg" /> in the sidebar.
|
||||||
</.no_entries>
|
</.no_entries>
|
||||||
"""
|
"""
|
||||||
|
@ -70,10 +73,7 @@ defmodule LivebookWeb.AppsDashboardLive do
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col mb-8">
|
<div class="flex-col mb-8">
|
||||||
<div class="p-4 border-x border-t border-gray-200 rounded-t-lg ">
|
<div class="p-4 border-x border-t border-gray-200 rounded-t-lg ">
|
||||||
<div class="uppercase text-gray-500 text-sm font-medium leading-normal tracking-wider">
|
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4">
|
||||||
App Info
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4 mt-3">
|
|
||||||
<div class="break-words">
|
<div class="break-words">
|
||||||
<.labeled_text label="Name">
|
<.labeled_text label="Name">
|
||||||
<%= app.notebook_name %>
|
<%= app.notebook_name %>
|
||||||
|
@ -196,7 +196,10 @@ defmodule LivebookWeb.AppsDashboardLive do
|
||||||
<div class="grid grid-cols-[minmax(0,_0.5fr)_minmax(0,_0.75fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)] md:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4 px-2">
|
<div class="grid grid-cols-[minmax(0,_0.5fr)_minmax(0,_0.75fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)] md:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4 px-2">
|
||||||
<div
|
<div
|
||||||
:for={col <- @col}
|
:for={col <- @col}
|
||||||
class={["text-gray-500 text-sm font-normal", align_to_class(col[:align])]}
|
class={[
|
||||||
|
"text-gray-500 text-sm font-normal flex items-center",
|
||||||
|
align_to_class(col[:align])
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<%= col[:label] %>
|
<%= col[:label] %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,7 +208,10 @@ defmodule LivebookWeb.AppsDashboardLive do
|
||||||
<div class="grid grid-cols-[minmax(0,_0.5fr)_minmax(0,_0.75fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)] md:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4">
|
<div class="grid grid-cols-[minmax(0,_0.5fr)_minmax(0,_0.75fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)_minmax(0,_0.5fr)] md:grid-cols-[minmax(0,_2fr)_minmax(0,_2fr)_minmax(0,_1fr)_minmax(0,_1fr)_minmax(0,_1fr)] gap-4">
|
||||||
<div
|
<div
|
||||||
:for={col <- @col}
|
:for={col <- @col}
|
||||||
class={["py-2 text-gray-800 text-sm font-semibold", align_to_class(col[:align])]}
|
class={[
|
||||||
|
"py-2 text-gray-800 text-sm font-semibold flex items-center",
|
||||||
|
align_to_class(col[:align])
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<%= render_slot(col, row) %>
|
<%= render_slot(col, row) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,7 +59,7 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="p-6 flex flex-col space-y-8">
|
<div class="p-6 flex flex-col space-y-8">
|
||||||
<h3 class="text-2xl font-semibold text-gray-800">
|
<h3 class="text-2xl font-semibold text-gray-800">
|
||||||
App deployment
|
App deployment with Docker
|
||||||
</h3>
|
</h3>
|
||||||
<.content
|
<.content
|
||||||
file={@file}
|
file={@file}
|
||||||
|
@ -149,7 +149,7 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div :if={@warnings != []} class="flex flex-col gap-2">
|
||||||
<.message_box :for={warning <- @warnings} kind={:warning}>
|
<.message_box :for={warning <- @warnings} kind={:warning}>
|
||||||
<%= raw(warning) %>
|
<%= raw(warning) %>
|
||||||
</.message_box>
|
</.message_box>
|
||||||
|
|
|
@ -27,140 +27,188 @@ defmodule LivebookWeb.SessionLive.AppInfoComponent do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-5 flex flex-col gap-6">
|
<div class="flex flex-col gap-3 mt-2">
|
||||||
<.message_box
|
<.message_box
|
||||||
:if={@any_session_secrets?}
|
:if={@any_session_secrets?}
|
||||||
kind={:warning}
|
kind={:warning}
|
||||||
message="The notebook uses session secrets, but those are not available to deployed apps. Convert them to Hub secrets instead."
|
message="The notebook uses session secrets, but those are not available to deployed apps. Convert them to Hub secrets instead."
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col space-y-3">
|
||||||
<.button
|
<.labeled_text label="Slug" one_line>
|
||||||
phx-click="deploy_app"
|
<%= @settings.slug || "?" %>
|
||||||
disabled={not Livebook.Notebook.AppSettings.valid?(@settings)}
|
</.labeled_text>
|
||||||
>
|
|
||||||
<.remix_icon icon="rocket-line" />
|
<.labeled_text label="Session type" one_line>
|
||||||
<span>Deploy</span>
|
<%= if @settings.multi_session, do: "Multi", else: "Single" %>
|
||||||
</.button>
|
</.labeled_text>
|
||||||
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/app"}>
|
|
||||||
Configure
|
<.labeled_text label="Access" one_line>
|
||||||
</.button>
|
<%= if @settings.access_type == :public do %>
|
||||||
|
No password <.remix_icon icon="lock-unlock-line" />
|
||||||
|
<% else %>
|
||||||
|
Password protected <.remix_icon icon="lock-password-line" />
|
||||||
|
<% end %>
|
||||||
|
</.labeled_text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/app"}>
|
||||||
|
Configure
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="mt-12 uppercase text-sm font-semibold text-gray-500">
|
||||||
|
Remote deployment
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="mt-2 flex flex-col gap-2">
|
||||||
|
<%!-- TODO: Livebook Teams flow --%>
|
||||||
|
<.button color="blue" patch={~p"/sessions/#{@session.id}/app-docker"}>
|
||||||
|
<.remix_icon icon="rocket-line" /> Deploy with Livebook Teams
|
||||||
|
</.button>
|
||||||
|
|
||||||
|
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/app-docker"}>
|
||||||
|
<.remix_icon icon="ship-line" /> Manual Docker deployment
|
||||||
|
</.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="mt-12 uppercase text-sm font-semibold text-gray-500">
|
||||||
|
Local preview
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="flex flex-col mt-2 space-y-4">
|
||||||
|
<div :if={@app} class="flex flex-col space-y-3">
|
||||||
|
<.labeled_text label="URL" one_line>
|
||||||
|
<a href={~p"/apps/#{@app.slug}"}>
|
||||||
|
<%= ~p"/apps/#{@app.slug}" %>
|
||||||
|
</a>
|
||||||
|
</.labeled_text>
|
||||||
|
|
||||||
|
<.labeled_text :if={@app.multi_session} label="Latest version" one_line>
|
||||||
|
v<%= @app.version %>
|
||||||
|
</.labeled_text>
|
||||||
|
|
||||||
|
<div :if={@app.sessions != []}>
|
||||||
|
<span class="text-sm text-gray-500">Running sessions</span>
|
||||||
|
|
||||||
|
<div class="mt-2 flex flex-col space-y-4">
|
||||||
|
<.app_sessions app={@app} myself={@myself} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<.link
|
</div>
|
||||||
class="text-sm text-gray-700 hover:text-blue-600"
|
|
||||||
patch={~p"/sessions/#{@session.id}/app-docker"}
|
<div class={["grid gap-2", @app && "grid-cols-2"]}>
|
||||||
|
<span
|
||||||
|
class={[
|
||||||
|
"flex flex-col",
|
||||||
|
not Livebook.Notebook.AppSettings.valid?(@settings) && "tooltip top"
|
||||||
|
]}
|
||||||
|
data-tooltip="You must configure the app to preview it"
|
||||||
>
|
>
|
||||||
<.remix_icon icon="arrow-right-line" />
|
<%= if @app do %>
|
||||||
<span>Deploy with Docker</span>
|
<.button
|
||||||
</.link>
|
color="gray"
|
||||||
|
outlined
|
||||||
|
phx-click="deploy_app"
|
||||||
|
disabled={not Livebook.Notebook.AppSettings.valid?(@settings)}
|
||||||
|
>
|
||||||
|
<.remix_icon icon="slideshow-4-line" /> Relaunch
|
||||||
|
</.button>
|
||||||
|
<% else %>
|
||||||
|
<.button
|
||||||
|
color="blue"
|
||||||
|
phx-click="deploy_app"
|
||||||
|
disabled={not Livebook.Notebook.AppSettings.valid?(@settings)}
|
||||||
|
>
|
||||||
|
<.remix_icon icon="slideshow-4-line" /> Launch preview
|
||||||
|
</.button>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<.button
|
||||||
|
:if={@app}
|
||||||
|
color="red"
|
||||||
|
outlined
|
||||||
|
type="button"
|
||||||
|
phx-click="terminate_app"
|
||||||
|
phx-target={@myself}
|
||||||
|
>
|
||||||
|
Terminate
|
||||||
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= if @app do %>
|
|
||||||
<h3 class="mt-10 uppercase text-sm font-semibold text-gray-500">
|
|
||||||
Latest deployment
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 border border-gray-200 rounded-lg">
|
|
||||||
<div class="p-4 flex flex-col gap-3">
|
|
||||||
<.labeled_text label="URL" one_line>
|
|
||||||
<a href={~p"/apps/#{@app.slug}"}>
|
|
||||||
<%= ~p"/apps/#{@app.slug}" %>
|
|
||||||
</a>
|
|
||||||
</.labeled_text>
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<.labeled_text label="Session type" one_line class="grow">
|
|
||||||
<%= if(@app.multi_session, do: "Multi", else: "Single") %>
|
|
||||||
</.labeled_text>
|
|
||||||
<.labeled_text label="Version" one_line class="grow">
|
|
||||||
v<%= @app.version %>
|
|
||||||
</.labeled_text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-gray-200 px-3 py-2 flex space-x-2">
|
|
||||||
<div class="grow" />
|
|
||||||
<span class="tooltip top" data-tooltip="Terminate">
|
|
||||||
<.icon_button
|
|
||||||
aria-label="terminate app"
|
|
||||||
phx-click={JS.push("terminate_app", target: @myself)}
|
|
||||||
>
|
|
||||||
<.remix_icon icon="delete-bin-6-line" />
|
|
||||||
</.icon_button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3 class="mt-10 uppercase text-sm font-semibold text-gray-500">
|
|
||||||
Running sessions
|
|
||||||
</h3>
|
|
||||||
<div class="mt-2 flex flex-col space-y-4">
|
|
||||||
<div :for={app_session <- @app.sessions} class="w-full border border-gray-200 rounded-lg">
|
|
||||||
<div class="p-4 flex gap-3">
|
|
||||||
<.labeled_text label="Status" class="grow">
|
|
||||||
<a
|
|
||||||
class="inline-block"
|
|
||||||
aria-label="debug app"
|
|
||||||
href={app_session.app_status == :error && ~p"/sessions/#{app_session.id}"}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<.app_status status={app_session.app_status} />
|
|
||||||
</a>
|
|
||||||
</.labeled_text>
|
|
||||||
<.labeled_text label="Version" class="grow">
|
|
||||||
v<%= app_session.version %>
|
|
||||||
</.labeled_text>
|
|
||||||
</div>
|
|
||||||
<div class="border-t border-gray-200 px-3 py-2 flex space-x-2">
|
|
||||||
<span class="tooltip top" data-tooltip="Open">
|
|
||||||
<.icon_button
|
|
||||||
disabled={app_session.app_status.lifecycle}
|
|
||||||
aria-label="open app"
|
|
||||||
href={~p"/apps/#{@app.slug}/#{app_session.id}"}
|
|
||||||
>
|
|
||||||
<.remix_icon icon="link" />
|
|
||||||
</.icon_button>
|
|
||||||
</span>
|
|
||||||
<div class="grow" />
|
|
||||||
<span class="tooltip top" data-tooltip="Debug">
|
|
||||||
<.icon_button aria-label="debug app" href={~p"/sessions/#{app_session.id}"}>
|
|
||||||
<.remix_icon icon="terminal-line" />
|
|
||||||
</.icon_button>
|
|
||||||
</span>
|
|
||||||
<%= if app_session.app_status.lifecycle == :active do %>
|
|
||||||
<span class="tooltip top" data-tooltip="Deactivate">
|
|
||||||
<.icon_button
|
|
||||||
aria-label="deactivate app session"
|
|
||||||
phx-click={
|
|
||||||
JS.push("deactivate_app_session",
|
|
||||||
value: %{session_id: app_session.id},
|
|
||||||
target: @myself
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<.remix_icon icon="stop-circle-line" />
|
|
||||||
</.icon_button>
|
|
||||||
</span>
|
|
||||||
<% else %>
|
|
||||||
<span class="tooltip top" data-tooltip="Terminate">
|
|
||||||
<.icon_button
|
|
||||||
aria-label="terminate app session"
|
|
||||||
phx-click={
|
|
||||||
JS.push("terminate_app_session",
|
|
||||||
value: %{session_id: app_session.id},
|
|
||||||
target: @myself
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<.remix_icon icon="delete-bin-6-line" />
|
|
||||||
</.icon_button>
|
|
||||||
</span>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp app_sessions(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div :for={app_session <- @app.sessions} class="w-full border border-gray-200 rounded-lg">
|
||||||
|
<div class="px-4 py-3 flex gap-3">
|
||||||
|
<.labeled_text label="Status" class="grow">
|
||||||
|
<a
|
||||||
|
class="inline-block"
|
||||||
|
aria-label="debug app"
|
||||||
|
href={app_session.app_status == :error && ~p"/sessions/#{app_session.id}"}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<.app_status status={app_session.app_status} />
|
||||||
|
</a>
|
||||||
|
</.labeled_text>
|
||||||
|
<.labeled_text label="Version" class="grow">
|
||||||
|
v<%= app_session.version %>
|
||||||
|
</.labeled_text>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-200 px-3 py-1 flex space-x-2">
|
||||||
|
<div class="grow" />
|
||||||
|
<span class="tooltip top" data-tooltip="Open">
|
||||||
|
<.icon_button
|
||||||
|
disabled={app_session.app_status.lifecycle != :active}
|
||||||
|
aria-label="open app"
|
||||||
|
href={~p"/apps/#{@app.slug}/#{app_session.id}"}
|
||||||
|
>
|
||||||
|
<.remix_icon icon="link" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
|
<span class="tooltip top" data-tooltip="Debug">
|
||||||
|
<.icon_button aria-label="debug app" href={~p"/sessions/#{app_session.id}"}>
|
||||||
|
<.remix_icon icon="terminal-line" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
|
<%= if app_session.app_status.lifecycle == :active do %>
|
||||||
|
<span class="tooltip top" data-tooltip="Deactivate">
|
||||||
|
<.icon_button
|
||||||
|
aria-label="deactivate app session"
|
||||||
|
phx-click={
|
||||||
|
JS.push("deactivate_app_session",
|
||||||
|
value: %{session_id: app_session.id},
|
||||||
|
target: @myself
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<.remix_icon icon="stop-circle-line" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
|
<% else %>
|
||||||
|
<span class="tooltip top" data-tooltip="Terminate">
|
||||||
|
<.icon_button
|
||||||
|
aria-label="terminate app session"
|
||||||
|
phx-click={
|
||||||
|
JS.push("terminate_app_session",
|
||||||
|
value: %{session_id: app_session.id},
|
||||||
|
target: @myself
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<.remix_icon icon="delete-bin-6-line" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
defp app_info_icon(assigns) do
|
defp app_info_icon(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<span
|
<span
|
||||||
|
@ -183,7 +231,7 @@ defmodule LivebookWeb.SessionLive.AppInfoComponent do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("terminate_app", %{}, socket) do
|
def handle_event("terminate_app", %{}, socket) do
|
||||||
{:noreply, confirm_app_termination(socket, socket.assigns.app.pid)}
|
{:noreply, confirm_app_termination(socket, socket.assigns.app.pid, "preview")}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("terminate_app_session", %{"session_id" => session_id}, socket) do
|
def handle_event("terminate_app_session", %{"session_id" => session_id}, socket) do
|
||||||
|
|
|
@ -24,7 +24,14 @@ defmodule LivebookWeb.SessionLive.AppSettingsComponent do
|
||||||
<h3 class="text-2xl font-semibold text-gray-800">
|
<h3 class="text-2xl font-semibold text-gray-800">
|
||||||
App settings
|
App settings
|
||||||
</h3>
|
</h3>
|
||||||
<.form :let={f} for={@changeset} phx-change="validate" phx-target={@myself} autocomplete="off">
|
<.form
|
||||||
|
:let={f}
|
||||||
|
for={@changeset}
|
||||||
|
phx-change="validate"
|
||||||
|
phx-submit="save"
|
||||||
|
phx-target={@myself}
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<.text_field field={f[:slug]} label="Slug" spellcheck="false" phx-debounce />
|
<.text_field field={f[:slug]} label="Slug" spellcheck="false" phx-debounce />
|
||||||
<div class="flex flex-col space-y-1">
|
<div class="flex flex-col space-y-1">
|
||||||
|
@ -118,13 +125,8 @@ defmodule LivebookWeb.SessionLive.AppSettingsComponent do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 flex space-x-2">
|
<div class="mt-8 flex space-x-2">
|
||||||
<.button
|
<.button type="submit" disabled={not @changeset.valid?}>
|
||||||
type="button"
|
<span>Save</span>
|
||||||
phx-click={JS.patch(~p"/sessions/#{@session.id}") |> JS.push("deploy_app")}
|
|
||||||
disabled={not @changeset.valid?}
|
|
||||||
>
|
|
||||||
<.remix_icon icon="rocket-line" />
|
|
||||||
<span>Deploy</span>
|
|
||||||
</.button>
|
</.button>
|
||||||
<.button color="gray" outlined type="reset" name="reset">
|
<.button color="gray" outlined type="reset" name="reset">
|
||||||
Reset
|
Reset
|
||||||
|
@ -148,10 +150,14 @@ defmodule LivebookWeb.SessionLive.AppSettingsComponent do
|
||||||
|> AppSettings.change(params)
|
|> AppSettings.change(params)
|
||||||
|> Map.put(:action, :validate)
|
|> Map.put(:action, :validate)
|
||||||
|
|
||||||
|
{:noreply, assign(socket, changeset: changeset)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save", %{"app_settings" => params}, socket) do
|
||||||
with {:ok, settings} <- AppSettings.update(socket.assigns.settings, params) do
|
with {:ok, settings} <- AppSettings.update(socket.assigns.settings, params) do
|
||||||
Livebook.Session.set_app_settings(socket.assigns.session.pid, settings)
|
Livebook.Session.set_app_settings(socket.assigns.session.pid, settings)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, assign(socket, changeset: changeset)}
|
{:noreply, push_patch(socket, to: ~p"/sessions/#{socket.assigns.session.id}")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ defmodule LivebookWeb.SessionLive.ElixirStandaloneLive do
|
||||||
<%= @error_message %>
|
<%= @error_message %>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-700">
|
<p class="text-gray-700">
|
||||||
Start a new local node to handle code evaluation.
|
Start a new local node to evaluate code.
|
||||||
</p>
|
</p>
|
||||||
<.button phx-click="init">
|
<.button phx-click="init">
|
||||||
<%= if(matching_runtime?(@current_runtime), do: "Reconnect", else: "Connect") %>
|
<%= if(matching_runtime?(@current_runtime), do: "Reconnect", else: "Connect") %>
|
||||||
|
|
|
@ -323,20 +323,11 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
button_attrs={["data-el-files-list-toggle": true]}
|
button_attrs={["data-el-files-list-toggle": true]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="relative">
|
<.button_item
|
||||||
<.button_item
|
icon="rocket-line"
|
||||||
icon="rocket-line"
|
label="App settings (sa)"
|
||||||
label="App settings (sa)"
|
button_attrs={["data-el-app-info-toggle": true]}
|
||||||
button_attrs={["data-el-app-info-toggle": true]}
|
/>
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-el-app-indicator
|
|
||||||
class={[
|
|
||||||
"absolute w-[12px] h-[12px] border-gray-900 border-2 rounded-full right-1.5 top-1.5 pointer-events-none",
|
|
||||||
app_status_color(app_status(@app))
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|
||||||
|
@ -373,17 +364,6 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
defp app_status(%{sessions: [app_session | _]}), do: app_session.app_status
|
|
||||||
defp app_status(_), do: nil
|
|
||||||
|
|
||||||
defp app_status_color(nil), do: "bg-gray-400"
|
|
||||||
defp app_status_color(%{lifecycle: :shutting_down}), do: "bg-gray-500"
|
|
||||||
defp app_status_color(%{lifecycle: :deactivated}), do: "bg-gray-500"
|
|
||||||
defp app_status_color(%{execution: :executing}), do: "bg-blue-500"
|
|
||||||
defp app_status_color(%{execution: :executed}), do: "bg-green-bright-400"
|
|
||||||
defp app_status_color(%{execution: :error}), do: "bg-red-400"
|
|
||||||
defp app_status_color(%{execution: :interrupted}), do: "bg-gray-400"
|
|
||||||
|
|
||||||
def side_panel(assigns) do
|
def side_panel(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div
|
<div
|
||||||
|
@ -601,9 +581,19 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
||||||
Runtime
|
Runtime
|
||||||
</h3>
|
</h3>
|
||||||
<.icon_button patch={~p"/sessions/#{@session.id}/settings/runtime"}>
|
<span
|
||||||
<.remix_icon icon="settings-3-line" />
|
class="tooltip bottom-left"
|
||||||
</.icon_button>
|
data-tooltip={
|
||||||
|
~S'''
|
||||||
|
The runtime configures which Erlang VM
|
||||||
|
instance the notebook code runs on.
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<.icon_button>
|
||||||
|
<.remix_icon icon="question-line" />
|
||||||
|
</.icon_button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col mt-2 space-y-4">
|
<div class="flex flex-col mt-2 space-y-4">
|
||||||
<div class="flex flex-col space-y-3">
|
<div class="flex flex-col space-y-3">
|
||||||
|
@ -615,37 +605,47 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
<%= value %>
|
<%= value %>
|
||||||
</.labeled_text>
|
</.labeled_text>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="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" />
|
||||||
<span>Reconnect</span>
|
<span>Reconnect</span>
|
||||||
</.button>
|
</.button>
|
||||||
<.button color="red" outlined type="button" phx-click="disconnect_runtime">
|
|
||||||
Disconnect
|
|
||||||
</.button>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<.button phx-click="connect_runtime">
|
<.button phx-click="connect_runtime">
|
||||||
<.remix_icon icon="wireless-charging-line" />
|
<.remix_icon icon="wireless-charging-line" />
|
||||||
<span>Connect</span>
|
<span>Connect</span>
|
||||||
</.button>
|
</.button>
|
||||||
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}>
|
|
||||||
Configure
|
|
||||||
</.button>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}>
|
||||||
|
Configure
|
||||||
|
</.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
|
||||||
|
:if={Runtime.connected?(@data_view.runtime)}
|
||||||
|
color="red"
|
||||||
|
outlined
|
||||||
|
type="button"
|
||||||
|
phx-click="disconnect_runtime"
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</.button>
|
||||||
</div>
|
</div>
|
||||||
<%= if uses_memory?(@session.memory_usage) do %>
|
|
||||||
<.memory_info memory_usage={@session.memory_usage} />
|
|
||||||
<% else %>
|
|
||||||
<div class="mb-1 text-sm text-gray-800 py-6 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 %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
@ -655,7 +655,7 @@ defmodule LivebookWeb.SessionLive.Render do
|
||||||
assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage))
|
assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage))
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="py-6 flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
<div class="mb-1 text-sm text-gray-800 flex flex-row justify-between">
|
<div class="mb-1 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">Memory</span>
|
||||||
<span class="text-right">
|
<span class="text-right">
|
||||||
|
|
|
@ -2127,7 +2127,11 @@ defmodule LivebookWeb.SessionLiveTest do
|
||||||
|> render_change(%{"app_settings" => %{"slug" => slug}})
|
|> render_change(%{"app_settings" => %{"slug" => slug}})
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element(~s/#app-settings-modal button/, "Deploy")
|
|> element(~s/#app-settings-modal form/)
|
||||||
|
|> render_submit(%{"app_settings" => %{"slug" => slug}})
|
||||||
|
|
||||||
|
view
|
||||||
|
|> element(~s/[data-el-app-info] button/, "Launch preview")
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
assert_receive {:app_created, %{slug: ^slug} = app}
|
assert_receive {:app_created, %{slug: ^slug} = app}
|
||||||
|
|
Loading…
Reference in a new issue