From 8754bc0d5a2498c38e4aba81e68b29bd39377f66 Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Tue, 7 Mar 2023 15:24:07 -0300 Subject: [PATCH] Shows only the secrets from selected hub (#1747) --- lib/livebook/application.ex | 29 +++-- lib/livebook/ecto_types/secret_origin.ex | 50 --------- lib/livebook/hubs.ex | 34 +++--- lib/livebook/hubs/enterprise.ex | 2 +- lib/livebook/hubs/enterprise_client.ex | 2 +- lib/livebook/hubs/personal.ex | 36 +++++- lib/livebook/hubs/provider.ex | 2 +- lib/livebook/secrets.ex | 106 +++++++----------- lib/livebook/secrets/secret.ex | 12 +- lib/livebook/session/data.ex | 2 +- lib/livebook_web/live/hooks/sidebar_hook.ex | 2 +- lib/livebook_web/live/session_live.ex | 54 +++------ .../live/session_live/secrets_component.ex | 104 +++++++---------- .../session_live/secrets_list_component.ex | 65 +++++------ test/livebook/hubs/enterprise_client_test.exs | 8 +- test/livebook/hubs/provider_test.exs | 31 +++-- test/livebook/secrets_test.exs | 57 ++++------ .../session_live/secrets_component_test.exs | 32 ++++-- test/livebook_web/live/session_live_test.exs | 74 ++++++------ test/support/factory.ex | 8 +- test/support/session_helpers.ex | 11 +- 21 files changed, 320 insertions(+), 401 deletions(-) delete mode 100644 lib/livebook/ecto_types/secret_origin.ex diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index c98ddce26..1bd998d11 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -57,7 +57,7 @@ defmodule Livebook.Application do display_startup_info() insert_personal_hub() Livebook.Hubs.connect_hubs() - update_app_secrets_origin() + migrate_secrets() deploy_apps() result @@ -191,10 +191,16 @@ defmodule Livebook.Application do secrets = for {"LB_" <> name = var, value} <- System.get_env() do System.delete_env(var) - %Livebook.Secrets.Secret{name: name, value: value, origin: :startup} + + %Livebook.Secrets.Secret{ + name: name, + value: value, + hub_id: Livebook.Hubs.Personal.id(), + readonly: true + } end - Livebook.Secrets.set_startup_secrets(secrets) + Livebook.Hubs.Personal.set_startup_secrets(secrets) end defp config_env_var?("LIVEBOOK_" <> _), do: true @@ -208,9 +214,9 @@ defmodule Livebook.Application do end defp insert_personal_hub do - unless Livebook.Hubs.hub_exists?("personal-hub") do + unless Livebook.Hubs.hub_exists?(Livebook.Hubs.Personal.id()) do Livebook.Hubs.save_hub(%Livebook.Hubs.Personal{ - id: "personal-hub", + id: Livebook.Hubs.Personal.id(), hub_name: "My Hub", hub_emoji: "🏠" }) @@ -218,10 +224,17 @@ defmodule Livebook.Application do end # TODO: Remove in the future - defp update_app_secrets_origin do - for %{origin: :app} = secret <- Livebook.Secrets.get_secrets() do - {:ok, secret} = Livebook.Secrets.update_secret(secret, %{origin: {:hub, "personal-hub"}}) + defp migrate_secrets do + for %{name: name, value: value} <- Livebook.Storage.all(:secrets) do + secret = %Livebook.Secrets.Secret{ + name: name, + value: value, + hub_id: Livebook.Hubs.Personal.id(), + readonly: false + } + Livebook.Secrets.set_secret(secret) + Livebook.Storage.delete(:secrets, name) end end diff --git a/lib/livebook/ecto_types/secret_origin.ex b/lib/livebook/ecto_types/secret_origin.ex deleted file mode 100644 index 249063498..000000000 --- a/lib/livebook/ecto_types/secret_origin.ex +++ /dev/null @@ -1,50 +0,0 @@ -defmodule Livebook.EctoTypes.SecretOrigin do - @moduledoc false - - use Ecto.Type - - @type t :: nil | :session | :startup | {:hub, String.t()} - - @impl true - def type, do: :string - - @impl true - def load(origin), do: decode(origin) - - @impl true - def dump(:session), do: {:ok, "session"} - def dump(:startup), do: {:ok, "startup"} - def dump({:hub, id}), do: {:ok, "hub-#{id}"} - def dump(_), do: :error - - @impl true - def cast(:session), do: {:ok, :session} - def cast(:startup), do: {:ok, :startup} - def cast({:hub, id}), do: {:ok, {:hub, id}} - - def cast(encoded) when is_binary(encoded) do - case decode(encoded) do - {:ok, origin} -> {:ok, origin} - :error -> {:error, message: "is invalid"} - end - end - - def cast(_), do: {:error, message: "is invalid"} - - @doc """ - Encodes origin into string representation. - """ - @spec encode(t()) :: String.t() - def encode(:session), do: "session" - def encode(:startup), do: "startup" - def encode({:hub, id}), do: "hub-#{id}" - - @doc """ - Decodes origin from string representation. - """ - @spec decode(String.t()) :: {:ok, t()} | :error - def decode("session"), do: {:ok, :session} - def decode("startup"), do: {:ok, :startup} - def decode("hub-" <> id), do: {:ok, {:hub, id}} - def decode(_other), do: :error -end diff --git a/lib/livebook/hubs.ex b/lib/livebook/hubs.ex index abdf14354..0c5a8175e 100644 --- a/lib/livebook/hubs.ex +++ b/lib/livebook/hubs.ex @@ -205,12 +205,24 @@ defmodule Livebook.Hubs do do: secret end + @doc """ + Gets a list of secrets for given hub. + """ + @spec get_secrets(Provider.t()) :: list(Secret.t()) + def get_secrets(hub) do + if capability?(hub, [:list_secrets]) do + hub |> Provider.get_secrets() |> Enum.sort() + else + [] + end + end + @doc """ Creates a secret for given hub. """ - @spec create_secret(Secret.t()) :: :ok | {:error, list({atom(), list(String.t())})} - def create_secret(%Secret{origin: {:hub, id}} = secret) do - {:ok, hub} = get_hub(id) + @spec create_secret(Provider.t(), Secret.t()) :: + :ok | {:error, list({atom(), list(String.t())})} + def create_secret(hub, %Secret{} = secret) do true = capability?(hub, [:create_secret]) Provider.create_secret(hub, secret) @@ -219,22 +231,18 @@ defmodule Livebook.Hubs do @doc """ Updates a secret for given hub. """ - @spec update_secret(Secret.t()) :: :ok | {:error, list({atom(), list(String.t())})} - def update_secret(%Secret{origin: {:hub, id}} = secret) do - {:ok, hub} = get_hub(id) - true = capability?(hub, [:update_secret]) - + @spec update_secret(Provider.t(), Secret.t()) :: + :ok | {:error, list({atom(), list(String.t())})} + def update_secret(hub, %Secret{readonly: false} = secret) do Provider.update_secret(hub, secret) end @doc """ Deletes a secret for given hub. """ - @spec delete_secret(Secret.t()) :: :ok | {:error, list({atom(), list(String.t())})} - def delete_secret(%Secret{origin: {:hub, id}} = secret) do - {:ok, hub} = get_hub(id) - true = capability?(hub, [:delete_secret]) - + @spec delete_secret(Provider.t(), Secret.t()) :: + :ok | {:error, list({atom(), list(String.t())})} + def delete_secret(hub, %Secret{readonly: false} = secret) do Provider.delete_secret(hub, secret) end diff --git a/lib/livebook/hubs/enterprise.ex b/lib/livebook/hubs/enterprise.ex index a30c40650..696928ec6 100644 --- a/lib/livebook/hubs/enterprise.ex +++ b/lib/livebook/hubs/enterprise.ex @@ -171,7 +171,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Enterprise do {:transport_error, reason} -> message = "#{enterprise.hub_emoji} #{enterprise.hub_name}: #{reason}" - changeset = Livebook.Secrets.add_secret_error(secret, :origin, message) + changeset = Livebook.Secrets.add_secret_error(secret, :hub_id, message) {:error, changeset} end diff --git a/lib/livebook/hubs/enterprise_client.ex b/lib/livebook/hubs/enterprise_client.ex index e0fc793fe..56560582b 100644 --- a/lib/livebook/hubs/enterprise_client.ex +++ b/lib/livebook/hubs/enterprise_client.ex @@ -154,7 +154,7 @@ defmodule Livebook.Hubs.EnterpriseClient do end defp build_secret(state, %{name: name, value: value}), - do: %Secret{name: name, value: value, origin: {:hub, state.hub.id}} + do: %Secret{name: name, value: value, hub_id: state.hub.id, readonly: true} defp update_hub(state, name) do case Enterprise.update_hub(state.hub, %{hub_name: name}) do diff --git a/lib/livebook/hubs/personal.ex b/lib/livebook/hubs/personal.ex index 988cf9252..9eb5f7289 100644 --- a/lib/livebook/hubs/personal.ex +++ b/lib/livebook/hubs/personal.ex @@ -19,6 +19,12 @@ defmodule Livebook.Hubs.Personal do @fields ~w(hub_name hub_emoji)a + @doc """ + The personal hub fixed id. + """ + @spec id() :: String.t() + def id, do: "personal-hub" + @doc """ Returns an `%Ecto.Changeset{}` for tracking hub changes. """ @@ -57,7 +63,25 @@ defmodule Livebook.Hubs.Personal do personal |> cast(attrs, @fields) |> validate_required(@fields) - |> put_change(:id, "personal-hub") + |> put_change(:id, id()) + end + + @secret_startup_key :livebook_startup_secrets + + @doc """ + Get the startup secrets list from persistent term. + """ + @spec get_startup_secrets() :: list(Secret.t()) + def get_startup_secrets do + :persistent_term.get(@secret_startup_key, []) + end + + @doc """ + Sets additional secrets that are kept only in memory. + """ + @spec set_startup_secrets(list(Secret.t())) :: :ok + def set_startup_secrets(secrets) do + :persistent_term.put(@secret_startup_key, secrets) end end @@ -85,10 +109,10 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do def disconnect(_personal), do: raise("not implemented") - def capabilities(_personal), do: ~w(list_secrets create_secret update_secret delete_secret)a + def capabilities(_personal), do: ~w(list_secrets create_secret)a - def get_secrets(_personal) do - Secrets.get_secrets() + def get_secrets(personal) do + Secrets.get_secrets(personal) ++ Livebook.Hubs.Personal.get_startup_secrets() end def create_secret(_personal, secret) do @@ -101,8 +125,8 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do :ok = Broadcasts.secret_updated(secret) end - def delete_secret(_personal, secret) do - :ok = Secrets.unset_secret(secret.name) + def delete_secret(personal, secret) do + :ok = Secrets.unset_secret(personal, secret.name) :ok = Broadcasts.secret_deleted(secret) end diff --git a/lib/livebook/hubs/provider.ex b/lib/livebook/hubs/provider.ex index dc6886e71..df239f0ba 100644 --- a/lib/livebook/hubs/provider.ex +++ b/lib/livebook/hubs/provider.ex @@ -4,7 +4,7 @@ defprotocol Livebook.Hubs.Provider do alias Livebook.Secrets.Secret @type t :: Livebook.Hubs.Enterprise.t() | Livebook.Hubs.Fly.t() | Livebook.Hubs.Personal.t() - @type capability :: :connect | :list_secrets | :create_secret | :update_secret | :delete_secret + @type capability :: :connect | :list_secrets | :create_secret @type capabilities :: list(capability()) @type changeset_errors :: %{required(:errors) => list({String.t(), {Stirng.t(), list()}})} diff --git a/lib/livebook/secrets.ex b/lib/livebook/secrets.ex index 96d57ba5e..b97c32ce4 100644 --- a/lib/livebook/secrets.ex +++ b/lib/livebook/secrets.ex @@ -1,50 +1,46 @@ defmodule Livebook.Secrets do @moduledoc false + alias Livebook.Hubs.Provider alias Livebook.Storage alias Livebook.Secrets.Secret - @secret_startup_key :livebook_startup_secrets + @namespace :hub_secrets @doc """ Get the secrets list from storage. """ - @spec get_secrets() :: list(Secret.t()) - def get_secrets do - startup_secrets = :persistent_term.get(@secret_startup_key, []) - storage_secrets = for fields <- Storage.all(:secrets), do: to_struct(fields) - - Enum.concat(storage_secrets, startup_secrets) + @spec get_secrets(Provider.t()) :: list(Secret.t()) + def get_secrets(hub) do + for fields <- Storage.all(@namespace), + from_hub?(fields, hub), + do: to_struct(fields) end @doc """ Gets a secret from storage. Raises `RuntimeError` if the secret doesn't exist. """ - @spec fetch_secret!(String.t()) :: Secret.t() - def fetch_secret!(id) do - fields = Storage.fetch!(:secrets, id) + @spec fetch_secret!(Provider.t(), String.t()) :: Secret.t() + def fetch_secret!(hub, id) do + fields = Storage.fetch!(@namespace, id) + true = from_hub?(fields, hub) + to_struct(fields) end @doc """ Gets a secret from storage. """ - @spec get_secret(String.t()) :: {:ok, Secret.t()} | :error - def get_secret(id) do - with {:ok, fields} <- Storage.fetch(:secrets, id) do - {:ok, to_struct(fields)} + @spec get_secret(Provider.t(), String.t()) :: {:ok, Secret.t()} | :error + def get_secret(hub, id) do + with {:ok, fields} <- Storage.fetch(@namespace, id) do + if from_hub?(fields, hub), + do: {:ok, to_struct(fields)}, + else: :error end end - @doc """ - Checks if the secret already exists. - """ - @spec secret_exists?(String.t()) :: boolean() - def secret_exists?(id) do - Storage.fetch(:secrets, id) != :error - end - @doc """ Returns an `%Ecto.Changeset{}` for tracking secret changes. """ @@ -83,63 +79,39 @@ defmodule Livebook.Secrets do """ @spec set_secret(Secret.t()) :: Secret.t() def set_secret(secret) do - attributes = Map.from_struct(secret) + attributes = + secret + |> Map.from_struct() + |> Map.delete(:readonly) - :ok = Storage.insert(:secrets, secret.name, Map.to_list(attributes)) - :ok = broadcast_secrets_change({:set_secret, secret}) + :ok = Storage.insert(@namespace, secret.name, Map.to_list(attributes)) - to_struct(attributes) + secret end @doc """ Unset secret from given id. """ - @spec unset_secret(String.t()) :: :ok - def unset_secret(id) do - with {:ok, secret} <- get_secret(id) do - Storage.delete(:secrets, id) - broadcast_secrets_change({:unset_secret, secret}) + @spec unset_secret(Provider.t(), String.t()) :: :ok + def unset_secret(hub, id) do + with {:ok, _secret} <- get_secret(hub, id) do + Storage.delete(@namespace, id) end :ok end - @doc """ - Sets additional secrets that are kept only in memory. - """ - @spec set_startup_secrets(list(Secret.t())) :: :ok - def set_startup_secrets(secrets) do - :persistent_term.put(@secret_startup_key, secrets) - end - - @doc """ - Subscribe to secrets updates. - - ## Messages - - * `{:set_secret, secret}` - * `{:unset_secret, secret}` - - """ - @spec subscribe() :: :ok | {:error, term()} - def subscribe do - Phoenix.PubSub.subscribe(Livebook.PubSub, "secrets") - end - - @doc """ - Unsubscribes from `subscribe/0`. - """ - @spec unsubscribe() :: :ok - def unsubscribe do - Phoenix.PubSub.unsubscribe(Livebook.PubSub, "secrets") - end - - defp broadcast_secrets_change(message) do - Phoenix.PubSub.broadcast(Livebook.PubSub, "secrets", message) - end - defp to_struct(%{name: name, value: value} = fields) do - # Previously stored secrets were all `:app`-based secrets - %Secret{name: name, value: value, origin: fields[:origin] || :app} + %Secret{ + name: name, + value: value, + hub_id: fields[:hub_id] || Livebook.Hubs.Personal.id(), + readonly: false + } + end + + defp from_hub?(fields, hub) do + hub_id = fields[:hub_id] || Livebook.Hubs.Personal.id() + hub_id == hub.id end end diff --git a/lib/livebook/secrets/secret.ex b/lib/livebook/secrets/secret.ex index 9328d9f59..5f43d2c83 100644 --- a/lib/livebook/secrets/secret.ex +++ b/lib/livebook/secrets/secret.ex @@ -3,27 +3,27 @@ defmodule Livebook.Secrets.Secret do use Ecto.Schema import Ecto.Changeset - alias Livebook.EctoTypes.SecretOrigin - @type t :: %__MODULE__{ name: String.t() | nil, value: String.t() | nil, - origin: SecretOrigin.t() + hub_id: String.t() | nil, + readonly: boolean() } @primary_key {:name, :string, autogenerate: false} embedded_schema do field :value, :string - field :origin, SecretOrigin + field :hub_id, :string + field :readonly, :boolean, virtual: true, default: false end def changeset(secret, attrs \\ %{}) do secret - |> cast(attrs, [:name, :value, :origin]) + |> cast(attrs, [:name, :value, :hub_id]) |> update_change(:name, &String.upcase/1) |> validate_format(:name, ~r/^\w+$/, message: "should contain only alphanumeric characters and underscore" ) - |> validate_required([:name, :value, :origin]) + |> validate_required([:name, :value, :hub_id]) end end diff --git a/lib/livebook/session/data.ex b/lib/livebook/session/data.ex index c526afb10..a7e40355a 100644 --- a/lib/livebook/session/data.ex +++ b/lib/livebook/session/data.ex @@ -278,7 +278,7 @@ defmodule Livebook.Session.Data do mode: opts[:mode], apps: [], app_data: app_data, - hub: Livebook.Hubs.fetch_hub!("personal-hub") + hub: Livebook.Hubs.fetch_hub!(Livebook.Hubs.Personal.id()) } data diff --git a/lib/livebook_web/live/hooks/sidebar_hook.ex b/lib/livebook_web/live/hooks/sidebar_hook.ex index 89341c046..77b4e0ce2 100644 --- a/lib/livebook_web/live/hooks/sidebar_hook.ex +++ b/lib/livebook_web/live/hooks/sidebar_hook.ex @@ -24,7 +24,7 @@ defmodule LivebookWeb.SidebarHook do {:halt, put_flash(socket, :info, "Livebook is shutting down. You can close this page.")} end - @connection_events ~w(hub_connected hub_disconnected hubs_changed)a + @connection_events ~w(hub_connected hub_disconnected hub_changed)a defp handle_info(event, socket) when event in @connection_events do {:cont, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())} diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index ce54873cd..f03caa276 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -5,7 +5,7 @@ defmodule LivebookWeb.SessionLive do import LivebookWeb.SessionHelpers import Livebook.Utils, only: [format_bytes: 1] - alias Livebook.{Sessions, Session, Delta, Notebook, Runtime, LiveMarkdown, Secrets} + alias Livebook.{Sessions, Session, Delta, Notebook, Runtime, LiveMarkdown} alias Livebook.Notebook.{Cell, ContentLoader} alias Livebook.JSInterop alias Livebook.Hubs @@ -24,7 +24,6 @@ defmodule LivebookWeb.SessionLive do Session.register_client(session_pid, self(), socket.assigns.current_user) Session.subscribe(session_id) - Secrets.subscribe() Hubs.subscribe(:secrets) Livebook.NotebookManager.subscribe_starred_notebooks() @@ -61,7 +60,7 @@ defmodule LivebookWeb.SessionLive do data_view: data_to_view(data), autofocus_cell_id: autofocus_cell_id(data.notebook), page_title: get_page_title(data.notebook.name), - saved_secrets: get_saved_secrets(), + saved_secrets: Hubs.get_secrets(data.hub), select_secret_ref: nil, select_secret_options: nil, allowed_uri_schemes: Livebook.Config.allowed_uri_schemes(), @@ -201,7 +200,7 @@ defmodule LivebookWeb.SessionLive do id="secrets-list" session={@session} saved_secrets={@saved_secrets} - hubs={@saved_hubs} + hub={@data_view.notebook_hub} secrets={@data_view.secrets} /> @@ -521,6 +520,7 @@ defmodule LivebookWeb.SessionLive do id="secrets" session={@session} secrets={@data_view.secrets} + hub={@data_view.notebook_hub} saved_secrets={@saved_secrets} prefill_secret_name={@prefill_secret_name} select_secret_ref={@select_secret_ref} @@ -1223,29 +1223,29 @@ defmodule LivebookWeb.SessionLive do {:noreply, handle_operation(socket, operation)} end - def handle_info({:secret_created, %{origin: {:hub, _id}}}, socket) do + def handle_info({:secret_created, _secret}, socket) do {:noreply, socket - |> assign(saved_secrets: get_saved_secrets()) + |> refresh_secrets() |> put_flash(:info, "A new secret has been created on your Livebook Hub")} end - def handle_info({:secret_updated, %{origin: {:hub, _id}}}, socket) do + def handle_info({:secret_updated, _secret}, socket) do {:noreply, socket - |> assign(saved_secrets: get_saved_secrets()) + |> refresh_secrets() |> put_flash(:info, "An existing secret has been updated on your Livebook Hub")} end - def handle_info({:secret_deleted, %{origin: {:hub, _id}}}, socket) do + def handle_info({:secret_deleted, _secret}, socket) do {:noreply, socket - |> assign(saved_secrets: get_saved_secrets()) + |> refresh_secrets() |> put_flash(:info, "An existing secret has been deleted on your Livebook Hub")} end - def handle_info(:hubs_changed, socket) do - {:noreply, assign(socket, saved_secrets: get_saved_secrets())} + def handle_info(:hub_changed, socket) do + {:noreply, refresh_secrets(socket)} end def handle_info({:error, error}, socket) do @@ -1330,26 +1330,6 @@ defmodule LivebookWeb.SessionLive do handle_event("insert_cell_below", params, socket) end - def handle_info({:set_secret, secret}, socket) do - saved_secrets = - Enum.reject( - socket.assigns.saved_secrets, - &(&1.name == secret.name and &1.origin == secret.origin) - ) - - {:noreply, assign(socket, saved_secrets: [secret | saved_secrets])} - end - - def handle_info({:unset_secret, secret}, socket) do - saved_secrets = - Enum.reject( - socket.assigns.saved_secrets, - &(&1.name == secret.name and &1.origin == secret.origin) - ) - - {:noreply, assign(socket, saved_secrets: saved_secrets)} - end - def handle_info({:push_patch, to}, socket) do {:noreply, push_patch(socket, to: to)} end @@ -1656,6 +1636,9 @@ defmodule LivebookWeb.SessionLive do prune_cell_sources(socket) end + defp after_operation(socket, _prev_socket, {:set_notebook_hub, _client_id, _id}), + do: refresh_secrets(socket) + defp after_operation(socket, _prev_socket, _operation), do: socket defp handle_actions(socket, actions) do @@ -2098,14 +2081,13 @@ defmodule LivebookWeb.SessionLive do end) end - defp get_saved_secrets do - Enum.sort(Hubs.get_secrets()) - end - defp app_status_color(nil), do: "bg-gray-400" defp app_status_color(:booting), do: "bg-blue-500" defp app_status_color(:running), do: "bg-green-bright-400" defp app_status_color(:error), do: "bg-red-400" defp app_status_color(:shutting_down), do: "bg-gray-500" defp app_status_color(:stopped), do: "bg-gray-500" + + defp refresh_secrets(socket), + do: assign(socket, saved_secrets: Hubs.get_secrets(socket.private.data.hub)) end diff --git a/lib/livebook_web/live/session_live/secrets_component.ex b/lib/livebook_web/live/session_live/secrets_component.ex index b4c09b71c..50e26a1c6 100644 --- a/lib/livebook_web/live/session_live/secrets_component.ex +++ b/lib/livebook_web/live/session_live/secrets_component.ex @@ -5,11 +5,10 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do alias Livebook.Secrets alias Livebook.Secrets.Secret alias Livebook.Session - alias Livebook.EctoTypes.SecretOrigin @impl true def mount(socket) do - {:ok, assign(socket, title: title(socket), hubs: Livebook.Hubs.get_hubs([:create_secret]))} + {:ok, assign(socket, title: title(socket))} end @impl true @@ -21,11 +20,14 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do socket = socket |> assign_new(:changeset, fn -> - attrs = %{name: secret_name, value: nil, origin: :session} + attrs = %{name: secret_name, value: nil, hub_id: "session", readonly: false} Secrets.change_secret(%Secret{}, attrs) end) |> assign_new(:grant_access_secret, fn -> - Enum.find(socket.assigns.saved_secrets, &(&1.name == secret_name)) + Enum.find( + socket.assigns.saved_secrets, + &(&1.name == secret_name and secret_name not in Map.keys(socket.assigns.secrets)) + ) end) {:ok, socket} @@ -42,6 +44,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do :if={@grant_access_secret} secret={@grant_access_secret} target={@myself} + hub={@hub} />
@@ -60,9 +63,9 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do /> <.secret_with_badge :for={secret <- @saved_secrets} + :if={!is_map_key(@secrets, secret.name) and @secrets[secret.name] != secret.value} secret_name={secret.name} - secret_origin={secret.origin} - stored={stored(secret, @hubs)} + stored={hub_label(@hub)} active={false} target={@myself} /> @@ -109,17 +112,12 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do phx-debounce="blur" /> <.radio_field - field={f[:origin]} - value={SecretOrigin.encode(f[:origin].value)} + field={f[:hub_id]} label="Storage" - options={ - [{"session", "only this session"}] ++ - if Livebook.Config.feature_flag_enabled?(:hub) do - for hub <- @hubs, do: {"hub-#{hub.id}", "in #{hub.hub_emoji} #{hub.hub_name}"} - else - [] - end - } + options={[ + {"session", "only this session"}, + {@hub.id, "in #{@hub.hub_emoji} #{@hub.hub_name}"} + ]} />