Packages

phoenix_kit

1.7.20
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 settings.ex
Raw

lib/modules/storage/web/settings.ex

defmodule PhoenixKitWeb.Live.Modules.Storage.Settings do
@moduledoc """
Storage settings management LiveView for PhoenixKit.
Provides configuration interface for the distributed file storage system.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
require Logger
import Ecto.Query
alias PhoenixKit.Modules.Storage
alias PhoenixKit.Settings
alias PhoenixKit.System.Dependencies
alias PhoenixKit.Utils.Routes
def mount(_params, _session, socket) do
# Get current path for navigation
current_path = get_current_path(socket, %{})
# Get project title from settings
project_title = Settings.get_setting("project_title", "PhoenixKit")
# Load buckets
buckets = Storage.list_buckets()
# Load file counts per bucket (unique files, not instances)
bucket_file_counts = get_bucket_file_counts(buckets)
# Load storage settings from database (using basic function to avoid cache issues)
redundancy_copies = Settings.get_setting("storage_redundancy_copies", "1")
auto_generate_variants = Settings.get_setting("storage_auto_generate_variants", "true")
default_bucket_id = Settings.get_setting("storage_default_bucket_id", nil)
# Calculate maximum redundancy based on available buckets
active_buckets = Enum.count(buckets, & &1.enabled)
max_redundancy = if active_buckets > 0, do: active_buckets, else: 1
# Keep user's current redundancy setting unchanged
current_redundancy = String.to_integer(redundancy_copies)
# Store form values for batch updates
form_redundancy = current_redundancy
form_auto_generate_variants = auto_generate_variants == "true"
# Check system dependencies
imagemagick_status = Dependencies.check_imagemagick_cached()
ffmpeg_status = Dependencies.check_ffmpeg_cached()
socket =
socket
|> assign(:current_path, current_path)
|> assign(:page_title, "Storage Settings")
|> assign(:project_title, project_title)
|> assign(:buckets, buckets)
|> assign(:bucket_file_counts, bucket_file_counts)
|> assign(:redundancy_copies, current_redundancy)
|> assign(:auto_generate_variants, auto_generate_variants == "true")
|> assign(:default_bucket_id, default_bucket_id)
|> assign(:active_buckets_count, active_buckets)
|> assign(:max_redundancy, max_redundancy)
|> assign(:form_redundancy, form_redundancy)
|> assign(:form_auto_generate_variants, form_auto_generate_variants)
|> assign(:imagemagick_status, imagemagick_status)
|> assign(:ffmpeg_status, ffmpeg_status)
{:ok, socket}
end
def handle_event("update_redundancy", %{"redundancy_copies" => copies}, socket) do
requested_copies = String.to_integer(copies)
max_redundancy = socket.assigns.max_redundancy
if requested_copies > max_redundancy do
socket =
socket
|> put_flash(
:error,
"Cannot set redundancy to #{requested_copies} copies. Only #{max_redundancy} active bucket(s) available."
)
{:noreply, socket}
else
case Settings.update_setting("storage_redundancy_copies", copies) do
{:ok, _setting} ->
# Settings.update_setting already handles cache invalidation
socket =
socket
|> assign(:redundancy_copies, requested_copies)
|> put_flash(
:info,
"Redundancy settings updated to #{requested_copies} #{if requested_copies == 1, do: "copy", else: "copies"}"
)
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to update redundancy settings")
{:noreply, socket}
end
end
end
def handle_event("update_form_redundancy", %{"form_redundancy" => copies}, socket) do
# Handle both string and integer inputs
form_redundancy =
cond do
is_integer(copies) -> copies
is_binary(copies) -> String.to_integer(copies)
# fallback
true -> 1
end
socket =
socket
|> assign(:form_redundancy, form_redundancy)
{:noreply, socket}
end
def handle_event("update_form_variants", %{"form_auto_generate_variants" => value}, socket) do
form_auto_generate_variants = value == "true"
socket =
socket
|> assign(:form_auto_generate_variants, form_auto_generate_variants)
{:noreply, socket}
end
def handle_event("toggle_form_variants", _params, socket) do
new_value = not socket.assigns.form_auto_generate_variants
socket =
socket
|> assign(:form_auto_generate_variants, new_value)
{:noreply, socket}
end
def handle_event("update_storage_form", %{"form_redundancy" => redundancy}, socket) do
# Handle both string and integer inputs
form_redundancy =
cond do
is_integer(redundancy) -> redundancy
is_binary(redundancy) -> String.to_integer(redundancy)
# fallback
true -> 1
end
socket =
socket
|> assign(:form_redundancy, form_redundancy)
{:noreply, socket}
end
def handle_event("update_storage_form", _params, socket) do
# Handle cases where form doesn't include redundancy field
{:noreply, socket}
end
def handle_event("apply_storage_settings", _params, socket) do
# Get current form values
new_redundancy = socket.assigns.form_redundancy
new_variants = if socket.assigns.form_auto_generate_variants, do: "true", else: "false"
# Validate redundancy doesn't exceed available buckets
max_redundancy = socket.assigns.max_redundancy
if new_redundancy > max_redundancy do
socket =
socket
|> put_flash(
:error,
"Cannot set redundancy to #{new_redundancy} copies. Only #{max_redundancy} active bucket(s) available."
)
{:noreply, socket}
else
# Update both settings
redundancy_result =
Settings.update_setting("storage_redundancy_copies", to_string(new_redundancy))
variants_result = Settings.update_setting("storage_auto_generate_variants", new_variants)
case {redundancy_result, variants_result} do
{{:ok, _}, {:ok, _}} ->
# Verify the settings were saved correctly by reading them back
saved_redundancy = Settings.get_setting("storage_redundancy_copies", "1")
saved_variants = Settings.get_setting("storage_auto_generate_variants", "true")
socket =
socket
|> assign(:redundancy_copies, String.to_integer(saved_redundancy))
|> assign(:auto_generate_variants, saved_variants == "true")
|> assign(:form_redundancy, String.to_integer(saved_redundancy))
|> assign(:form_auto_generate_variants, saved_variants == "true")
|> put_flash(:info, "Storage settings updated successfully")
{:noreply, socket}
{{:error, _}, {:ok, _}} ->
socket = put_flash(socket, :error, "Failed to update redundancy settings")
{:noreply, socket}
{{:ok, _}, {:error, _}} ->
socket = put_flash(socket, :error, "Failed to update variant settings")
{:noreply, socket}
{{:error, _}, {:error, _}} ->
socket = put_flash(socket, :error, "Failed to update storage settings")
{:noreply, socket}
end
end
end
def handle_event("toggle_variants", _params, socket) do
new_value = if socket.assigns.auto_generate_variants, do: "false", else: "true"
case Settings.update_setting("storage_auto_generate_variants", new_value) do
{:ok, _setting} ->
# Settings.update_setting already handles cache invalidation
socket =
socket
|> assign(:auto_generate_variants, new_value == "true")
|> put_flash(
:info,
"Auto-variant generation #{if new_value == "true", do: "enabled", else: "disabled"}"
)
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to update variant settings")
{:noreply, socket}
end
end
def handle_event("update_default_bucket", %{"bucket_id" => bucket_id}, socket) do
new_value = if bucket_id == "", do: nil, else: bucket_id
case Settings.update_setting("storage_default_bucket_id", new_value) do
{:ok, _setting} ->
socket =
socket
|> assign(:default_bucket_id, new_value)
|> put_flash(:info, "Default bucket updated")
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to update default bucket")
{:noreply, socket}
end
end
def handle_event("delete_bucket", %{"id" => bucket_id}, socket) do
bucket = Storage.get_bucket(bucket_id)
case Storage.delete_bucket(bucket) do
{:ok, _bucket} ->
# Reload buckets and recalculate max redundancy
buckets = Storage.list_buckets()
active_buckets_count = Enum.count(buckets, & &1.enabled)
max_redundancy = max(1, active_buckets_count)
socket =
socket
|> assign(:buckets, buckets)
|> assign(:active_buckets_count, active_buckets_count)
|> assign(:max_redundancy, max_redundancy)
|> put_flash(:info, "Bucket deleted successfully")
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to delete bucket")
{:noreply, socket}
end
end
def handle_event("repair_storage_module", _params, socket) do
case Storage.repair_storage_module() do
{:ok, repairs} ->
repair_summary = format_repairs(repairs)
socket =
socket
|> reload_settings_data()
|> put_flash(:info, "Storage module repaired: #{repair_summary}")
{:noreply, socket}
{:error, reason} ->
socket = put_flash(socket, :error, "Failed to repair: #{inspect(reason)}")
{:noreply, socket}
end
end
defp get_current_path(_socket, _session) do
# For Storage settings page
Routes.path("/admin/settings/media")
end
# Helper function to get full path for a bucket
defp get_bucket_full_path(bucket) do
case bucket.provider do
"local" ->
bucket.endpoint || "No path configured"
provider when provider in ["s3", "b2", "r2"] ->
path_parts = [
provider <> ":",
if(bucket.bucket_name, do: bucket.bucket_name, else: "no-bucket"),
if(bucket.endpoint, do: bucket.endpoint, else: "/")
]
Enum.join(path_parts, "")
_ ->
"#{bucket.provider}: unknown configuration"
end
end
# Get count of unique files stored on each bucket
defp get_bucket_file_counts(buckets) do
repo = PhoenixKit.Config.get_repo()
Enum.reduce(buckets, %{}, fn bucket, acc ->
# Count distinct files that have at least one instance located on this bucket
# We count files, not instances or locations
count =
repo.one(
from f in Storage.File,
join: fi in Storage.FileInstance,
on: fi.file_id == f.id,
join: fl in Storage.FileLocation,
on: fl.file_instance_id == fi.id,
where: fl.bucket_id == ^bucket.id and fl.status == "active",
select: count(f.id, :distinct)
)
Map.put(acc, bucket.id, count || 0)
end)
rescue
_ -> %{}
end
defp format_repairs(repairs) do
Enum.map_join(repairs, ", ", fn
{:bucket_created, name} -> "created bucket '#{name}'"
{:dimensions_reset, count} -> "reset #{count} dimensions"
{:settings_reset, count} -> "reset #{count} settings"
end)
end
defp reload_settings_data(socket) do
# Reload buckets
buckets = Storage.list_buckets()
bucket_file_counts = get_bucket_file_counts(buckets)
# Reload storage settings
redundancy_copies = Settings.get_setting("storage_redundancy_copies", "1")
auto_generate_variants = Settings.get_setting("storage_auto_generate_variants", "true")
default_bucket_id = Settings.get_setting("storage_default_bucket_id", nil)
# Recalculate max redundancy
active_buckets_count = Enum.count(buckets, & &1.enabled)
max_redundancy = if active_buckets_count > 0, do: active_buckets_count, else: 1
current_redundancy = String.to_integer(redundancy_copies)
socket
|> assign(:buckets, buckets)
|> assign(:bucket_file_counts, bucket_file_counts)
|> assign(:redundancy_copies, current_redundancy)
|> assign(:auto_generate_variants, auto_generate_variants == "true")
|> assign(:default_bucket_id, default_bucket_id)
|> assign(:active_buckets_count, active_buckets_count)
|> assign(:max_redundancy, max_redundancy)
|> assign(:form_redundancy, current_redundancy)
|> assign(:form_auto_generate_variants, auto_generate_variants == "true")
end
end