From 92bb56764533fb65d9f4bb14e2528062abea367a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Wed, 29 Mar 2023 00:02:07 +0100 Subject: [PATCH] Make the app list on auth screen live (#1836) --- lib/livebook/apps.ex | 13 +++++ .../controllers/auth_controller.ex | 22 ++------ .../controllers/auth_html/index.html.heex | 13 +---- lib/livebook_web/live/app_helpers.ex | 13 +++-- lib/livebook_web/live/auth_app_list_live.ex | 56 +++++++++++++++++++ 5 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 lib/livebook_web/live/auth_app_list_live.ex diff --git a/lib/livebook/apps.ex b/lib/livebook/apps.ex index eb0d267a0..1726d4099 100644 --- a/lib/livebook/apps.ex +++ b/lib/livebook/apps.ex @@ -140,4 +140,17 @@ defmodule Livebook.Apps do :ok end + + @doc """ + Checks if the apps directory is configured and contains no notebooks. + """ + @spec empty_apps_path?() :: boolean() + def empty_apps_path?() do + if path = Livebook.Config.apps_path() do + pattern = Path.join([path, "**", "*.livemd"]) + Path.wildcard(pattern) == [] + else + false + end + end end diff --git a/lib/livebook_web/controllers/auth_controller.ex b/lib/livebook_web/controllers/auth_controller.ex index 858454b78..85e60f3c1 100644 --- a/lib/livebook_web/controllers/auth_controller.ex +++ b/lib/livebook_web/controllers/auth_controller.ex @@ -25,8 +25,8 @@ defmodule LivebookWeb.AuthController do render(conn, "index.html", errors: [], auth_mode: Livebook.Config.auth_mode(), - app_sessions: app_sessions(), - empty_apps_path?: empty_apps_path?() + any_public_app?: any_public_app?(), + empty_apps_path?: Livebook.Apps.empty_apps_path?() ) end @@ -56,8 +56,8 @@ defmodule LivebookWeb.AuthController do render(conn, "index.html", errors: errors, auth_mode: auth_mode, - app_sessions: app_sessions(), - empty_apps_path?: empty_apps_path?() + any_public_app?: any_public_app?(), + empty_apps_path?: Livebook.Apps.empty_apps_path?() ) end @@ -75,18 +75,8 @@ defmodule LivebookWeb.AuthController do |> halt() end - defp app_sessions() do + defp any_public_app?() do Livebook.Sessions.list_sessions() - |> Enum.filter(&(&1.mode == :app and &1.app_info.public? and &1.app_info.registered)) - |> Enum.sort_by(& &1.notebook_name) - end - - defp empty_apps_path?() do - if path = Livebook.Config.apps_path() do - pattern = Path.join([path, "**", "*.livemd"]) - Path.wildcard(pattern) == [] - else - false - end + |> Enum.any?(&(&1.mode == :app and &1.app_info.public?)) end end diff --git a/lib/livebook_web/controllers/auth_html/index.html.heex b/lib/livebook_web/controllers/auth_html/index.html.heex index e3a2bf5dc..0bc97c52d 100644 --- a/lib/livebook_web/controllers/auth_html/index.html.heex +++ b/lib/livebook_web/controllers/auth_html/index.html.heex @@ -62,22 +62,15 @@
Public apps
-
- <.link - :for={session <- @app_sessions} - navigate={~p"/apps/#{session.app_info.slug}"} - class="px-4 py-3 border border-gray-200 rounded-xl text-gray-800 pointer hover:bg-gray-50 flex justify-between" - > - <%= session.notebook_name %> - <.remix_icon icon="arrow-right-line" class="" /> - +
+ <%= live_render(@conn, LivebookWeb.AuthAppListLive) %>
diff --git a/lib/livebook_web/live/app_helpers.ex b/lib/livebook_web/live/app_helpers.ex index 63339ee61..7bf5fc523 100644 --- a/lib/livebook_web/live/app_helpers.ex +++ b/lib/livebook_web/live/app_helpers.ex @@ -5,41 +5,42 @@ defmodule LivebookWeb.AppHelpers do Renders app status with indicator. """ attr :status, :atom, required: true + attr :show_label, :boolean, default: true def app_status(%{status: :booting} = assigns) do ~H""" - <.app_status_indicator text="Booting" variant={:progressing} /> + <.app_status_indicator text={@show_label && "Booting"} variant={:progressing} /> """ end def app_status(%{status: :running} = assigns) do ~H""" - <.app_status_indicator text="Running" variant={:success} /> + <.app_status_indicator text={@show_label && "Running"} variant={:success} /> """ end def app_status(%{status: :error} = assigns) do ~H""" - <.app_status_indicator text="Error" variant={:error} /> + <.app_status_indicator text={@show_label && "Error"} variant={:error} /> """ end def app_status(%{status: :shutting_down} = assigns) do ~H""" - <.app_status_indicator text="Shutting down" variant={:inactive} /> + <.app_status_indicator text={@show_label && "Shutting down"} variant={:inactive} /> """ end def app_status(%{status: :stopped} = assigns) do ~H""" - <.app_status_indicator text="Stopped" variant={:inactive} /> + <.app_status_indicator text={@show_label && "Stopped"} variant={:inactive} /> """ end defp app_status_indicator(assigns) do ~H"""
-
<%= @text %>
+
<%= @text %>
<.status_indicator variant={@variant} />
""" diff --git a/lib/livebook_web/live/auth_app_list_live.ex b/lib/livebook_web/live/auth_app_list_live.ex new file mode 100644 index 000000000..584fe534a --- /dev/null +++ b/lib/livebook_web/live/auth_app_list_live.ex @@ -0,0 +1,56 @@ +defmodule LivebookWeb.AuthAppListLive do + use LivebookWeb, :live_view + + import LivebookWeb.AppHelpers + import LivebookWeb.SessionHelpers + + @impl true + def mount(_params, _session, socket) do + if connected?(socket) do + Livebook.Sessions.subscribe() + end + + sessions = Livebook.Sessions.list_sessions() |> Enum.filter(&(&1.mode == :app)) + + {:ok, assign(socket, sessions: sessions), layout: false} + end + + @impl true + def render(assigns) do + ~H""" +
+ <.link + :for={session <- visible_sessions(@sessions)} + navigate={~p"/apps/#{session.app_info.slug}"} + class={[ + "px-4 py-3 border border-gray-200 rounded-xl text-gray-800 pointer hover:bg-gray-50 flex justify-between", + not session.app_info.registered && "pointer-events-none" + ]} + > + <%= session.notebook_name %> + <%= if session.app_info.registered do %> + <.remix_icon icon="arrow-right-line" class="" /> + <% else %> +
+ <.app_status status={session.app_info.status} show_label={false} /> +
+ <% end %> + +
+ """ + 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} + + defp visible_sessions(sessions) do + sessions + |> Enum.filter(& &1.app_info.public?) + |> Enum.sort_by(& &1.notebook_name) + end +end