Packages
livebook
0.9.2
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/session_live/app_info_component.ex
defmodule LivebookWeb.SessionLive.AppInfoComponent do
use LivebookWeb, :live_component
import LivebookWeb.AppHelpers
alias Livebook.Notebook.AppSettings
@impl true
def mount(socket) do
{:ok, assign(socket, deploy_confirmation: false)}
end
@impl true
def update(assigns, socket) do
changeset =
case socket.assigns do
%{changeset: changeset} when changeset.data == assigns.settings -> changeset
_ -> AppSettings.change(assigns.settings)
end
{:ok,
socket
|> assign(assigns)
|> assign(changeset: changeset)}
end
@impl true
def render(assigns) do
~H"""
<div class="flex flex-col">
<div class="flex items-center justify-between">
<h3 class="uppercase text-sm font-semibold text-gray-500">
App settings
</h3>
<.app_info_icon />
</div>
<div class="mt-2">
<.app_form
changeset={@changeset}
deploy_confirmation={@deploy_confirmation}
session={@session}
myself={@myself}
/>
</div>
<%= if @apps != [] do %>
<h3 class="mt-16 uppercase text-sm font-semibold text-gray-500">
Deployments
</h3>
<div class="mt-2 flex flex-col space-y-4">
<div :for={app <- @apps} class="border border-gray-200 rounded-lg">
<div class="p-4 flex flex-col space-y-3">
<.labeled_text label="Status">
<a
class="inline-block"
aria-label="debug app"
href={app.status == :error && ~p"/sessions/#{app.session_id}"}
target="_blank"
>
<.app_status status={app.status} />
</a>
</.labeled_text>
<.labeled_text label="URL" one_line>
<%= if app.registered do %>
<a href={~p"/apps/#{app.settings.slug}"}>
<%= ~p"/apps/#{app.settings.slug}" %>
</a>
<% else %>
-
<% end %>
</.labeled_text>
</div>
<div class="border-t border-gray-200 px-3 py-2 flex space-x-2 justify-between">
<span class="tooltip top" data-tooltip="Debug">
<a class="icon-button" aria-label="debug app" href={~p"/sessions/#{app.session_id}"}>
<.remix_icon icon="terminal-line" class="text-lg" />
</a>
</span>
<%= if app.registered do %>
<span class="tooltip top" data-tooltip="Stop">
<button
class="icon-button"
aria-label="stop app"
phx-click={
JS.push("stop_app", value: %{session_id: app.session_id}, target: @myself)
}
>
<.remix_icon icon="stop-circle-line" class="text-lg" />
</button>
</span>
<% else %>
<span class="tooltip top" data-tooltip="Terminate">
<button
class="icon-button"
aria-label="terminate app"
phx-click={
JS.push("terminate_app", value: %{session_id: app.session_id}, target: @myself)
}
>
<.remix_icon icon="delete-bin-6-line" class="text-lg" />
</button>
</span>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
"""
end
defp app_info_icon(assigns) do
~H"""
<span
class="icon-button cursor-pointer tooltip bottom-left"
data-tooltip={
~S'''
App deployment is a way to share your
notebook for people to interact with. Use
inputs and controls to build interactive
UIs, perfect for demos and tasks.
'''
}
>
<.remix_icon icon="question-line" class="text-xl leading-none" />
</span>
"""
end
defp app_form(%{session: %{app_info: %{}}} = assigns) do
~H"""
<div class="mt-5 flex flex-col space-y-6">
<span class="text-gray-700 text-sm">
This session is a running app. To deploy a modified version, you can fork it.
</span>
<div>
<button class="button-base button-blue" phx-click="fork_session">
<.remix_icon icon="git-branch-line" />
<span>Fork</span>
</button>
</div>
</div>
"""
end
defp app_form(%{deploy_confirmation: true} = assigns) do
~H"""
<div class="mt-5">
<span class="text-gray-700 text-sm">
Another app is already running under this slug, do you want to replace it?
</span>
<div class="mt-5 flex space-x-2">
<button
class="button-base button-red"
phx-click="deploy_confirmation_confirm"
phx-target={@myself}
>
Replace
</button>
<button
class="button-base button-outlined-gray bg-transparent"
phx-click="deploy_confirmation_cancel"
phx-target={@myself}
>
Cancel
</button>
</div>
</div>
"""
end
defp app_form(assigns) do
~H"""
<.form
:let={f}
for={@changeset}
phx-submit="deploy"
phx-change="validate"
phx-target={@myself}
autocomplete="off"
>
<div class="flex flex-col space-y-4">
<.text_field
field={f[:slug]}
label="Slug"
spellcheck="false"
phx-debounce
class="bg-gray-100"
/>
<div class="flex flex-col space-y-1">
<.checkbox_field
field={f[:access_type]}
label="Password-protected"
checked_value="protected"
unchecked_value="public"
/>
<%= if Ecto.Changeset.get_field(@changeset, :access_type) == :protected do %>
<.password_field field={f[:password]} spellcheck="false" phx-debounce class="bg-gray-100" />
<% end %>
</div>
<.checkbox_field field={f[:show_source]} label="Show source" />
</div>
<div class="mt-6 flex space-x-2">
<button class="button-base button-blue" type="submit" disabled={not @changeset.valid?}>
Deploy
</button>
<button class="button-base button-outlined-gray bg-transparent" type="reset" name="reset">
Reset
</button>
</div>
</.form>
"""
end
@impl true
def handle_event("validate", %{"_target" => ["reset"]}, socket) do
settings = AppSettings.new()
Livebook.Session.set_app_settings(socket.assigns.session.pid, settings)
{:noreply, assign(socket, changeset: AppSettings.change(settings))}
end
def handle_event("validate", %{"app_settings" => params}, socket) do
changeset =
socket.assigns.settings
|> AppSettings.change(params)
|> Map.put(:action, :validate)
with {:ok, settings} <- AppSettings.update(socket.assigns.settings, params) do
Livebook.Session.set_app_settings(socket.assigns.session.pid, settings)
end
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("deploy", %{"app_settings" => params}, socket) do
case AppSettings.update(socket.assigns.settings, params) do
{:ok, settings} ->
Livebook.Session.set_app_settings(socket.assigns.session.pid, settings)
if slug_taken?(settings.slug, socket.assigns.apps) do
{:noreply, assign(socket, deploy_confirmation: true)}
else
Livebook.Session.deploy_app(socket.assigns.session.pid)
{:noreply, socket}
end
{:error, _changeset} ->
{:noreply, socket}
end
end
def handle_event("deploy_confirmation_confirm", %{}, socket) do
Livebook.Session.deploy_app(socket.assigns.session.pid)
{:noreply, assign(socket, deploy_confirmation: false)}
end
def handle_event("deploy_confirmation_cancel", %{}, socket) do
{:noreply, assign(socket, deploy_confirmation: false)}
end
def handle_event("terminate_app", %{"session_id" => session_id}, socket) do
app = Enum.find(socket.assigns.apps, &(&1.session_id == session_id))
Livebook.Session.close(app.session_pid)
{:noreply, socket}
end
def handle_event("stop_app", %{"session_id" => session_id}, socket) do
app = Enum.find(socket.assigns.apps, &(&1.session_id == session_id))
Livebook.Session.app_stop(app.session_pid)
{:noreply, socket}
end
defp slug_taken?(slug, apps) do
own? =
Enum.any?(apps, fn app ->
app.registered and app.settings.slug == slug
end)
not own? and Livebook.Apps.exists?(slug)
end
end