diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 54a77cf12..5bf9954ed 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -53,7 +53,7 @@ defmodule Livebook.Application do load_lb_env_vars() clear_env_vars() display_startup_info() - insert_development_hub() + insert_personal_hub() Livebook.Hubs.connect_hubs() result @@ -203,16 +203,14 @@ defmodule Livebook.Application do defp app_specs, do: [] end - if Livebook.Config.feature_flag_enabled?(:localhost_hub) do - defp insert_development_hub do - Livebook.Hubs.save_hub(%Livebook.Hubs.Local{ - id: "local-host", - hub_name: "Localhost", + defp insert_personal_hub do + unless Livebook.Hubs.hub_exists?("personal-hub") do + Livebook.Hubs.save_hub(%Livebook.Hubs.Personal{ + id: "personal-hub", + hub_name: "My Hub", hub_emoji: "🏠" }) end - else - defp insert_development_hub, do: :ok end defp iframe_server_specs() do diff --git a/lib/livebook/hubs.ex b/lib/livebook/hubs.ex index e814ddf03..ca0a9eb7b 100644 --- a/lib/livebook/hubs.ex +++ b/lib/livebook/hubs.ex @@ -2,7 +2,7 @@ defmodule Livebook.Hubs do @moduledoc false alias Livebook.Storage - alias Livebook.Hubs.{Broadcasts, Enterprise, Fly, Local, Metadata, Provider} + alias Livebook.Hubs.{Broadcasts, Enterprise, Fly, Metadata, Personal, Provider} alias Livebook.Secrets alias Livebook.Secrets.Secret @@ -94,6 +94,7 @@ defmodule Livebook.Hubs do @spec delete_hub(String.t()) :: :ok def delete_hub(id) do with {:ok, hub} <- get_hub(id) do + true = Provider.type(hub) != "personal" :ok = Broadcasts.hub_changed() :ok = Storage.delete(@namespace, id) :ok = disconnect_hub(hub) @@ -111,13 +112,6 @@ defmodule Livebook.Hubs do :ok end - @doc false - def clean_hubs do - for hub <- get_hubs(), do: delete_hub(hub.id) - - :ok - end - @doc """ Subscribes to one or more subtopics in `"hubs"`. @@ -172,8 +166,8 @@ defmodule Livebook.Hubs do Provider.load(%Enterprise{}, fields) end - defp to_struct(%{id: "local-" <> _} = fields) do - Provider.load(%Local{}, fields) + defp to_struct(%{id: "personal-" <> _} = fields) do + Provider.load(%Personal{}, fields) end @doc """ diff --git a/lib/livebook/hubs/enterprise_client.ex b/lib/livebook/hubs/enterprise_client.ex index 0f9c3878a..4216516d4 100644 --- a/lib/livebook/hubs/enterprise_client.ex +++ b/lib/livebook/hubs/enterprise_client.ex @@ -59,9 +59,11 @@ defmodule Livebook.Hubs.EnterpriseClient do @doc """ Returns the latest error from connection. """ - @spec get_connection_error(String.t()) :: Secret.t() | nil + @spec get_connection_error(String.t()) :: String.t() | nil def get_connection_error(id) do GenServer.call(registry_name(id), :get_connection_error) + catch + :exit, _ -> "connection refused" end @doc """ diff --git a/lib/livebook/hubs/local.ex b/lib/livebook/hubs/local.ex deleted file mode 100644 index 907a7eebb..000000000 --- a/lib/livebook/hubs/local.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule Livebook.Hubs.Local do - @moduledoc false - - defstruct [:id, :hub_name, :hub_emoji] -end - -defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Local do - def load(local, fields) do - %{local | id: fields.id, hub_name: fields.hub_name, hub_emoji: fields.hub_emoji} - end - - def to_metadata(local) do - %Livebook.Hubs.Metadata{ - id: local.id, - name: local.hub_name, - provider: local, - emoji: local.hub_emoji, - connected?: false - } - end - - def type(_local), do: "local" - - def connection_spec(_local), do: nil - - def disconnect(_local), do: :ok - - def capabilities(_local), do: [] - - def get_secrets(_local), do: [] - - def create_secret(_local, _secret), do: :ok - - def connection_error(_local), do: nil -end diff --git a/lib/livebook/hubs/personal.ex b/lib/livebook/hubs/personal.ex new file mode 100644 index 000000000..d3fd9db97 --- /dev/null +++ b/lib/livebook/hubs/personal.ex @@ -0,0 +1,84 @@ +defmodule Livebook.Hubs.Personal do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + + alias Livebook.Hubs + + @type t :: %__MODULE__{ + id: String.t() | nil, + hub_name: String.t() | nil, + hub_emoji: String.t() | nil + } + + embedded_schema do + field :hub_name, :string + field :hub_emoji, :string + end + + @fields ~w(hub_name hub_emoji)a + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking hub changes. + """ + @spec change_hub(t(), map()) :: Ecto.Changeset.t() + def change_hub(%__MODULE__{} = personal, attrs \\ %{}) do + personal + |> changeset(attrs) + |> Map.put(:action, :validate) + end + + @doc """ + Updates a Hub. + + With success, notifies interested processes about hub metadatas data change. + Otherwise, it will return an error tuple with changeset. + """ + @spec update_hub(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def update_hub(%__MODULE__{} = personal, attrs) do + changeset = changeset(personal, attrs) + + with {:ok, struct} <- apply_action(changeset, :update) do + Hubs.save_hub(struct) + {:ok, struct} + end + end + + defp changeset(personal, attrs) do + personal + |> cast(attrs, @fields) + |> validate_required(@fields) + |> put_change(:id, "personal-hub") + end +end + +defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do + def load(personal, fields) do + %{personal | id: fields.id, hub_name: fields.hub_name, hub_emoji: fields.hub_emoji} + end + + def to_metadata(personal) do + %Livebook.Hubs.Metadata{ + id: personal.id, + name: personal.hub_name, + provider: personal, + emoji: personal.hub_emoji, + connected?: false + } + end + + def type(_personal), do: "personal" + + def connection_spec(_personal), do: nil + + def disconnect(_personal), do: :ok + + def capabilities(_personal), do: [] + + def get_secrets(_personal), do: [] + + def create_secret(_personal, _secret), do: :ok + + def connection_error(_personal), do: nil +end diff --git a/lib/livebook_web/live/hub/edit/personal_component.ex b/lib/livebook_web/live/hub/edit/personal_component.ex new file mode 100644 index 000000000..a25e68bd4 --- /dev/null +++ b/lib/livebook_web/live/hub/edit/personal_component.ex @@ -0,0 +1,82 @@ +defmodule LivebookWeb.Hub.Edit.PersonalComponent do + use LivebookWeb, :live_component + + alias Livebook.Hubs.Personal + + @impl true + def update(assigns, socket) do + changeset = Personal.change_hub(assigns.hub) + + {:ok, + socket + |> assign(assigns) + |> assign(changeset: changeset)} + end + + @impl true + def render(assigns) do + ~H""" +