Move secrets list into a separate component

This commit is contained in:
Jonatan Kłosko 2023-02-24 21:24:00 +01:00
parent 6fd2d4f3b7
commit 984a4f1690
2 changed files with 245 additions and 233 deletions

View file

@ -195,11 +195,13 @@ defmodule LivebookWeb.SessionLive do
<.clients_list data_view={@data_view} client_id={@client_id} />
</div>
<div data-el-secrets-list>
<.secrets_list
data_view={@data_view}
<.live_component
module={LivebookWeb.SessionLive.SecretsListComponent}
id="secrets-list"
session={@session}
saved_secrets={@saved_secrets}
hubs={@saved_hubs}
session={@session}
secrets={@data_view.secrets}
/>
</div>
<div data-el-app-info>
@ -621,201 +623,6 @@ defmodule LivebookWeb.SessionLive do
"""
end
defp secrets_list(assigns) do
~H"""
<div class="flex flex-col grow">
<div class="flex justify-between items-center">
<h3 class="uppercase text-sm font-semibold text-gray-500">
Secrets
</h3>
<.secrets_info_icon />
</div>
<span class="text-sm text-gray-500">Available only to this session</span>
<div class="flex flex-col">
<div class="flex flex-col space-y-4 mt-6">
<div
:for={{secret_name, secret_value} <- Enum.sort(@data_view.secrets)}
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
id={"session-secret-#{secret_name}-wrapper"}
>
<span
class="text-sm font-mono break-all flex-row cursor-pointer"
phx-click={
JS.toggle(to: "#session-secret-#{secret_name}-detail", display: "flex")
|> toggle_class("bg-gray-100", to: "#session-secret-#{secret_name}-wrapper")
}
>
<%= secret_name %>
</span>
<div
class="flex flex-row justify-between items-center my-1 hidden"
id={"session-secret-#{secret_name}-detail"}
>
<span class="text-sm font-mono break-all flex-row">
<%= secret_value %>
</span>
<button
id={"session-secret-#{secret_name}-delete"}
type="button"
phx-click={
with_confirm(
JS.push("delete_session_secret", value: %{secret_name: secret_name}),
title: "Delete session secret - #{secret_name}",
description: "Are you sure you want to delete this session secret?",
confirm_text: "Delete",
confirm_icon: "delete-bin-6-line"
)
}
class="hover:text-gray-900"
>
<.remix_icon icon="delete-bin-line" />
</button>
</div>
</div>
</div>
<.link
patch={~p"/sessions/#{@session.id}/secrets"}
class="inline-flex items-center justify-center p-8 py-1 mt-6 space-x-2 text-sm font-medium text-gray-500 border border-gray-400 border-dashed rounded-xl hover:bg-gray-100"
role="button"
>
<.remix_icon icon="add-line" class="text-lg align-center" />
<span>New secret</span>
</.link>
<div class="mt-16">
<h3 class="uppercase text-sm font-semibold text-gray-500">
App secrets
</h3>
<span class="text-sm text-gray-500">
<%= if @saved_secrets == [] do %>
No secrets stored in Livebook so far
<% else %>
Toggle to share with this session
<% end %>
</span>
</div>
<div class="flex flex-col space-y-4 mt-6">
<.secrets_item
:for={secret when secret.origin in [:app, :startup] <- @saved_secrets}
secret={secret}
prefix={to_string(secret.origin)}
data_secrets={@data_view.secrets}
hubs={@hubs}
/>
</div>
<div :if={Livebook.Config.feature_flag_enabled?(:hub)} class="mt-16">
<h3 class="uppercase text-sm font-semibold text-gray-500">
Hub secrets
</h3>
<span class="text-sm text-gray-500">
<%= if @saved_secrets == [] do %>
No secrets stored in Livebook so far
<% else %>
Toggle to share with this session
<% end %>
</span>
</div>
<div class="flex flex-col space-y-4 mt-6">
<.secrets_item
:for={%{origin: {:hub, id}} = secret <- @saved_secrets}
secret={secret}
prefix={"hub-#{id}"}
data_secrets={@data_view.secrets}
hubs={@hubs}
/>
</div>
</div>
</div>
"""
end
defp secrets_item(assigns) do
~H"""
<div
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
id={"#{@prefix}-secret-#{@secret.name}-wrapper"}
>
<div class="flex flex-col text-gray-800">
<div class="flex flex-col">
<div class="flex justify-between items-center">
<span
class="text-sm font-mono w-full break-all flex-row cursor-pointer"
phx-click={
JS.toggle(to: "##{@prefix}-secret-#{@secret.name}-detail", display: "flex")
|> toggle_class("bg-gray-100", to: "##{@prefix}-secret-#{@secret.name}-wrapper")
}
>
<%= @secret.name %>
</span>
<.form
:let={f}
id={"#{@prefix}-secret-#{@secret.name}-toggle"}
for={%{"toggled" => secret_toggled?(@secret, @data_secrets)}}
as={:data}
phx-change="toggle_secret"
>
<.switch_field
field={f[:toggled]}
label={secret_label(@secret, @hubs)}
tooltip={secret_tooltip(@secret, @hubs)}
/>
<.hidden_field field={f[:name]} value={@secret.name} />
<.hidden_field field={f[:value]} value={@secret.value} />
</.form>
</div>
<div
class="flex flex-row justify-between items-center my-1 hidden"
id={"#{@prefix}-secret-#{@secret.name}-detail"}
>
<span class="text-sm font-mono break-all flex-row">
<%= @secret.value %>
</span>
<button
:if={@secret.origin == :app}
id={"#{@prefix}-secret-#{@secret.name}-delete"}
type="button"
phx-click={
with_confirm(
JS.push("delete_app_secret", value: %{secret_name: @secret.name}),
title: "Delete app secret - #{@secret.name}",
description: "Are you sure you want to delete this app secret?",
confirm_text: "Delete",
confirm_icon: "delete-bin-6-line"
)
}
class="hover:text-gray-900"
>
<.remix_icon icon="delete-bin-line" />
</button>
</div>
</div>
</div>
</div>
"""
end
defp secrets_info_icon(assigns) do
~H"""
<span
class="icon-button p-0 cursor-pointer tooltip bottom-left"
data-tooltip={
~S'''
Secrets are a safe way to share credentials
and tokens with notebooks. They are often
accessed by Smart cells and can be read as
environment variables using the LB_ prefix.
'''
}
>
<.remix_icon icon="question-line" class="text-xl leading-none" />
</span>
"""
end
defp runtime_info(assigns) do
~H"""
<div class="flex flex-col grow">
@ -1372,27 +1179,6 @@ defmodule LivebookWeb.SessionLive do
)}
end
def handle_event("toggle_secret", %{"data" => data}, socket) do
if data["toggled"] == "true" do
secret = %{name: data["name"], value: data["value"]}
Livebook.Session.set_secret(socket.assigns.session.pid, secret)
else
Livebook.Session.unset_secret(socket.assigns.session.pid, data["name"])
end
{:noreply, socket}
end
def handle_event("delete_session_secret", %{"secret_name" => secret_name}, socket) do
Livebook.Session.unset_secret(socket.assigns.session.pid, secret_name)
{:noreply, socket}
end
def handle_event("delete_app_secret", %{"secret_name" => secret_name}, socket) do
Livebook.Secrets.unset_secret(secret_name)
{:noreply, socket}
end
@impl true
def handle_info({:operation, operation}, socket) do
{:noreply, handle_operation(socket, operation)}
@ -2253,24 +2039,10 @@ defmodule LivebookWeb.SessionLive do
end)
end
defp secret_toggled?(secret, secrets) do
Map.has_key?(secrets, secret.name) and secrets[secret.name] == secret.value
end
defp get_saved_secrets do
Enum.sort(Hubs.get_secrets() ++ Secrets.get_secrets())
end
defp secret_label(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).emoji
defp secret_label(_, _), do: nil
defp secret_tooltip(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).name
defp secret_tooltip(_, _), do: nil
defp fetch_hub!(id, hubs) do
Enum.find(hubs, &(&1.id == id)) || raise "unknown hub id: #{id}"
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"

View file

@ -0,0 +1,240 @@
defmodule LivebookWeb.SessionLive.SecretsListComponent do
use LivebookWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div class="flex flex-col grow">
<div class="flex justify-between items-center">
<h3 class="uppercase text-sm font-semibold text-gray-500">
Secrets
</h3>
<.secrets_info_icon />
</div>
<span class="text-sm text-gray-500">Available only to this session</span>
<div class="flex flex-col">
<div class="flex flex-col space-y-4 mt-6">
<div
:for={{secret_name, secret_value} <- Enum.sort(@secrets)}
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
id={"session-secret-#{secret_name}-wrapper"}
>
<span
class="text-sm font-mono break-all flex-row cursor-pointer"
phx-click={
JS.toggle(to: "#session-secret-#{secret_name}-detail", display: "flex")
|> toggle_class("bg-gray-100", to: "#session-secret-#{secret_name}-wrapper")
}
>
<%= secret_name %>
</span>
<div
class="flex flex-row justify-between items-center my-1 hidden"
id={"session-secret-#{secret_name}-detail"}
>
<span class="text-sm font-mono break-all flex-row">
<%= secret_value %>
</span>
<button
id={"session-secret-#{secret_name}-delete"}
type="button"
phx-click={
with_confirm(
JS.push("delete_session_secret",
value: %{secret_name: secret_name},
target: @myself
),
title: "Delete session secret - #{secret_name}",
description: "Are you sure you want to delete this session secret?",
confirm_text: "Delete",
confirm_icon: "delete-bin-6-line"
)
}
class="hover:text-gray-900"
>
<.remix_icon icon="delete-bin-line" />
</button>
</div>
</div>
</div>
<.link
patch={~p"/sessions/#{@session.id}/secrets"}
class="inline-flex items-center justify-center p-8 py-1 mt-6 space-x-2 text-sm font-medium text-gray-500 border border-gray-400 border-dashed rounded-xl hover:bg-gray-100"
role="button"
>
<.remix_icon icon="add-line" class="text-lg align-center" />
<span>New secret</span>
</.link>
<div class="mt-16">
<h3 class="uppercase text-sm font-semibold text-gray-500">
App secrets
</h3>
<span class="text-sm text-gray-500">
<%= if @saved_secrets == [] do %>
No secrets stored in Livebook so far
<% else %>
Toggle to share with this session
<% end %>
</span>
</div>
<div class="flex flex-col space-y-4 mt-6">
<.secrets_item
:for={secret when secret.origin in [:app, :startup] <- @saved_secrets}
secret={secret}
prefix={to_string(secret.origin)}
data_secrets={@secrets}
hubs={@hubs}
myself={@myself}
/>
</div>
<div :if={Livebook.Config.feature_flag_enabled?(:hub)} class="mt-16">
<h3 class="uppercase text-sm font-semibold text-gray-500">
Hub secrets
</h3>
<span class="text-sm text-gray-500">
<%= if @saved_secrets == [] do %>
No secrets stored in Livebook so far
<% else %>
Toggle to share with this session
<% end %>
</span>
</div>
<div class="flex flex-col space-y-4 mt-6">
<.secrets_item
:for={%{origin: {:hub, id}} = secret <- @saved_secrets}
secret={secret}
prefix={"hub-#{id}"}
data_secrets={@secrets}
hubs={@hubs}
/>
</div>
</div>
</div>
"""
end
defp secrets_item(assigns) do
~H"""
<div
class="flex flex-col text-gray-500 rounded-lg px-2 pt-1"
id={"#{@prefix}-secret-#{@secret.name}-wrapper"}
>
<div class="flex flex-col text-gray-800">
<div class="flex flex-col">
<div class="flex justify-between items-center">
<span
class="text-sm font-mono w-full break-all flex-row cursor-pointer"
phx-click={
JS.toggle(to: "##{@prefix}-secret-#{@secret.name}-detail", display: "flex")
|> toggle_class("bg-gray-100", to: "##{@prefix}-secret-#{@secret.name}-wrapper")
}
>
<%= @secret.name %>
</span>
<.form
:let={f}
id={"#{@prefix}-secret-#{@secret.name}-toggle"}
for={%{"toggled" => secret_toggled?(@secret, @data_secrets)}}
as={:data}
phx-change="toggle_secret"
phx-target={@myself}
>
<.switch_field
field={f[:toggled]}
label={secret_label(@secret, @hubs)}
tooltip={secret_tooltip(@secret, @hubs)}
/>
<.hidden_field field={f[:name]} value={@secret.name} />
<.hidden_field field={f[:value]} value={@secret.value} />
</.form>
</div>
<div
class="flex flex-row justify-between items-center my-1 hidden"
id={"#{@prefix}-secret-#{@secret.name}-detail"}
>
<span class="text-sm font-mono break-all flex-row">
<%= @secret.value %>
</span>
<button
:if={@secret.origin == :app}
id={"#{@prefix}-secret-#{@secret.name}-delete"}
type="button"
phx-click={
with_confirm(
JS.push("delete_app_secret", value: %{secret_name: @secret.name}, target: @myself),
title: "Delete app secret - #{@secret.name}",
description: "Are you sure you want to delete this app secret?",
confirm_text: "Delete",
confirm_icon: "delete-bin-6-line"
)
}
class="hover:text-gray-900"
>
<.remix_icon icon="delete-bin-line" />
</button>
</div>
</div>
</div>
</div>
"""
end
defp secrets_info_icon(assigns) do
~H"""
<span
class="icon-button p-0 cursor-pointer tooltip bottom-left"
data-tooltip={
~S'''
Secrets are a safe way to share credentials
and tokens with notebooks. They are often
accessed by Smart cells and can be read as
environment variables using the LB_ prefix.
'''
}
>
<.remix_icon icon="question-line" class="text-xl leading-none" />
</span>
"""
end
@impl true
def handle_event("toggle_secret", %{"data" => data}, socket) do
if data["toggled"] == "true" do
secret = %{name: data["name"], value: data["value"]}
Livebook.Session.set_secret(socket.assigns.session.pid, secret)
else
Livebook.Session.unset_secret(socket.assigns.session.pid, data["name"])
end
{:noreply, socket}
end
def handle_event("delete_session_secret", %{"secret_name" => secret_name}, socket) do
Livebook.Session.unset_secret(socket.assigns.session.pid, secret_name)
{:noreply, socket}
end
def handle_event("delete_app_secret", %{"secret_name" => secret_name}, socket) do
Livebook.Secrets.unset_secret(secret_name)
{:noreply, socket}
end
defp secret_toggled?(secret, secrets) do
Map.has_key?(secrets, secret.name) and secrets[secret.name] == secret.value
end
defp secret_label(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).emoji
defp secret_label(_, _), do: nil
defp secret_tooltip(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).name
defp secret_tooltip(_, _), do: nil
defp fetch_hub!(id, hubs) do
Enum.find(hubs, &(&1.id == id)) || raise "unknown hub id: #{id}"
end
end