Packages
livebook
0.19.0
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/hub/file_system_form_component.ex
defmodule LivebookWeb.Hub.FileSystemFormComponent do
use LivebookWeb, :live_component
alias Livebook.FileSystem
alias Livebook.FileSystems
@impl true
def mount(socket) do
{:ok, assign(socket, error_message: nil)}
end
@impl true
def update(assigns, socket) do
{file_system, assigns} = Map.pop!(assigns, :file_system)
mode = mode(file_system)
title = title(file_system)
button = button_attrs(file_system)
file_system = file_system || %FileSystem.S3{hub_id: assigns.hub.id}
changeset = FileSystems.change_file_system(file_system)
{:ok,
socket
|> assign_new(:hub, fn -> assigns.hub end)
|> assign_new(:file_system, fn -> file_system end)
|> assign_new(:type, fn -> FileSystems.type(file_system) end)
|> assign_new(:mode, fn -> mode end)
|> assign_new(:title, fn -> title end)
|> assign_new(:button, fn -> button end)
|> assign_new(:return_to, fn -> assigns.return_to end)
|> assign_new(:disabled, fn -> assigns.disabled end)
|> assign_form(changeset)}
end
@impl true
def render(assigns) do
~H"""
<div class="flex flex-col space-y-5">
<h3 class="text-2xl font-semibold text-gray-800">
{@title}
</h3>
<div :if={@error_message} class="error-box">
{@error_message}
</div>
<div>
<label class="mb-2 flex items-center gap-1 text-sm text-gray-800 font-medium">
Type
</label>
<div class="flex gap-y-6 sm:gap-x-4">
<.radio_card_input
id="file_system_type-s3"
name="file_system[type]"
title="S3"
phx-click={JS.push("select_type", value: %{value: :s3})}
phx-target={@myself}
value={@type}
checked_value={:s3}
disabled={@mode == :edit}
>
Configure an AWS S3 bucket as a Livebook file storage. Many storage services offer an S3-compatible API and those work as well.
</.radio_card_input>
<.radio_card_input
id="file_system_type-git"
name="file_system[type]"
title="Git"
phx-click={JS.push("select_type", value: %{value: :git})}
phx-target={@myself}
value={@type}
checked_value={:git}
disabled={@mode == :edit}
>
Configure a Git repository as a read-only Livebook file storage. You will need a valid SSH key to access your Git repository.
</.radio_card_input>
</div>
</div>
<.form
id="file-systems-form"
for={@form}
phx-target={@myself}
phx-submit="save"
phx-change="validate"
autocomplete="off"
spellcheck="false"
>
<div class="flex flex-col space-y-4">
<.file_system_form_fields {assigns} />
<div class="flex space-x-2">
<.button
type="submit"
disabled={@disabled or not @form.source.valid?}
>
<.spinner class="hidden phx-submit-loading:block mr-1" />
<.remix_icon icon={@button.icon} />
<span class="font-normal">{@button.label}</span>
</.button>
<.button color="gray" outlined patch={@return_to}>
Cancel
</.button>
</div>
</div>
</.form>
</div>
"""
end
defp file_system_form_fields(%{file_system: %FileSystem.S3{}} = assigns) do
~H"""
<div class="flex flex-col space-y-4">
<.text_field
field={@form[:bucket_url]}
label="Bucket URL"
placeholder="https://s3.[region].amazonaws.com/[bucket]"
/>
<.text_field
field={@form[:region]}
label="Region (optional)"
placeholder={FileSystem.S3.region_from_url(@form[:bucket_url].value || "")}
/>
<%= if Livebook.Config.aws_credentials?() do %>
<.password_field field={@form[:access_key_id]} label="Access Key ID (optional)" />
<.password_field field={@form[:secret_access_key]} label="Secret Access Key (optional)" />
<p class="text-xs text-gray-700">
You may leave Access Key fields empty. In such cases,
they will be automatically read from your environment variables,
AWS credentials, or Amazon EC2/ECS metadata.
</p>
<% else %>
<.password_field field={@form[:access_key_id]} label="Access Key ID" />
<.password_field field={@form[:secret_access_key]} label="Secret Access Key" />
<p class="text-xs text-gray-700">
Start Livebook with <code>LIVEBOOK_AWS_CREDENTIALS</code> environment
variable set if you want to automatically read credentials from
environment variables, AWS credentials, or Amazon EC2/ECS metadata.
</p>
<% end %>
</div>
"""
end
defp file_system_form_fields(%{file_system: %FileSystem.Git{}} = assigns) do
~H"""
<div class="flex flex-col space-y-4">
<.text_field
field={@form[:repo_url]}
label="Repository URL"
placeholder="git@[provider_host]:[owner]/[repo_name].git"
/>
<.text_field
field={@form[:branch]}
label="Branch"
placeholder="main"
/>
<.password_field field={@form[:key]} label="SSH Private Key" />
<p class="text-xs text-gray-700">
An SSH private key with repository access. You may use a personal key or a deploy key.
See your Git provider’s documentation to create a deploy key.
</p>
</div>
"""
end
@impl true
def handle_event("select_type", %{"value" => type}, socket) do
if to_string(socket.assigns.type) == to_string(type) do
{:noreply, socket}
else
file_system = FileSystems.load(type, %{"hub_id" => socket.assigns.hub.id})
changeset = FileSystems.change_file_system(file_system)
{:noreply,
socket
|> assign(type: type, file_system: file_system)
|> assign_form(changeset)}
end
end
def handle_event("validate", %{"file_system" => attrs}, socket) do
changeset = FileSystems.change_file_system(socket.assigns.file_system, attrs)
{:noreply, assign_form(socket, changeset)}
end
def handle_event("save", %{"file_system" => attrs}, socket) do
changeset = prepare_to_save(socket.assigns.file_system, attrs)
with {:ok, file_system} <- Ecto.Changeset.apply_action(changeset, :update),
:ok <- check_file_system_connectivity(file_system, socket),
:ok <- save_file_system(file_system, changeset, socket) do
message =
case socket.assigns.mode do
:new -> "File storage added successfully"
:edit -> "File storage updated successfully"
end
{:noreply,
socket
|> put_flash(:success, message)
|> push_patch(to: socket.assigns.return_to)}
else
{:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign_form(socket, changeset)}
{:transport_error, message} -> {:noreply, assign(socket, error_message: message)}
{:error, message} -> {:noreply, assign(socket, error_message: message)}
end
end
defp check_file_system_connectivity(file_system, socket) do
default_path = FileSystem.default_path(file_system)
with :ok <- FileSystem.mount(file_system),
{:ok, _} <- FileSystem.list(file_system, default_path, false) do
if socket.assigns.mode == :new do
FileSystem.unmount(file_system)
end
:ok
else
{:error, message} -> {:error, "Connection test failed: " <> message}
end
end
defp save_file_system(file_system, changeset, socket) do
result =
case socket.assigns.mode do
:new -> Livebook.Hubs.create_file_system(socket.assigns.hub, file_system)
:edit -> Livebook.Hubs.update_file_system(socket.assigns.hub, file_system)
end
with {:error, errors} <- result do
{:error,
changeset
|> Livebook.Utils.put_changeset_errors(errors)
|> Map.replace!(:action, :validate)}
end
end
defp prepare_to_save(file_system, attrs) do
changeset = FileSystems.change_file_system(file_system, attrs)
if FileSystems.type(file_system) == "s3" do
FileSystem.S3.maybe_put_region_from_url(changeset)
else
changeset
end
end
defp assign_form(socket, changeset) do
changeset = Map.replace!(changeset, :action, :validate)
assign(socket, :form, to_form(changeset, as: :file_system))
end
defp mode(nil), do: :new
defp mode(_), do: :edit
defp title(nil), do: "Add file storage"
defp title(_), do: "Edit file storage"
defp button_attrs(nil), do: %{icon: "add-line", label: "Add"}
defp button_attrs(_), do: %{icon: "save-line", label: "Save"}
end