2022-08-26 04:24:24 +08:00
|
|
|
defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|
|
|
use LivebookWeb, :live_component
|
|
|
|
|
2023-02-07 07:37:11 +08:00
|
|
|
alias Livebook.Hubs
|
|
|
|
alias Livebook.Secrets
|
2023-02-01 06:17:05 +08:00
|
|
|
alias Livebook.Secrets.Secret
|
2023-02-07 07:37:11 +08:00
|
|
|
alias Livebook.Session
|
2023-02-23 17:40:32 +08:00
|
|
|
alias Livebook.EctoTypes.SecretOrigin
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def mount(socket) do
|
|
|
|
{:ok, assign(socket, title: title(socket), hubs: Livebook.Hubs.get_hubs([:secrets]))}
|
|
|
|
end
|
2023-01-07 03:14:44 +08:00
|
|
|
|
2022-09-03 02:04:41 +08:00
|
|
|
@impl true
|
|
|
|
def update(assigns, socket) do
|
2023-02-23 17:40:32 +08:00
|
|
|
socket = assign(socket, assigns)
|
|
|
|
|
|
|
|
secret_name = socket.assigns[:prefill_secret_name]
|
|
|
|
|
2023-01-11 04:12:35 +08:00
|
|
|
socket =
|
|
|
|
socket
|
2023-02-23 17:40:32 +08:00
|
|
|
|> assign_new(:changeset, fn ->
|
|
|
|
attrs = %{name: secret_name, value: nil, origin: :session}
|
|
|
|
Secrets.change_secret(%Secret{}, attrs)
|
|
|
|
end)
|
|
|
|
|> assign_new(:grant_access_secret, fn ->
|
|
|
|
Enum.find(socket.assigns.saved_secrets, &(&1.name == secret_name))
|
|
|
|
end)
|
2023-01-11 04:12:35 +08:00
|
|
|
|
2023-02-23 17:40:32 +08:00
|
|
|
{:ok, socket}
|
2022-09-03 02:04:41 +08:00
|
|
|
end
|
|
|
|
|
2022-08-26 04:24:24 +08:00
|
|
|
@impl true
|
|
|
|
def render(assigns) do
|
|
|
|
~H"""
|
2022-09-01 05:53:23 +08:00
|
|
|
<div class="p-6 max-w-4xl flex flex-col space-y-5">
|
2022-08-26 04:24:24 +08:00
|
|
|
<h3 class="text-2xl font-semibold text-gray-800">
|
2022-09-28 10:25:07 +08:00
|
|
|
<%= @title %>
|
2022-08-26 04:24:24 +08:00
|
|
|
</h3>
|
2023-02-23 02:34:54 +08:00
|
|
|
<.grant_access_message
|
2023-02-23 17:40:32 +08:00
|
|
|
:if={@grant_access_secret}
|
|
|
|
secret={@grant_access_secret}
|
2023-02-23 02:34:54 +08:00
|
|
|
target={@myself}
|
|
|
|
/>
|
2022-09-28 04:26:21 +08:00
|
|
|
<div class="flex flex-columns gap-4">
|
2023-02-23 02:34:54 +08:00
|
|
|
<div :if={@select_secret_ref} class="basis-1/2 grow-0 pr-4 border-r">
|
|
|
|
<div class="flex flex-col space-y-4">
|
|
|
|
<p class="text-gray-800">
|
|
|
|
Choose a secret
|
|
|
|
</p>
|
|
|
|
<div class="flex flex-wrap">
|
|
|
|
<.secret_with_badge
|
|
|
|
:for={{secret_name, _} <- Enum.sort(@secrets)}
|
|
|
|
secret_name={secret_name}
|
2023-02-23 17:40:32 +08:00
|
|
|
secret_origin={:session}
|
2023-02-23 02:34:54 +08:00
|
|
|
stored="Session"
|
|
|
|
active={secret_name == @prefill_secret_name}
|
|
|
|
target={@myself}
|
|
|
|
/>
|
|
|
|
<.secret_with_badge
|
|
|
|
:for={secret <- @saved_secrets}
|
|
|
|
secret_name={secret.name}
|
2023-02-23 17:40:32 +08:00
|
|
|
secret_origin={secret.origin}
|
2023-02-23 02:34:54 +08:00
|
|
|
stored={stored(secret)}
|
|
|
|
active={false}
|
|
|
|
target={@myself}
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
:if={@secrets == %{} and @saved_secrets == []}
|
|
|
|
class="w-full text-center text-gray-400 border rounded-lg p-8"
|
|
|
|
>
|
|
|
|
<.remix_icon icon="folder-lock-line" class="align-middle text-2xl" />
|
|
|
|
<span class="mt-1 block text-sm text-gray-700">
|
|
|
|
Secrets not found. <br /> Add to see them here.
|
|
|
|
</span>
|
2022-09-28 04:26:21 +08:00
|
|
|
</div>
|
2022-08-26 04:24:24 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-23 02:34:54 +08:00
|
|
|
</div>
|
2022-09-17 04:56:40 +08:00
|
|
|
<.form
|
2022-10-04 14:46:55 +08:00
|
|
|
:let={f}
|
2023-02-07 07:37:11 +08:00
|
|
|
for={@changeset}
|
2023-02-05 00:50:50 +08:00
|
|
|
phx-target={@myself}
|
2022-09-28 04:26:21 +08:00
|
|
|
phx-change="validate"
|
2023-02-05 00:50:50 +08:00
|
|
|
phx-submit="save"
|
2022-09-28 04:26:21 +08:00
|
|
|
autocomplete="off"
|
|
|
|
class="basis-1/2 grow"
|
2022-09-17 04:56:40 +08:00
|
|
|
>
|
2022-09-28 04:26:21 +08:00
|
|
|
<div class="flex flex-col space-y-4">
|
2023-02-23 02:34:54 +08:00
|
|
|
<p :if={@select_secret_ref} class="text-gray-700">
|
|
|
|
Add new secret
|
|
|
|
</p>
|
|
|
|
<.text_field
|
|
|
|
field={f[:name]}
|
|
|
|
label="Name (alphanumeric and underscore)"
|
2023-02-23 17:40:32 +08:00
|
|
|
autofocus={@prefill_secret_name == nil}
|
2023-02-23 02:34:54 +08:00
|
|
|
spellcheck="false"
|
|
|
|
autocomplete="off"
|
|
|
|
phx-debounce="blur"
|
|
|
|
/>
|
|
|
|
<.text_field
|
|
|
|
field={f[:value]}
|
|
|
|
label="Value"
|
2023-02-23 17:40:32 +08:00
|
|
|
autofocus={@prefill_secret_name != nil}
|
2023-02-23 02:34:54 +08:00
|
|
|
spellcheck="false"
|
|
|
|
autocomplete="off"
|
|
|
|
phx-debounce="blur"
|
|
|
|
/>
|
|
|
|
<.radio_field
|
|
|
|
field={f[:origin]}
|
|
|
|
label="Storage"
|
|
|
|
options={
|
|
|
|
[{"session", "only this session"}, {"app", "in the Livebook app"}] ++
|
|
|
|
if Livebook.Config.feature_flag_enabled?(:hub) do
|
|
|
|
for hub <- @hubs, do: {"hub-#{hub.id}", "in #{hub.hub_emoji} #{hub.hub_name}"}
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
}
|
|
|
|
/>
|
2022-09-28 04:26:21 +08:00
|
|
|
<div class="flex space-x-2">
|
2023-02-23 17:40:32 +08:00
|
|
|
<button class="button-base button-blue" type="submit" disabled={not @changeset.valid?}>
|
2022-09-28 04:26:21 +08:00
|
|
|
<.remix_icon icon="add-line" class="align-middle" />
|
|
|
|
<span class="font-normal">Add</span>
|
|
|
|
</button>
|
2023-02-23 02:34:54 +08:00
|
|
|
<.link patch={@return_to} class="button-base button-outlined-gray">
|
|
|
|
Cancel
|
|
|
|
</.link>
|
2022-09-28 04:26:21 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-09-17 04:56:40 +08:00
|
|
|
</.form>
|
2022-09-28 04:26:21 +08:00
|
|
|
</div>
|
2022-08-26 04:24:24 +08:00
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2022-10-07 00:41:26 +08:00
|
|
|
defp secret_with_badge(assigns) do
|
|
|
|
~H"""
|
|
|
|
<div
|
|
|
|
role="button"
|
2022-10-07 03:44:04 +08:00
|
|
|
class={[
|
|
|
|
"flex justify-between w-full font-mono text-sm p-2 border-b cursor-pointer",
|
2022-10-07 00:41:26 +08:00
|
|
|
if @active do
|
2022-10-07 03:44:04 +08:00
|
|
|
"bg-blue-100 text-blue-700"
|
2022-10-07 00:41:26 +08:00
|
|
|
else
|
2022-10-07 03:44:04 +08:00
|
|
|
"text-gray-700 hover:bg-gray-100"
|
2022-10-07 00:41:26 +08:00
|
|
|
end
|
2022-10-07 03:44:04 +08:00
|
|
|
]}
|
2023-02-04 00:15:46 +08:00
|
|
|
phx-value-name={@secret_name}
|
2023-02-23 17:40:32 +08:00
|
|
|
phx-value-origin={SecretOrigin.encode(@secret_origin)}
|
2022-10-07 00:41:26 +08:00
|
|
|
phx-target={@target}
|
2023-02-23 17:40:32 +08:00
|
|
|
phx-click="select_secret"
|
2022-10-07 00:41:26 +08:00
|
|
|
>
|
|
|
|
<%= @secret_name %>
|
2022-10-07 03:44:04 +08:00
|
|
|
<span class={[
|
|
|
|
"inline-flex items-center font-sans rounded-full px-2.5 py-0.5 text-xs font-medium bg-gray-100",
|
2022-10-07 00:41:26 +08:00
|
|
|
if @active do
|
2022-10-07 03:44:04 +08:00
|
|
|
"bg-indigo-100 text-blue-800"
|
2022-10-07 00:41:26 +08:00
|
|
|
else
|
2022-10-07 03:44:04 +08:00
|
|
|
"bg-gray-100 text-gray-800"
|
2022-10-07 00:41:26 +08:00
|
|
|
end
|
2022-10-07 03:44:04 +08:00
|
|
|
]}>
|
2023-02-23 02:34:54 +08:00
|
|
|
<svg
|
|
|
|
:if={@active}
|
|
|
|
class="-ml-0.5 mr-1.5 h-2 w-2 text-blue-400"
|
|
|
|
fill="currentColor"
|
|
|
|
viewBox="0 0 8 8"
|
|
|
|
>
|
|
|
|
<circle cx="4" cy="4" r="3" />
|
|
|
|
</svg>
|
2022-10-07 00:41:26 +08:00
|
|
|
<%= @stored %>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp grant_access_message(assigns) do
|
|
|
|
~H"""
|
|
|
|
<div>
|
|
|
|
<div class="mx-auto">
|
2022-10-07 03:44:04 +08:00
|
|
|
<div class="rounded-lg bg-blue-600 py-1 px-4 shadow-sm">
|
2022-10-07 00:41:26 +08:00
|
|
|
<div class="flex flex-wrap items-center justify-between">
|
|
|
|
<div class="flex w-0 flex-1 items-center">
|
|
|
|
<.remix_icon
|
|
|
|
icon="error-warning-fill"
|
|
|
|
class="align-middle text-2xl flex text-gray-100 rounded-lg py-2"
|
|
|
|
/>
|
2023-02-23 17:40:32 +08:00
|
|
|
<%= if @secret.origin in [:app, :startup] do %>
|
2023-02-04 00:15:46 +08:00
|
|
|
<span class="ml-2 text-sm font-normal text-gray-100">
|
|
|
|
There is a secret named
|
2023-02-23 17:40:32 +08:00
|
|
|
<span class="font-semibold text-white"><%= @secret.name %></span>
|
2023-02-04 00:15:46 +08:00
|
|
|
in your Livebook app. Allow this session to access it?
|
|
|
|
</span>
|
|
|
|
<% else %>
|
|
|
|
<span class="ml-2 text-sm font-normal text-gray-100">
|
|
|
|
There is a secret named
|
2023-02-23 17:40:32 +08:00
|
|
|
<span class="font-semibold text-white"><%= @secret.name %></span>
|
2023-02-04 00:15:46 +08:00
|
|
|
in your Livebook Hub. Allow this session to access it?
|
|
|
|
</span>
|
|
|
|
<% end %>
|
2022-10-07 00:41:26 +08:00
|
|
|
</div>
|
2023-02-23 17:40:32 +08:00
|
|
|
<button
|
|
|
|
class="button-base button-gray"
|
|
|
|
phx-click="grant_access"
|
|
|
|
phx-value-name={@secret.name}
|
|
|
|
phx-value-origin={SecretOrigin.encode(@secret.origin)}
|
|
|
|
phx-target={@target}
|
|
|
|
>
|
|
|
|
Grant access
|
|
|
|
</button>
|
2022-10-07 00:41:26 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2023-02-01 06:17:05 +08:00
|
|
|
defp stored(%{origin: {:hub, _}}), do: "Hub"
|
|
|
|
defp stored(%{origin: origin}) when origin in [:app, :startup], do: "Livebook"
|
|
|
|
|
2022-08-26 04:24:24 +08:00
|
|
|
@impl true
|
2023-02-07 07:37:11 +08:00
|
|
|
def handle_event("save", %{"secret" => attrs}, socket) do
|
2023-02-23 17:40:32 +08:00
|
|
|
with {:ok, secret} <- Secrets.update_secret(%Secret{}, attrs),
|
2023-02-01 06:17:05 +08:00
|
|
|
:ok <- set_secret(socket, secret) do
|
2023-01-07 03:14:44 +08:00
|
|
|
{:noreply,
|
|
|
|
socket
|
|
|
|
|> push_patch(to: socket.assigns.return_to)
|
|
|
|
|> push_secret_selected(secret.name)}
|
|
|
|
else
|
2023-02-07 07:37:11 +08:00
|
|
|
{:error, changeset} ->
|
|
|
|
{:noreply, assign(socket, changeset: changeset)}
|
2022-09-06 05:59:13 +08:00
|
|
|
end
|
2022-08-26 04:24:24 +08:00
|
|
|
end
|
|
|
|
|
2023-02-23 17:40:32 +08:00
|
|
|
def handle_event("select_secret", %{"name" => secret_name, "origin" => origin}, socket) do
|
|
|
|
{:ok, origin} = SecretOrigin.decode(origin)
|
|
|
|
grant_access(socket.assigns.saved_secrets, secret_name, origin, socket)
|
2022-10-07 00:41:26 +08:00
|
|
|
|
2022-09-17 04:56:40 +08:00
|
|
|
{:noreply,
|
2023-02-01 06:17:05 +08:00
|
|
|
socket
|
|
|
|
|> push_patch(to: socket.assigns.return_to)
|
|
|
|
|> push_secret_selected(secret_name)}
|
2022-09-17 04:56:40 +08:00
|
|
|
end
|
|
|
|
|
2023-02-07 07:37:11 +08:00
|
|
|
def handle_event("validate", %{"secret" => attrs}, socket) do
|
2023-02-23 17:40:32 +08:00
|
|
|
changeset =
|
|
|
|
%Secret{}
|
|
|
|
|> Secrets.change_secret(attrs)
|
|
|
|
|> Map.put(:action, :validate)
|
|
|
|
|
|
|
|
{:noreply, assign(socket, changeset: changeset)}
|
2022-08-26 04:24:24 +08:00
|
|
|
end
|
|
|
|
|
2023-02-23 17:40:32 +08:00
|
|
|
def handle_event("grant_access", %{"name" => secret_name, "origin" => origin}, socket) do
|
|
|
|
{:ok, origin} = SecretOrigin.decode(origin)
|
|
|
|
grant_access(socket.assigns.saved_secrets, secret_name, origin, socket)
|
2022-10-07 00:41:26 +08:00
|
|
|
|
|
|
|
{:noreply,
|
2023-02-01 06:17:05 +08:00
|
|
|
socket
|
|
|
|
|> push_patch(to: socket.assigns.return_to)
|
|
|
|
|> push_secret_selected(secret_name)}
|
2022-10-07 00:41:26 +08:00
|
|
|
end
|
|
|
|
|
2022-09-17 04:56:40 +08:00
|
|
|
defp push_secret_selected(%{assigns: %{select_secret_ref: nil}} = socket, _), do: socket
|
|
|
|
|
2022-09-19 05:40:09 +08:00
|
|
|
defp push_secret_selected(%{assigns: %{select_secret_ref: ref}} = socket, secret_name) do
|
|
|
|
push_event(socket, "secret_selected", %{select_secret_ref: ref, secret_name: secret_name})
|
2022-09-17 04:56:40 +08:00
|
|
|
end
|
|
|
|
|
2022-09-28 10:25:07 +08:00
|
|
|
defp title(%{assigns: %{select_secret_ref: nil}}), do: "Add secret"
|
|
|
|
defp title(%{assigns: %{select_secret_options: %{"title" => title}}}), do: title
|
|
|
|
defp title(_), do: "Select secret"
|
2022-10-07 00:41:26 +08:00
|
|
|
|
2023-02-01 06:17:05 +08:00
|
|
|
defp set_secret(socket, %Secret{origin: :session} = secret) do
|
2023-02-07 07:37:11 +08:00
|
|
|
Session.set_secret(socket.assigns.session.pid, secret)
|
2022-10-11 22:12:14 +08:00
|
|
|
end
|
|
|
|
|
2023-02-01 06:17:05 +08:00
|
|
|
defp set_secret(socket, %Secret{origin: :app} = secret) do
|
2023-02-07 07:37:11 +08:00
|
|
|
Secrets.set_secret(secret)
|
|
|
|
Session.set_secret(socket.assigns.session.pid, secret)
|
2023-01-07 03:14:44 +08:00
|
|
|
end
|
|
|
|
|
2023-02-04 00:15:46 +08:00
|
|
|
defp set_secret(socket, %Secret{origin: {:hub, id}} = secret) when is_binary(id) do
|
2023-02-07 07:37:11 +08:00
|
|
|
with :ok <- Hubs.create_secret(secret) do
|
|
|
|
Session.set_secret(socket.assigns.session.pid, secret)
|
2023-02-04 00:15:46 +08:00
|
|
|
end
|
2022-10-11 22:12:14 +08:00
|
|
|
end
|
2022-10-07 00:41:26 +08:00
|
|
|
|
2023-02-01 06:17:05 +08:00
|
|
|
defp grant_access(secrets, secret_name, origin, socket) do
|
|
|
|
secret = Enum.find(secrets, &(&1.name == secret_name and &1.origin == origin))
|
2022-10-07 00:41:26 +08:00
|
|
|
|
2023-02-04 00:15:46 +08:00
|
|
|
if secret, do: Livebook.Session.set_secret(socket.assigns.session.pid, secret)
|
2023-02-01 06:17:05 +08:00
|
|
|
end
|
2022-08-26 04:24:24 +08:00
|
|
|
end
|