Packages
phoenix_kit
1.7.78
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
Current section
Files
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-4xl">
<.admin_page_header back={PhoenixKit.Utils.Routes.path("/admin/settings/media")}>
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">{@page_title}</h1>
<p class="text-sm text-base-content/60 mt-0.5">
Configure storage provider settings and access credentials
</p>
</.admin_page_header>
<%!-- Form Section --%>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<.form for={@changeset} phx-change="validate" phx-submit="save" class="space-y-6">
<%!-- Basic Information --%>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Bucket Name *</span>
<span class="label-text-alt">Display name for this storage location</span>
</label>
<input
type="text"
name="bucket[name]"
value={Ecto.Changeset.get_field(@changeset, :name)}
class={"input input-bordered #{input_class(@changeset, :name)}"}
placeholder="e.g., Production S3, Local SSD, Backup B2"
required
/>
</div>
<%= if @mode == :new do %>
<%!-- Provider Selection (only in new mode) --%>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Storage Provider *</span>
<span class="label-text-alt">Type of storage service</span>
</label>
<select
name="bucket[provider]"
class={"select select-bordered #{input_class(@changeset, :provider)}"}
required
>
<option value="">Select provider...</option>
<option
value="local"
selected={Ecto.Changeset.get_field(@changeset, :provider) == "local"}
>
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>
</select>
</div>
<% else %>
<%!-- Provider Display (read-only in edit mode) --%>
<input type="hidden" name="bucket[provider]" value={@current_provider} />
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Storage Provider</span>
<span class="label-text-alt">Cannot be changed after creation</span>
</label>
<div class="p-3 bg-base-200 rounded-lg">
<span class="badge badge-lg badge-ghost">
<%= case @current_provider do %>
<% "local" -> %>
Local Filesystem
<% "s3" -> %>
AWS S3
<% "b2" -> %>
Backblaze B2
<% "r2" -> %>
Cloudflare R2
<% _ -> %>
{String.upcase(@current_provider || "Unknown")}
<% end %>
</span>
</div>
</div>
<% end %>
<%!-- Local Provider Settings --%>
<%= if @current_provider == "local" do %>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Storage Path *</span>
<span class="label-text-alt">Directory path for local file storage</span>
</label>
<input
type="text"
name="bucket[endpoint]"
value={Ecto.Changeset.get_field(@changeset, :endpoint)}
class={"input input-bordered #{input_class(@changeset, :endpoint)}"}
placeholder="/path/to/storage"
required
/>
<label class="label">
<span class="label-text-alt">
Absolute path to directory where files will be stored
</span>
</label>
</div>
<% end %>
<%!-- Cloud Provider Settings --%>
<%= if @current_provider in ["s3", "b2", "r2"] do %>
<div class="divider">Cloud Provider Configuration</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Region</span>
<span class="label-text-alt">AWS region or equivalent</span>
</label>
<input
type="text"
name="bucket[region]"
value={Ecto.Changeset.get_field(@changeset, :region)}
class={"input input-bordered #{input_class(@changeset, :region)}"}
placeholder="e.g., us-east-1"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Bucket Name *</span>
<span class="label-text-alt">Cloud storage bucket name</span>
</label>
<input
type="text"
name="bucket[bucket_name]"
value={Ecto.Changeset.get_field(@changeset, :bucket_name)}
class={"input input-bordered #{input_class(@changeset, :bucket_name)}"}
placeholder="e.g., my-app-files"
required={@current_provider in ["b2", "r2"]}
/>
</div>
</div>
<%= if @current_provider in ["b2", "r2"] do %>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Endpoint *</span>
<span class="label-text-alt">S3-compatible endpoint URL</span>
</label>
<input
type="text"
name="bucket[endpoint]"
value={Ecto.Changeset.get_field(@changeset, :endpoint)}
class={"input input-bordered #{input_class(@changeset, :endpoint)}"}
placeholder="e.g., s3.us-west-002.backblazeb2.com"
required={@current_provider in ["b2", "r2"]}
/>
</div>
<% end %>
<%!-- Access Credentials --%>
<div class="divider">Access Credentials</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Access Key ID *</span>
<span class="label-text-alt">AWS access key or API key</span>
</label>
<input
type="text"
name="bucket[access_key_id]"
value={Ecto.Changeset.get_field(@changeset, :access_key_id)}
class={"input input-bordered #{input_class(@changeset, :access_key_id)}"}
placeholder="AKIA..."
required={@current_provider in ["b2", "r2"]}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Secret Access Key *</span>
<span class="label-text-alt">AWS secret key or API secret</span>
</label>
<input
type="password"
name="bucket[secret_access_key]"
value={Ecto.Changeset.get_field(@changeset, :secret_access_key)}
class={"input input-bordered #{input_class(@changeset, :secret_access_key)}"}
placeholder="•••••••••••••••••••••••••••••••••"
required={@current_provider in ["b2", "r2"]}
/>
</div>
</div>
<% end %>
<%!-- Additional Settings (shown for all providers) --%>
<div class="divider">Additional Settings</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<%= if @current_provider in ["s3", "b2", "r2"] do %>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">CDN URL</span>
<span class="label-text-alt">CDN endpoint for file serving</span>
</label>
<input
type="url"
name="bucket[cdn_url]"
value={Ecto.Changeset.get_field(@changeset, :cdn_url)}
class={"input input-bordered #{input_class(@changeset, :cdn_url)}"}
placeholder="https://cdn.example.com"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Access Type</span>
<span class="label-text-alt">How files are served</span>
</label>
<select
name="bucket[access_type]"
class="select select-bordered w-full"
>
<option
value="public"
selected={Ecto.Changeset.get_field(@changeset, :access_type) == "public"}
>
Public (redirect to bucket URL)
</option>
<option
value="private"
selected={Ecto.Changeset.get_field(@changeset, :access_type) == "private"}
>
Private (proxy through server)
</option>
</select>
<label class="label">
<span class="label-text-alt text-base-content/60">
Public: faster, uses CDN. Private: slower, works with ACL-protected buckets.
</span>
</label>
</div>
<% end %>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Priority</span>
<span class="label-text-alt">Selection priority</span>
</label>
<input
type="number"
name="bucket[priority]"
value={Ecto.Changeset.get_field(@changeset, :priority)}
class={"input input-bordered #{input_class(@changeset, :priority)}"}
placeholder="0"
min="0"
/>
<label class="label">
<span class="label-text-alt">0 = random/emptiest, 1+ = specific priority</span>
</label>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold">Status</span>
<span class="label-text-alt">Enable/disable bucket</span>
</label>
<div class="flex items-center gap-2">
<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"
/>
<span class="label-text">Enable this bucket</span>
</div>
</div>
</div>
<%!-- Form Actions --%>
<div class="flex justify-end gap-2 pt-4">
<button type="submit" class="btn btn-primary" disabled={!@changeset.valid?}>
<.icon name="hero-check" class="w-4 h-4 mr-1" /> Apply
</button>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/settings/media")}
class="btn btn-outline"
>
Cancel
</.link>
</div>
</.form>
</div>
</div>
<%!-- Help Section --%>
<div class="alert mt-6">
<.icon name="hero-light-bulb" class="w-5 h-5" />
<div>
<h3 class="font-bold">Storage Provider Configuration</h3>
<p class="text-sm">
<strong>Local Filesystem:</strong> No additional configuration needed<br />
<strong>AWS S3:</strong> Requires region, bucket name, and access credentials<br />
<strong>Backblaze B2:</strong> Requires endpoint and access credentials<br />
<strong>Cloudflare R2:</strong> Requires account ID and access credentials
</p>
</div>
</div>
</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">Create Storage Path</h3>
<p class="py-4">
The storage path <code class="badge badge-ghost">{@missing_path}</code> does not exist.
</p>
<p class="mb-6">
Would you like to create it now?
</p>
<div class="modal-action">
<button
phx-click="cancel_create_path"
class="btn btn-ghost"
>
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 mr-1" /> Create Path
</button>
</div>
</div>
</div>
<% end %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>