defmodule LivebookWeb.Hub.Edit.TeamComponent do
use LivebookWeb, :live_component
alias Livebook.Hubs
alias Livebook.Hubs.Provider
alias Livebook.Teams
alias LivebookWeb.LayoutComponents
alias LivebookWeb.NotFoundError
@impl true
def update(assigns, socket) do
socket = assign(socket, assigns)
changeset = Teams.change_hub(assigns.hub)
show_key = assigns.params["show-key"]
secrets = Hubs.get_secrets(assigns.hub)
file_systems = Hubs.get_file_systems(assigns.hub, hub_only: true)
deployment_groups = Teams.get_deployment_groups(assigns.hub)
app_deployments = Teams.get_app_deployments(assigns.hub)
agents = Teams.get_agents(assigns.hub)
environment_variables = Teams.get_environment_variables(assigns.hub)
secret_name = assigns.params["secret_name"]
file_system_id = assigns.params["file_system_id"]
default? = default_hub?(assigns.hub)
secret_value =
if assigns.live_action == :edit_secret do
Enum.find_value(secrets, &(&1.name == secret_name and &1.value)) ||
raise(NotFoundError, "could not find secret matching #{inspect(secret_name)}")
end
file_system =
if assigns.live_action == :edit_file_system do
Enum.find_value(file_systems, &(&1.id == file_system_id && &1)) ||
raise(NotFoundError, "could not find file system matching #{inspect(file_system_id)}")
end
{:ok,
socket
|> assign(
secrets: secrets,
file_system: file_system,
file_system_id: file_system_id,
file_systems: file_systems,
deployment_groups: Enum.sort_by(deployment_groups, & &1.name),
app_deployments: Enum.frequencies_by(app_deployments, & &1.deployment_group_id),
agents: Enum.frequencies_by(agents, & &1.deployment_group_id),
environment_variables:
Enum.frequencies_by(environment_variables, & &1.deployment_group_id),
show_key: show_key,
secret_name: secret_name,
secret_value: secret_value,
hub_metadata: Provider.to_metadata(assigns.hub),
default?: default?
)
|> assign_form(changeset)}
end
@impl true
def render(assigns) do
~H"""
{Provider.connection_status(@hub)}
Your organization has
{Date.diff(@hub.billing_status.trial_ends_at, Date.utc_today())} day(s) left
on the free trial. Need help getting set up? Contact us.
Workspace disabled: your organization doesn't have an active subscription. Please contact your <.link
href={org_url(@hub, "/users")}
class="underline"
>org's admin.
General
<.form
id={@id}
class="flex flex-col md:flex-row mt-4 space-y-4 md:space-x-2 md:space-y-0"
for={@form}
phx-submit="save"
phx-change="validate"
phx-target={@myself}
>
<.text_field
field={@form[:hub_name]}
label="Name"
disabled
help="Name cannot be changed"
class="bg-gray-200/50 border-200/80 cursor-not-allowed"
/>
<.emoji_field field={@form[:hub_emoji]} label="Emoji" />
<.button type="submit" phx-disable-with="Updating...">
Save
Secrets
Secrets are a safe way to allow notebooks to access
credentials and tokens.
<.live_component
module={LivebookWeb.Hub.SecretListComponent}
id="hub-secrets-list"
hub={@hub}
secrets={@secrets}
edit_path={"hub/#{@hub.id}/secrets/edit"}
return_to={~p"/hub/#{@hub.id}"}
disabled={@hub.billing_status.disabled}
/>
<.button
patch={~p"/hub/#{@hub.id}/secrets/new"}
id="add-secret"
disabled={@hub.billing_status.disabled}
>
Add secret
File storages
File storages are used to store notebooks and their files across your whole team.
<.live_component
module={LivebookWeb.Hub.FileSystemListComponent}
id="hub-file-systems-list"
hub_id={@hub.id}
file_systems={@file_systems}
target={@myself}
disabled={@hub.billing_status.disabled}
/>
Deployment groups
Deployment groups allow you to deploy Livebook apps to self-hosted machines with the click of a button.
<.no_entries :if={@deployment_groups == []}>
No deployment groups here... yet!
<.live_component
module={LivebookWeb.Hub.Teams.DeploymentGroupComponent}
id={"hub-deployment-group-#{deployment_group.id}"}
hub={@hub}
deployment_group={deployment_group}
app_deployments_count={Map.get(@app_deployments, deployment_group.id, 0)}
environment_variables_count={
Map.get(@environment_variables, deployment_group.id, 0)
}
agents_count={Map.get(@agents, deployment_group.id, 0)}
live_action={@live_action}
params={@params}
/>
<.button
patch={~p"/hub/#{@hub.id}/groups/new"}
id="add-deployment-group"
disabled={@hub.billing_status.disabled}
>
Add deployment group
Danger zone
Delete this workspace
This only removes the workpsace from this machine. You must rejoin to access its features once again.
<.button
color="red"
outlined
id="delete-hub"
phx-click={JS.push("delete_hub", value: %{id: @hub.id})}
>
Delete workspace
<.remix_icon icon="delete-bin-line" class="text-lg sm:hidden" />
<.modal :if={@show_key} id="key-modal" show width="medium" patch={~p"/hub/#{@hub.id}"}>
<.teams_key_modal
teams_key={@hub.teams_key}
confirm_url={if @show_key == "confirm", do: ~p"/hub/#{@hub.id}"}
/>
<.modal
:if={@live_action in [:new_secret, :edit_secret]}
id="secrets-modal"
show
width="medium"
patch={~p"/hub/#{@hub.id}"}
>
<.live_component
module={LivebookWeb.Hub.SecretFormComponent}
id="secrets"
hub={@hub}
secret_name={@secret_name}
secret_value={@secret_value}
return_to={~p"/hub/#{@hub.id}"}
disabled={@hub.billing_status.disabled}
/>
<.modal
:if={@live_action in [:new_file_system, :edit_file_system]}
id="file-systems-modal"
show
width="medium"
patch={~p"/hub/#{@hub.id}"}
>
<.live_component
module={LivebookWeb.Hub.FileSystemFormComponent}
id="file-systems"
hub={@hub}
disabled={@hub.billing_status.disabled}
file_system={@file_system}
file_system_id={@file_system_id}
return_to={~p"/hub/#{@hub.id}"}
/>
<.modal
:if={@live_action == :new_deployment_group}
id="deployment-group-modal"
show
width="big"
patch={~p"/hub/#{@hub.id}"}
>
<.live_component
module={LivebookWeb.Hub.Teams.DeploymentGroupFormComponent}
id="deployment-group"
hub={@hub}
return_to={~p"/hub/#{@hub.id}"}
/>
"""
end
defp org_url(hub, path) do
Livebook.Config.teams_url() <> "/orgs/#{hub.org_id}" <> path
end
defp teams_key_modal(assigns) do
~H"""
Teams key
This is your Teams key. This key encrypts your
data before it is sent to Livebook Teams servers. This key is
required for you and invited users to join this organization.
We recommend storing it somewhere safe:
<.password_field id="teams-key" name="teams_key" value={@teams_key} readonly />
JS.transition("tooltip left", time: 2000)
}
>
<.button color="gray" small type="button">
<.remix_icon icon="clipboard-line" class="text-xl leading-none py-1" />
<.button :if={@confirm_url} patch={@confirm_url}>
<.remix_icon class="mr-1" icon="thumb-up-fill" /> I've saved my Teams key in a secure location
"""
end
@impl true
def handle_event("save", %{"team" => params}, socket) do
case Teams.update_hub(socket.assigns.hub, params) do
{:ok, hub} ->
{:noreply,
socket
|> put_flash(:success, "Workspace updated successfully")
|> push_patch(to: ~p"/hub/#{hub.id}")}
{:error, changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
def handle_event("validate", %{"team" => attrs}, socket) do
changeset =
socket.assigns.hub
|> Teams.change_hub(attrs)
|> Map.replace!(:action, :validate)
{:noreply, assign_form(socket, changeset)}
end
def handle_event("mark_as_default", _, socket) do
Hubs.set_default_hub(socket.assigns.hub.id)
{:noreply, assign(socket, default?: true)}
end
def handle_event("remove_as_default", _, socket) do
Hubs.unset_default_hub()
{:noreply, assign(socket, default?: false)}
end
defp default_hub?(hub) do
Hubs.get_default_hub().id == hub.id
end
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
assign(socket, form: to_form(changeset))
end
end