defmodule LivebookWeb.AppsLive do use LivebookWeb, :live_view import LivebookWeb.AppHelpers alias LivebookWeb.LayoutHelpers 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"""
<.app_list apps={@apps} />
""" end defp app_list(%{apps: []} = assigns) do ~H""" <.no_entries> You do not have any apps running.
You can deploy new apps by opening a notebook and clicking <.remix_icon icon="rocket-line" class="align-sub text-lg" /> in the sidebar. """ end defp app_list(assigns) do ~H"""
<%= "/" <> app.slug %>
App info
<.labeled_text label="Name" one_line> <%= app.notebook_name %>
<.labeled_text label="URL" one_line> <%= ~p"/apps/#{app.slug}" %>
<.labeled_text label="Version" one_line> v<%= app.version %>
<.labeled_text label="Session type" one_line> <%= if(app.multi_session, do: "Multi", else: "Single") %>
Running sessions
<.table rows={app.sessions}> <:col :let={app_session} label="Status"> <.app_status status={app_session.app_status} /> <:col :let={app_session} label="Uptime"> <%= format_datetime_relatively(app_session.created_at) %> <:col :let={app_session} label="Version" align={:center}> v<%= app_session.version %> <:col :let={app_session} label="Clients" align={:center}> <%= app_session.client_count %> <:actions :let={app_session}> <.remix_icon icon="link" class="text-lg" /> <.remix_icon icon="terminal-line" class="text-lg" /> <%= if app_session.app_status.lifecycle == :active do %> <% else %> <% end %>
""" end defp table(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 def update_app_list(apps, {:app_created, app}) do if app in apps, do: apps, else: [app | apps] end def update_app_list(apps, {:app_updated, app}) do Enum.map(apps, fn other -> if other.slug == app.slug, do: app, else: other end) end def update_app_list(apps, {:app_closed, app}) do Enum.reject(apps, &(&1.slug == app.slug)) end end