Packages
livebook
0.9.3
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
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/apps_live.ex
defmodule LivebookWeb.AppsLive do
use LivebookWeb, :live_view
import LivebookWeb.AppHelpers
import LivebookWeb.SessionHelpers
alias LivebookWeb.LayoutHelpers
alias Livebook.Sessions
on_mount LivebookWeb.SidebarHook
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Livebook.Sessions.subscribe()
end
sessions =
Sessions.list_sessions()
|> Enum.filter(&(&1.mode == :app))
|> Enum.sort_by(& &1.created_at, {:desc, DateTime})
{:ok, assign(socket, sessions: sessions, page_title: "App - Livebook")}
end
@impl true
def render(assigns) do
~H"""
<LayoutHelpers.layout
current_page={~p"/apps"}
current_user={@current_user}
saved_hubs={@saved_hubs}
>
<div class="p-4 md:px-12 md:py-7 max-w-screen-lg mx-auto">
<LayoutHelpers.title text="Apps" />
<div class="mt-8">
<.app_list sessions={@sessions} />
</div>
</div>
</LayoutHelpers.layout>
"""
end
defp app_list(%{sessions: []} = assigns) do
~H"""
<.no_entries>
You do not have any apps running. <br />
You can deploy new apps by opening a notebook and clicking
<.remix_icon icon="rocket-line" class="align-sub text-lg" /> in the sidebar.
</.no_entries>
"""
end
defp app_list(assigns) do
~H"""
<div class="flex flex-col space-y-8">
<div :for={{slug, sessions} <- group_apps(@sessions)}>
<div class="mb-2 text-gray-800 font-medium text-lg">
<%= "/" <> slug %>
</div>
<div class="flex flex-col">
<%= for {session, idx} <- Enum.with_index(sessions) do %>
<div :if={idx > 0} class="ml-4 border-l-2 border-gray-300 border-dashed h-6"></div>
<.app_box session={session} />
<% end %>
</div>
</div>
</div>
"""
end
defp app_box(assigns) do
~H"""
<div
class="border border-gray-200 rounded-lg flex justify-between p-4"
data-app-slug={@session.app_info.slug}
>
<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-8 w-full max-w-2xl">
<div class="flex-1">
<.labeled_text label="Status">
<a
class="inline-block"
aria-label="debug app"
href={@session.app_info.status == :error && ~p"/sessions/#{@session.id}"}
target="_blank"
>
<.app_status status={@session.app_info.status} />
</a>
</.labeled_text>
</div>
<div class="flex-1">
<.labeled_text label="Name">
<%= @session.notebook_name %>
</.labeled_text>
</div>
<div class="flex-1 grow-[2]">
<.labeled_text label="URL">
<%= if @session.app_info.registered do %>
<a href={~p"/apps/#{@session.app_info.slug}"}>
<%= ~p"/apps/#{@session.app_info.slug}" %>
</a>
<% else %>
-
<% end %>
</.labeled_text>
</div>
</div>
<div class="flex flex-col md:flex-row md:items-center gap-2">
<span class="tooltip top" data-tooltip="Debug">
<a class="icon-button" aria-label="debug app" href={~p"/sessions/#{@session.id}"}>
<.remix_icon icon="terminal-line" class="text-lg" />
</a>
</span>
<%= if @session.app_info.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: @session.id})}
>
<.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: @session.id})}
>
<.remix_icon icon="delete-bin-6-line" class="text-lg" />
</button>
</span>
<% end %>
</div>
</div>
"""
end
@impl true
def handle_info({type, session} = event, socket)
when type in [:session_created, :session_updated, :session_closed] and session.mode == :app do
{:noreply, update(socket, :sessions, &update_session_list(&1, event))}
end
def handle_info(_message, socket), do: {:noreply, socket}
@impl true
def handle_event("terminate_app", %{"session_id" => session_id}, socket) do
session = Enum.find(socket.assigns.sessions, &(&1.id == session_id))
Livebook.Session.close(session.pid)
{:noreply, socket}
end
def handle_event("stop_app", %{"session_id" => session_id}, socket) do
session = Enum.find(socket.assigns.sessions, &(&1.id == session_id))
Livebook.Session.app_stop(session.pid)
{:noreply, socket}
end
defp group_apps(sessions) do
sessions
|> Enum.group_by(& &1.app_info.slug)
|> Enum.sort_by(&elem(&1, 0))
end
end