Packages
livebook
0.14.0-rc.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, changeset: nil, error_message: nil)}
end
@impl true
def update(assigns, socket) do
{file_system, assigns} = Map.pop!(assigns, :file_system)
mode = mode(file_system)
button = button_attrs(file_system)
title = title(file_system)
file_system = file_system || %FileSystem.S3{hub_id: assigns.hub.id}
changeset = socket.assigns.changeset || FileSystems.change_file_system(file_system)
{:ok,
socket
|> assign(assigns)
|> assign(
file_system: file_system,
changeset: changeset,
mode: mode,
title: title,
button: button
)}
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>
<p class="text-gray-700">
Configure an AWS S3 bucket as a Livebook file storage.
Many storage services offer an S3-compatible API and
those work as well.
</p>
<div :if={@error_message} class="error-box">
<%= @error_message %>
</div>
<.form
:let={f}
id="file-systems-form"
for={to_form(@changeset, as: :file_system)}
phx-target={@myself}
phx-submit="save"
phx-change="validate"
autocomplete="off"
spellcheck="false"
>
<div class="flex flex-col space-y-4">
<.text_field
field={f[:bucket_url]}
label="Bucket URL"
placeholder="https://s3.[region].amazonaws.com/[bucket]"
/>
<.text_field
field={f[:region]}
label="Region (optional)"
placeholder={
FileSystem.S3.region_from_url(Ecto.Changeset.get_field(@changeset, :bucket_url) || "")
}
/>
<%= if Livebook.Config.aws_credentials?() do %>
<.password_field field={f[:access_key_id]} label="Access Key ID (optional)" />
<.password_field field={f[: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={f[:access_key_id]} label="Access Key ID" />
<.password_field field={f[: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 class="flex space-x-2">
<.button type="submit" disabled={not @changeset.valid?}>
<.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
@impl true
def handle_event("validate", %{"file_system" => attrs}, socket) do
changeset =
socket.assigns.file_system
|> FileSystems.change_file_system(attrs)
|> Map.replace!(:action, :validate)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("save", %{"file_system" => attrs}, socket) do
changeset =
socket.assigns.file_system
|> FileSystems.change_file_system(attrs)
|> FileSystem.S3.maybe_put_region_from_url()
with {:ok, file_system} <- Ecto.Changeset.apply_action(changeset, :update),
:ok <- check_file_system_connectivity(file_system),
: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(socket, changeset: Map.replace!(changeset, :action, :validate))}
{: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) do
default_path = FileSystem.default_path(file_system)
case FileSystem.list(file_system, default_path, false) do
{:ok, _} -> :ok
{: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 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