Packages
livebook
0.9.0
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="flex flex-col mt-2 space-y-4">
<div class="w-full flex flex-col">
<%= if @deploy_confirmation do %>
<div class="mt-5">
<div class="text-gray-700 flex items-center">
<span class="text-sm">
Another app is already running under this slug, do you want to replace it?
</span>
</div>
<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>
<% else %>
<.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 />
<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 />
<% 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 %>
</div>
</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">
<.app_status status={app.status} />
</.labeled_text>
<.labeled_text label="URL" one_line>
<%= if app.registered do %>
<a href={url(~p"/apps/#{app.settings.slug}")} target="_blank">
<%= url(~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}"}
target="_blank"
>
<.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
@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