defmodule LivebookWeb.AppsDashboardLive do use LivebookWeb, :live_view import LivebookWeb.AppComponents alias LivebookWeb.LayoutComponents on_mount LivebookWeb.SidebarHook @impl true def mount(_params, _session, socket) do if connected?(socket) do Livebook.Apps.subscribe() end apps = Livebook.Apps.list_apps() {:ok, assign(socket, apps: apps, page_title: "Apps - Livebook")} end @impl true def render(assigns) do ~H"""
<.link navigate={~p"/apps"} class="flex items-center text-blue-600"> Listing <.remix_icon icon="arrow-right-line" class="align-middle ml-1" />

An overview of all deployed applications and previews running on this instance.

<.app_list apps={@apps} />
""" end defp app_list(%{apps: []} = assigns) do ~H""" <.no_entries> You do not have any apps running.
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. """ end defp app_list(assigns) do ~H"""
<%= "/" <> app.slug %>
<.app_group_tag app_spec={app.app_spec} /> <.remix_icon icon="arrow-drop-up-line" class="text-3xl text-gray-400 toggle" /> <.remix_icon icon="arrow-drop-down-line" class="text-3xl text-gray-400 hidden toggle" />
<.message_box :for={warning <- app.warnings} kind={:warning} message={warning} />
<.labeled_text label="Name"> <%= app.notebook_name %> <.remix_icon icon="lock-line" />
<.labeled_text label="URL"> <%= ~p"/apps/#{app.slug}" %>
<.labeled_text label="Latest version" one_line> v<%= app.version %>
<.labeled_text label="Session type" one_line> <%= if(app.multi_session, do: "Multi", else: "Single") %>
<%= if app.permanent do %> <.icon_button disabled> <.remix_icon icon="delete-bin-6-line" /> <% else %> <.icon_button aria-label="terminate app" phx-click={JS.push("terminate_app", value: %{slug: app.slug})} > <.remix_icon icon="delete-bin-6-line" /> <% end %>
<%= if Enum.any?(app.sessions) do %>
Running sessions
<.grid rows={app.sessions}> <:col :let={app_session} label="Status"> <.app_status status={app_session.app_status} /> <:col :let={app_session} label="Uptime"> <%= LivebookWeb.HTMLHelpers.format_datetime_relatively(app_session.created_at) %> <:col :let={app_session} label="Version"> v<%= app_session.version %> <:col :let={app_session} label="Clients"> <%= app_session.client_count %> <:actions :let={app_session}> <.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 aria-label="debug app" href={~p"/sessions/#{app_session.id}"}> <.remix_icon icon="terminal-line" /> <%= if app_session.app_status.lifecycle == :active do %> <.icon_button aria-label="deactivate app session" phx-click={ JS.push("deactivate_app_session", value: %{slug: app.slug, session_id: app_session.id} ) } > <.remix_icon icon="stop-circle-line" /> <% else %> <.icon_button aria-label="terminate app session" phx-click={ JS.push("terminate_app_session", value: %{slug: app.slug, session_id: app_session.id} ) } > <.remix_icon icon="delete-bin-6-line" /> <% end %> <% else %>
No running sessions
<% end %>
""" end defp app_group_tag(%{app_spec: %Livebook.Apps.PreviewAppSpec{}} = assigns) do ~H""" Preview """ end defp app_group_tag(%{app_spec: %Livebook.Apps.PathAppSpec{}} = assigns) do ~H""" Apps directory """ end defp app_group_tag(%{app_spec: %Livebook.Apps.TeamsAppSpec{}} = assigns) do ~H""" Livebook Teams """ end defp app_group_tag(assigns), do: ~H"" defp grid(assigns) do ~H"""
<%= col[:label] %>
<%= render_slot(col, row) %>
<%= render_slot(@actions, row) %>
""" end defp align_to_class(:right), do: "text-right" defp align_to_class(:center), do: "text-center" defp align_to_class(_), do: "text-left" @impl true def handle_info({type, _app} = event, socket) when type in [:app_created, :app_updated, :app_closed] do {:noreply, update(socket, :apps, &update_app_list(&1, event))} end def handle_info(_message, socket), do: {:noreply, socket} @impl true def handle_event("terminate_app", %{"slug" => slug}, socket) do app = Enum.find(socket.assigns.apps, &(&1.slug == slug)) {:noreply, confirm_app_termination(socket, app.pid)} end def handle_event("terminate_app_session", %{"slug" => slug, "session_id" => session_id}, socket) do app_session = find_app_session(socket.assigns.apps, slug, session_id) Livebook.Session.close(app_session.pid) {:noreply, socket} end def handle_event( "deactivate_app_session", %{"slug" => slug, "session_id" => session_id}, socket ) do app_session = find_app_session(socket.assigns.apps, slug, session_id) Livebook.Session.app_deactivate(app_session.pid) {:noreply, socket} end defp find_app_session(apps, slug, session_id) do app = Enum.find(apps, &(&1.slug == slug)) Enum.find(app.sessions, &(&1.id == session_id)) end end