Packages

phoenix_kit

1.7.151
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules storage web bucket_form.html.heex
Raw

lib/modules/storage/web/bucket_form.html.heex

<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title}
current_path={@current_path}
current_locale={@current_locale}
project_title={@project_title}
>
<div class="container mx-auto px-4 py-8 max-w-3xl">
<.admin_page_header
back={PhoenixKit.Utils.Routes.path("/admin/settings/media")}
title={@page_title}
subtitle={gettext("Configure storage provider settings")}
/>
<.form for={@changeset} phx-change="validate" phx-submit="save" class="space-y-6">
<%!-- Basic Information --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body gap-4">
<h2 class="card-title text-lg">
<.icon name="hero-server" class="w-5 h-5" /> {gettext("Basic Information")}
</h2>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">{gettext("Bucket Name")} *</legend>
<input
type="text"
name="bucket[name]"
value={Ecto.Changeset.get_field(@changeset, :name)}
class={"input input-bordered w-full #{input_class(@changeset, :name)}"}
placeholder={gettext("e.g., Production Media, Backup Storage")}
required
/>
<p class="text-xs text-base-content/50 mt-1">
{gettext("A friendly name to identify this storage location")}
</p>
</fieldset>
<%= if @mode == :new do %>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">
{gettext("Storage Provider")} *
</legend>
<label class={"select w-full #{input_class(@changeset, :provider)}"}>
<select name="bucket[provider]" required>
<option value="">{gettext("Select provider...")}</option>
<option
value="local"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "local"}
>
{gettext("Local Filesystem")}
</option>
<option
value="s3"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "s3"}
>
AWS S3
</option>
<option
value="b2"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "b2"}
>
Backblaze B2
</option>
<option
value="r2"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "r2"}
>
Cloudflare R2
</option>
<option
value="tigris"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "tigris"}
>
Tigris
</option>
</select>
</label>
</fieldset>
<% else %>
<input type="hidden" name="bucket[provider]" value={@current_provider} />
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">{gettext("Storage Provider")}</legend>
<div class="flex items-center gap-2">
<span class="badge badge-lg badge-outline">
<%= case @current_provider do %>
<% "local" -> %>
{gettext("Local Filesystem")}
<% "s3" -> %>
AWS S3
<% "b2" -> %>
Backblaze B2
<% "r2" -> %>
Cloudflare R2
<% "tigris" -> %>
Tigris
<% _ -> %>
{String.upcase(@current_provider || "Unknown")}
<% end %>
</span>
<span class="text-xs text-base-content/40">{gettext("Cannot be changed")}</span>
</div>
</fieldset>
<% end %>
</div>
</div>
<%!-- Local Provider Settings --%>
<%= if @current_provider == "local" do %>
<div class="card bg-base-100 shadow-sm">
<div class="card-body gap-4">
<h2 class="card-title text-lg">
<.icon name="hero-folder" class="w-5 h-5" /> {gettext("Local Storage")}
</h2>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">{gettext("Storage Path")} *</legend>
<input
type="text"
name="bucket[endpoint]"
value={Ecto.Changeset.get_field(@changeset, :endpoint)}
class={"input input-bordered w-full #{input_class(@changeset, :endpoint)}"}
placeholder="/path/to/storage"
required
/>
<p class="text-xs text-base-content/50 mt-1">
{gettext("Absolute path where files will be stored on disk")}
</p>
</fieldset>
</div>
</div>
<% end %>
<%!-- Cloud Provider Settings --%>
<%= if @current_provider in ["s3", "b2", "r2", "tigris"] do %>
<div class="card bg-base-100 shadow-sm">
<div class="p-8 space-y-4">
<h2 class="card-title text-lg">
<.icon name="hero-cloud" class="w-5 h-5" /> {gettext("Provider Configuration")}
</h2>
<%= if @current_provider == "s3" do %>
<.live_component
module={PhoenixKitWeb.Live.Components.SearchableSelect}
id="bucket-region-select"
name="bucket[region]"
value={Ecto.Changeset.get_field(@changeset, :region)}
label={"#{gettext("AWS Region")} *"}
placeholder="Search regions..."
required={true}
grouped_options={
Enum.map(AwsRegions.group_by_continent(), fn {continent, regions} ->
{continent, Enum.map(regions, fn r -> {"#{r.name}#{r.code}", r.code} end)}
end)
}
/>
<% end %>
<%= if @current_provider == "b2" do %>
<.live_component
module={PhoenixKitWeb.Live.Components.SearchableSelect}
id="bucket-b2-region-select"
name="bucket[region]"
value={Ecto.Changeset.get_field(@changeset, :region)}
label={"#{gettext("Backblaze Region")} *"}
placeholder="Search Backblaze regions..."
required={true}
grouped_options={
Enum.map(BackblazeRegions.group_by_continent(), fn {continent, regions} ->
{continent, Enum.map(regions, fn r -> {"#{r.name}#{r.code}", r.code} end)}
end)
}
/>
<% end %>
<%= if @current_provider == "tigris" do %>
<.live_component
module={PhoenixKitWeb.Live.Components.SearchableSelect}
id="bucket-tigris-region-select"
name="bucket[region]"
value={Ecto.Changeset.get_field(@changeset, :region)}
label={"#{gettext("Tigris Region")} *"}
placeholder="Search Tigris regions..."
required={true}
grouped_options={
Enum.map(Enum.sort(TigrisRegions.group_by_continent()), fn {continent, regions} ->
{continent, Enum.map(regions, fn r -> {"#{r.name}#{r.code}", r.code} end)}
end)
}
/>
<% end %>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">
{case @current_provider do
"s3" -> gettext("AWS Bucket Name")
"b2" -> gettext("Backblaze Bucket Name")
"r2" -> gettext("Cloudflare Bucket Name")
"tigris" -> gettext("Tigris Bucket Name")
_ -> gettext("Bucket Name")
end} *
</legend>
<input
type="text"
name="bucket[bucket_name]"
value={Ecto.Changeset.get_field(@changeset, :bucket_name)}
class={"input input-bordered w-full #{input_class(@changeset, :bucket_name)}"}
placeholder="e.g., my-app-files"
required={@current_provider in ["b2", "r2", "tigris"]}
/>
</fieldset>
<%= if @current_provider in ["b2", "r2", "tigris"] do %>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">
{case @current_provider do
"b2" -> gettext("Backblaze Endpoint")
"r2" -> gettext("Cloudflare Endpoint")
"tigris" -> gettext("Tigris Endpoint")
_ -> gettext("Endpoint")
end} *
</legend>
<input
type="text"
name="bucket[endpoint]"
value={Ecto.Changeset.get_field(@changeset, :endpoint)}
class={"input input-bordered w-full #{input_class(@changeset, :endpoint)}"}
placeholder={
case @current_provider do
"b2" -> "e.g., s3.us-west-002.backblazeb2.com"
"r2" -> "e.g., {account_id}.r2.cloudflarestorage.com"
"tigris" -> "e.g., fly.storage.tigris.dev"
_ -> "e.g., endpoint.example.com"
end
}
required={@current_provider in ["b2", "r2", "tigris"]}
/>
</fieldset>
<% end %>
</div>
</div>
<%!-- Access Credentials --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body gap-4">
<h2 class="card-title text-lg">
<.icon name="hero-key" class="w-5 h-5" /> {gettext("Access Credentials")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">
{gettext("Access Key ID")} *
</legend>
<input
type="text"
name="bucket[access_key_id]"
value={Ecto.Changeset.get_field(@changeset, :access_key_id)}
class={"input input-bordered w-full #{input_class(@changeset, :access_key_id)}"}
placeholder="AKIA..."
required={@current_provider in ["b2", "r2", "tigris"]}
/>
</fieldset>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">
{gettext("Secret Access Key")} *
</legend>
<input
type="password"
name="bucket[secret_access_key]"
value={Ecto.Changeset.get_field(@changeset, :secret_access_key)}
class={"input input-bordered w-full #{input_class(@changeset, :secret_access_key)}"}
placeholder="•••••••••••••••••••••••••••••••••"
required={@current_provider in ["b2", "r2", "tigris"]}
/>
</fieldset>
</div>
<%!-- Test Connection --%>
<div class="pt-2">
<button
type="button"
phx-click="test_connection"
class={"btn btn-outline btn-sm #{if @connection_status == :success, do: "btn-success", else: ""}"}
disabled={@testing_connection}
>
<%= if @testing_connection do %>
<span class="loading loading-spinner loading-xs"></span>
{gettext("Testing...")}
<% else %>
<.icon name="hero-signal" class="w-4 h-4" />
{gettext("Test Connection")}
<% end %>
</button>
<%= if @connection_status == :success do %>
<span class="text-sm text-success ml-2">
<.icon name="hero-check-circle" class="w-4 h-4 inline" />
{gettext("Connection successful")}
</span>
<% end %>
<%= if @connection_status == :failed do %>
<div class="mt-2 text-sm text-error">
<.icon name="hero-x-circle" class="w-4 h-4 inline" />
{gettext("Connection failed")}
<%= if @connection_error do %>
: {@connection_error}
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<%!-- Settings --%>
<div class="card bg-base-100 shadow-sm">
<div class="card-body gap-4">
<h2 class="card-title text-lg">
<.icon name="hero-cog-6-tooth" class="w-5 h-5" /> {gettext("Settings")}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<%= if @current_provider in ["s3", "b2", "r2", "tigris"] do %>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">{gettext("Access Type")}</legend>
<label class="select w-full">
<select name="bucket[access_type]">
<option
value="public"
selected={Ecto.Changeset.get_field(@changeset, :access_type) == "public"}
>
{gettext("Public (direct URL)")}
</option>
<option
value="private"
selected={Ecto.Changeset.get_field(@changeset, :access_type) == "private"}
>
{gettext("Private (proxied)")}
</option>
</select>
</label>
</fieldset>
<% end %>
<fieldset class="fieldset">
<legend class="fieldset-legend font-semibold">{gettext("Priority")}</legend>
<input
type="number"
name="bucket[priority]"
value={Ecto.Changeset.get_field(@changeset, :priority)}
class={"input input-bordered w-full #{input_class(@changeset, :priority)}"}
placeholder="0"
min="0"
/>
<p class="text-xs text-base-content/50 mt-1">
{gettext("0 = auto, 1+ = higher priority")}
</p>
</fieldset>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="hidden" name="bucket[enabled]" value="false" />
<input
type="checkbox"
name="bucket[enabled]"
value="true"
checked={Ecto.Changeset.get_field(@changeset, :enabled)}
class="checkbox checkbox-primary"
/>
<div>
<span class="label-text font-semibold">{gettext("Enable bucket")}</span>
<p class="text-xs text-base-content/50">
{gettext("Only enabled buckets receive new uploads")}
</p>
</div>
</label>
</div>
</div>
</div>
<%!-- Actions --%>
<div class="flex justify-end gap-3">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media")}
class="btn btn-ghost"
>
{gettext("Cancel")}
</.link>
<button
type="submit"
class="btn btn-primary"
disabled={
!@changeset.valid? ||
(@current_provider in ["s3", "b2", "r2", "tigris"] && @connection_status != :success)
}
>
<.icon name="hero-check" class="w-4 h-4" /> {gettext("Save Bucket")}
</button>
</div>
</.form>
</div>
<%!-- Create Path Confirmation Modal --%>
<%= if @show_create_path_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg">{gettext("Create Storage Path")}</h3>
<p class="py-4">
{gettext("The storage path")}
<code class="badge badge-ghost">{@missing_path}</code> {gettext("does not exist.")}
</p>
<p class="mb-6">{gettext("Would you like to create it now?")}</p>
<div class="modal-action">
<button phx-click="cancel_create_path" class="btn btn-ghost">
{gettext("Cancel")}
</button>
<button
phx-click="confirm_create_path"
phx-value-path={@missing_path}
class="btn btn-primary"
>
<.icon name="hero-plus" class="w-4 h-4" /> {gettext("Create Path")}
</button>
</div>
</div>
</div>
<% end %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>