From 86678eee5cafb1304757f9d785b4df620a9a6193 Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 22:26:25 +0200 Subject: [PATCH 1/7] Made the upload work with one button --- .../components/core/file_upload.ex | 8 ++--- lib/phoenix_kit_web/live/users/media.ex | 34 +++++++++++++++---- .../live/users/media.html.heex | 24 ++++--------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/lib/phoenix_kit_web/components/core/file_upload.ex b/lib/phoenix_kit_web/components/core/file_upload.ex index 0abde6372..6a69ccb1a 100644 --- a/lib/phoenix_kit_web/components/core/file_upload.ex +++ b/lib/phoenix_kit_web/components/core/file_upload.ex @@ -33,14 +33,14 @@ defmodule PhoenixKitWeb.Components.Core.FileUpload do def file_upload(assigns) do ~H"""
- <%!-- Upload Button and File Input --%> -
+ <%!-- Upload Form with phx-change on form not file input --%> +
@upload.ref}> - <.live_file_input upload={@upload} class="hidden" phx-change="validate" /> -
+ <.live_file_input upload={@upload} class="hidden" /> + <%!-- File Type and Size Info --%> <%= if @accept_description != nil or @max_size_description != nil do %> diff --git a/lib/phoenix_kit_web/live/users/media.ex b/lib/phoenix_kit_web/live/users/media.ex index afeebe599..e3d436574 100644 --- a/lib/phoenix_kit_web/live/users/media.ex +++ b/lib/phoenix_kit_web/live/users/media.ex @@ -37,7 +37,7 @@ defmodule PhoenixKitWeb.Live.Users.Media do accept: ["image/*", "video/*", "application/pdf"], max_entries: 10, max_file_size: 100_000_000, - auto_upload: false + auto_upload: true ) |> assign(:page_title, "Media") |> assign(:project_title, settings["project_title"]) @@ -49,17 +49,37 @@ defmodule PhoenixKitWeb.Live.Users.Media do end def handle_event("validate", _params, socket) do - # File validation event - called when files are selected + # File selection event - files will auto-upload + entries = socket.assigns.uploads.media_files.entries + Logger.info("validate event: entries=#{length(entries)}") + + if entries != [] do + Logger.info("validate: scheduling check_uploads_complete") + Process.send_after(self(), :check_uploads_complete, 500) + end + {:noreply, socket} end - def handle_event("cancel_upload", %{"ref" => ref}, socket) do - {:noreply, cancel_upload(socket, :media_files, ref)} + def handle_info(:check_uploads_complete, socket) do + entries = socket.assigns.uploads.media_files.entries + Logger.info("check_uploads_complete: entries=#{length(entries)}, done?=#{inspect(Enum.map(entries, & &1.done?))}") + + # Check if all entries are done uploading + if entries != [] && Enum.all?(entries, & &1.done?) do + Logger.info("All uploads done! Processing...") + # All done - process them + process_uploads(socket) + else + # Still uploading - check again later + Logger.info("Still uploading, checking again...") + Process.send_after(self(), :check_uploads_complete, 500) + {:noreply, socket} + end end - def handle_event("upload", _params, socket) do - # Process uploaded files when user clicks upload button - process_uploads(socket) + def handle_event("cancel_upload", %{"ref" => ref}, socket) do + {:noreply, cancel_upload(socket, :media_files, ref)} end defp process_uploads(socket) do diff --git a/lib/phoenix_kit_web/live/users/media.html.heex b/lib/phoenix_kit_web/live/users/media.html.heex index c6efea3db..cc358bd51 100644 --- a/lib/phoenix_kit_web/live/users/media.html.heex +++ b/lib/phoenix_kit_web/live/users/media.html.heex @@ -20,23 +20,13 @@ Upload images, videos, or PDFs. Files are automatically processed and optimized.

-
- <.file_upload - upload={@uploads.media_files} - label="Upload Media Files" - icon="hero-cloud-arrow-up" - accept_description="JPG, PNG, WebP, MP4, WebM, PDF" - max_size_description="100MB per file" - /> - - -
+ <.file_upload + upload={@uploads.media_files} + label="Upload Media Files" + icon="hero-cloud-arrow-up" + accept_description="JPG, PNG, WebP, MP4, WebM, PDF" + max_size_description="100MB per file" + />
From d2ba221004ec161770bb4f89fa4852559048f488 Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 22:31:07 +0200 Subject: [PATCH 2/7] Remove upload test button from storage settings Clean up temporary upload test functionality that was added for testing the storage system. --- lib/phoenix_kit_web/live/settings/storage.ex | 79 ------------------ .../live/settings/storage.html.heex | 83 ------------------- 2 files changed, 162 deletions(-) diff --git a/lib/phoenix_kit_web/live/settings/storage.ex b/lib/phoenix_kit_web/live/settings/storage.ex index a067ae54d..695322967 100644 --- a/lib/phoenix_kit_web/live/settings/storage.ex +++ b/lib/phoenix_kit_web/live/settings/storage.ex @@ -49,16 +49,8 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do imagemagick_status = Dependencies.check_imagemagick_cached() ffmpeg_status = Dependencies.check_ffmpeg_cached() - # Allow uploads - SUPER SIMPLE! socket = socket - |> allow_upload(:files, - accept: ["image/*", "video/*", "application/pdf"], - max_entries: 10, - max_file_size: 100_000_000, - # Manually upload on submit - auto_upload: false - ) |> assign(:current_path, current_path) |> assign(:page_title, "Storage Settings") |> assign(:project_title, project_title) @@ -71,7 +63,6 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do |> assign(:max_redundancy, max_redundancy) |> assign(:form_redundancy, form_redundancy) |> assign(:form_auto_generate_variants, form_auto_generate_variants) - |> assign(:uploaded_files, []) |> assign(:imagemagick_status, imagemagick_status) |> assign(:ffmpeg_status, ffmpeg_status) @@ -266,76 +257,6 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do end end - def handle_event("validate", _params, socket) do - # Handle form validation - files are being validated on change - {:noreply, socket} - end - - def handle_event("save", _params, socket) do - # Check if there are files selected in the browser - if socket.assigns.uploads.files.entries == [] do - {:noreply, put_flash(socket, :info, "No files selected")} - else - # consume_uploaded_entries will automatically upload the files first, then process them - uploaded_files = - consume_uploaded_entries(socket, :files, fn %{path: path}, entry -> - # Get file info - ext = Path.extname(entry.client_name) |> String.replace_leading(".", "") - mime_type = entry.client_type || MIME.from_path(entry.client_name) - file_type = determine_file_type(mime_type) - - # Get current user - current_user = socket.assigns.phoenix_kit_current_user - user_id = if current_user, do: current_user.id, else: 1 - - # Get file size - {:ok, stat} = File.stat(path) - file_size = stat.size - - # Calculate hash - file_hash = calculate_file_hash(path) - - # Store file in storage - case PhoenixKit.Storage.store_file_in_buckets(path, file_type, user_id, file_hash, ext) do - {:ok, file} -> - # Queue background job for processing - job = - %{file_id: file.id, user_id: user_id, filename: entry.client_name} - |> ProcessFileJob.new() - |> Oban.insert() - - # Debug logging - Logger.debug("Oban Job Inserted: #{inspect(job)}") - - {:ok, - %{ - file_id: file.id, - filename: entry.client_name, - file_type: file_type, - mime_type: mime_type, - size: file_size, - status: file.status, - url: nil - }} - - {:error, reason} -> - Logger.error("Storage Error: #{inspect(reason)}") - {:error, reason} - end - end) - - socket = - socket - |> assign(:uploaded_files, uploaded_files) - |> put_flash(:info, "Upload successful! #{length(uploaded_files)} file(s) processed") - - {:noreply, socket} - end - end - - def handle_event("cancel_upload", %{"ref" => ref}, socket) do - {:noreply, cancel_upload(socket, :files, ref)} - end defp calculate_file_hash(file_path) do file_path diff --git a/lib/phoenix_kit_web/live/settings/storage.html.heex b/lib/phoenix_kit_web/live/settings/storage.html.heex index 6fc589b94..1dab2ca2f 100644 --- a/lib/phoenix_kit_web/live/settings/storage.html.heex +++ b/lib/phoenix_kit_web/live/settings/storage.html.heex @@ -477,89 +477,6 @@ <%!-- Upload Test Section --%> -
-
-

- <.icon name="hero-cloud-arrow-up" class="w-6 h-6 mr-2" /> Upload Test -

-

- Upload a file to test the storage system. Supported formats: JPG, PNG, WebP, MP4, WebM, PDF -
Maximum file size: 100MB -

- - <%!-- Super simple: file input + submit button --%> -
- <.live_file_input upload={@uploads.files} phx-change="validate" /> - -
- - <%!-- Show uploaded entries --%> - <%= for entry <- @uploads.files.entries do %> -
-
-
-

{entry.client_name}

-

- {format_bytes(entry.client_size)} -

-
- -
-
- <% end %> - - <%= if length(@uploaded_files) > 0 do %> -
-

Uploaded Files

-
- - - - - - - - - - - - <%= for file <- @uploaded_files do %> - - - - - - - - <% end %> - -
FilenameTypeSizeStatusActions
-
{file.filename}
-
- {file.file_type} - {format_bytes(file.size)} - - {file.status} - - - -
-
-
- <% end %> -
-
<%!-- Help Section --%>
From 9394c7a76529d7a4b61eb3575c49dc64f094c645 Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 23:08:00 +0200 Subject: [PATCH 3/7] Updated Media page pagination --- lib/phoenix_kit_web/live/users/media.ex | 93 +++++++++++++------ .../live/users/media.html.heex | 20 +++- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/lib/phoenix_kit_web/live/users/media.ex b/lib/phoenix_kit_web/live/users/media.ex index e3d436574..ddeaa949c 100644 --- a/lib/phoenix_kit_web/live/users/media.ex +++ b/lib/phoenix_kit_web/live/users/media.ex @@ -28,9 +28,6 @@ defmodule PhoenixKitWeb.Live.Users.Media do %{"project_title" => "PhoenixKit"} ) - # Load existing files from database - existing_files = load_existing_files() - socket = socket |> allow_upload(:media_files, @@ -43,11 +40,30 @@ defmodule PhoenixKitWeb.Live.Users.Media do |> assign(:project_title, settings["project_title"]) |> assign(:current_locale, locale) |> assign(:url_path, Routes.path("/admin/users/media")) - |> assign(:uploaded_files, existing_files) {:ok, socket} end + def handle_params(params, _uri, socket) do + # Pagination setup + per_page = 50 + page = String.to_integer(params["page"] || "1") + + # Load existing files from database with pagination + {existing_files, total_count} = load_existing_files(page, per_page) + total_pages = ceil(total_count / per_page) + + socket = + socket + |> assign(:uploaded_files, existing_files) + |> assign(:current_page, page) + |> assign(:per_page, per_page) + |> assign(:total_pages, total_pages) + |> assign(:total_count, total_count) + + {:noreply, socket} + end + def handle_event("validate", _params, socket) do # File selection event - files will auto-upload entries = socket.assigns.uploads.media_files.entries @@ -63,7 +79,10 @@ defmodule PhoenixKitWeb.Live.Users.Media do def handle_info(:check_uploads_complete, socket) do entries = socket.assigns.uploads.media_files.entries - Logger.info("check_uploads_complete: entries=#{length(entries)}, done?=#{inspect(Enum.map(entries, & &1.done?))}") + + Logger.info( + "check_uploads_complete: entries=#{length(entries)}, done?=#{inspect(Enum.map(entries, & &1.done?))}" + ) # Check if all entries are done uploading if entries != [] && Enum.all?(entries, & &1.done?) do @@ -201,17 +220,24 @@ defmodule PhoenixKitWeb.Live.Users.Media do defp file_icon("document"), do: "hero-document" defp file_icon(_), do: "hero-document-arrow-down" - # Load existing files from database - defp load_existing_files do + # Load existing files from database with pagination + defp load_existing_files(page, per_page) do import Ecto.Query repo = Application.get_env(:phoenix_kit, :repo) - # Query files ordered by most recent first + # Get total count + total_count = repo.aggregate(PhoenixKit.Storage.File, :count, :id) + + # Calculate offset + offset = (page - 1) * per_page + + # Query files ordered by most recent first with pagination files = from(f in PhoenixKit.Storage.File, order_by: [desc: f.inserted_at], - limit: 50 + limit: ^per_page, + offset: ^offset ) |> repo.all() @@ -219,28 +245,35 @@ defmodule PhoenixKitWeb.Live.Users.Media do file_ids = Enum.map(files, & &1.id) instances_by_file = - from(fi in FileInstance, - where: fi.file_id in ^file_ids - ) - |> repo.all() - |> Enum.group_by(& &1.file_id) + if file_ids != [] do + from(fi in FileInstance, + where: fi.file_id in ^file_ids + ) + |> repo.all() + |> Enum.group_by(& &1.file_id) + else + %{} + end # Convert to same format as uploaded files - Enum.map(files, fn file -> - # Get pre-loaded instances for this file (no DB query!) - instances = Map.get(instances_by_file, file.id, []) - urls = generate_urls_from_instances(instances, file.id) - - %{ - file_id: file.id, - filename: file.original_file_name || file.file_name || "Unknown", - original_filename: file.original_file_name, - file_type: file.file_type, - mime_type: file.mime_type, - size: file.size || 0, - status: file.status, - urls: urls - } - end) + existing_files = + Enum.map(files, fn file -> + # Get pre-loaded instances for this file (no DB query!) + instances = Map.get(instances_by_file, file.id, []) + urls = generate_urls_from_instances(instances, file.id) + + %{ + file_id: file.id, + filename: file.original_file_name || file.file_name || "Unknown", + original_filename: file.original_file_name, + file_type: file.file_type, + mime_type: file.mime_type, + size: file.size || 0, + status: file.status, + urls: urls + } + end) + + {existing_files, total_count} end end diff --git a/lib/phoenix_kit_web/live/users/media.html.heex b/lib/phoenix_kit_web/live/users/media.html.heex index cc358bd51..26ef00f79 100644 --- a/lib/phoenix_kit_web/live/users/media.html.heex +++ b/lib/phoenix_kit_web/live/users/media.html.heex @@ -181,10 +181,17 @@ <%!-- Uploaded Files --%> - <%= if length(@uploaded_files) > 0 do %> + <%= if @total_count > 0 do %>
-

Uploaded Files ({length(@uploaded_files)})

+
+

Uploaded Files ({@total_count})

+ <.pagination_info + page={@current_page} + per_page={@per_page} + total_count={@total_count} + /> +
<%= for file <- @uploaded_files do %> @@ -221,6 +228,15 @@
<% end %>
+ + <%!-- Pagination Controls --%> + <%= if @total_count > @per_page do %> + <.pagination + current_page={@current_page} + total_pages={@total_pages} + base_path={@url_path} + /> + <% end %>
<% else %> From f25c029e01d7364d745917f5dc7f5bf2ccd9c415 Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 23:08:54 +0200 Subject: [PATCH 4/7] Updated the integrations to fix the catch-all issue stopping couple routes --- lib/phoenix_kit_web/integration.ex | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/phoenix_kit_web/integration.ex b/lib/phoenix_kit_web/integration.ex index 074dc2650..680107f70 100644 --- a/lib/phoenix_kit_web/integration.ex +++ b/lib/phoenix_kit_web/integration.ex @@ -451,6 +451,7 @@ defmodule PhoenixKitWeb.Integration do Live.Settings.Storage.DimensionForm, :edit + live "/admin/users/referral-codes", Live.Users.ReferralCodes, :index live "/admin/users/referral-codes/new", Live.Users.ReferralCodeForm, :new live "/admin/users/referral-codes/edit/:id", Live.Users.ReferralCodeForm, :edit live "/admin/emails/dashboard", Live.Modules.Emails.Metrics, :index @@ -513,8 +514,24 @@ defmodule PhoenixKitWeb.Integration do scope blog_scope_multi, PhoenixKitWeb do pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation] - get "/:blog", BlogController, :show - get "/:blog/*path", BlogController, :show + # Exclude admin paths from blogging catch-all routes + get "/:blog", BlogController, :show, constraints: %{"blog" => ~r/^(?!admin$)/} + get "/:blog/*path", BlogController, :show, constraints: %{"blog" => ~r/^(?!admin$)/} + end + + # Non-localized blog routes (for when url_prefix is "/") + blog_scope_non_localized = + case unquote(url_prefix) do + "/" -> "/" + prefix -> prefix + end + + scope blog_scope_non_localized, PhoenixKitWeb do + pipe_through [:browser, :phoenix_kit_auto_setup, :phoenix_kit_locale_validation] + + # Exclude admin paths from blogging catch-all routes + get "/:blog", BlogController, :show, constraints: %{"blog" => ~r/^(?!admin$)/} + get "/:blog/*path", BlogController, :show, constraints: %{"blog" => ~r/^(?!admin$)/} end end end From 4d51e774bbae71c88af7032c9825218c7667f702 Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 23:19:45 +0200 Subject: [PATCH 5/7] Uploaded images auto refresh the media display --- lib/phoenix_kit_web/live/settings/storage.ex | 1 - lib/phoenix_kit_web/live/users/media.ex | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/phoenix_kit_web/live/settings/storage.ex b/lib/phoenix_kit_web/live/settings/storage.ex index 695322967..777a78621 100644 --- a/lib/phoenix_kit_web/live/settings/storage.ex +++ b/lib/phoenix_kit_web/live/settings/storage.ex @@ -257,7 +257,6 @@ defmodule PhoenixKitWeb.Live.Settings.Storage do end end - defp calculate_file_hash(file_path) do file_path |> File.read!() diff --git a/lib/phoenix_kit_web/live/users/media.ex b/lib/phoenix_kit_web/live/users/media.ex index ddeaa949c..00bfdb1fa 100644 --- a/lib/phoenix_kit_web/live/users/media.ex +++ b/lib/phoenix_kit_web/live/users/media.ex @@ -157,9 +157,17 @@ defmodule PhoenixKitWeb.Live.Users.Media do end end) + # Reload paginated data from database to show newly uploaded files + per_page = socket.assigns.per_page || 50 + page = socket.assigns.current_page || 1 + {refreshed_files, total_count} = load_existing_files(page, per_page) + total_pages = ceil(total_count / per_page) + socket = socket - |> assign(:uploaded_files, (socket.assigns.uploaded_files || []) ++ uploaded_files) + |> assign(:uploaded_files, refreshed_files) + |> assign(:total_count, total_count) + |> assign(:total_pages, total_pages) |> put_flash(:info, "Upload successful! #{length(uploaded_files)} file(s) processed") {:noreply, socket} From d65c4db55b2d6f65ae47e2afd9827bb287ad4f8e Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 23:29:03 +0200 Subject: [PATCH 6/7] Added a media details page --- lib/phoenix_kit_web/integration.ex | 2 + .../live/users/media.html.heex | 11 +- .../live/users/media_detail.ex | 110 +++++++++++ .../live/users/media_detail.html.heex | 181 ++++++++++++++++++ 4 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 lib/phoenix_kit_web/live/users/media_detail.ex create mode 100644 lib/phoenix_kit_web/live/users/media_detail.html.heex diff --git a/lib/phoenix_kit_web/integration.ex b/lib/phoenix_kit_web/integration.ex index 680107f70..e7b09c71c 100644 --- a/lib/phoenix_kit_web/integration.ex +++ b/lib/phoenix_kit_web/integration.ex @@ -285,6 +285,7 @@ defmodule PhoenixKitWeb.Integration do live "/admin/users/live_sessions", Live.Users.LiveSessions, :index live "/admin/users/sessions", Live.Users.Sessions, :index live "/admin/users/media", Live.Users.Media, :index + live "/admin/users/media/:file_id", Live.Users.MediaDetail, :show live "/admin/settings", Live.Settings, :index live "/admin/settings/users", Live.Settings.Users, :index live "/admin/modules", Live.Modules, :index @@ -415,6 +416,7 @@ defmodule PhoenixKitWeb.Integration do live "/admin/users/live_sessions", Live.Users.LiveSessions, :index live "/admin/users/sessions", Live.Users.Sessions, :index live "/admin/users/media", Live.Users.Media, :index + live "/admin/users/media/:file_id", Live.Users.MediaDetail, :show live "/admin/settings", Live.Settings, :index live "/admin/settings/users", Live.Settings.Users, :index live "/admin/modules", Live.Modules, :index diff --git a/lib/phoenix_kit_web/live/users/media.html.heex b/lib/phoenix_kit_web/live/users/media.html.heex index 26ef00f79..f4365f819 100644 --- a/lib/phoenix_kit_web/live/users/media.html.heex +++ b/lib/phoenix_kit_web/live/users/media.html.heex @@ -195,8 +195,9 @@
<%= for file <- @uploaded_files do %> -
@@ -205,7 +206,7 @@ {file.filename}
<% else %> -
+
<.icon name={file_icon(file.file_type)} class="w-12 h-12 mb-2" />

{String.upcase(file.file_type)}

@@ -225,7 +226,7 @@
{format_file_size(file.size)}
-
+ <% end %>
diff --git a/lib/phoenix_kit_web/live/users/media_detail.ex b/lib/phoenix_kit_web/live/users/media_detail.ex new file mode 100644 index 000000000..cbca8ab25 --- /dev/null +++ b/lib/phoenix_kit_web/live/users/media_detail.ex @@ -0,0 +1,110 @@ +defmodule PhoenixKitWeb.Live.Users.MediaDetail do + @moduledoc """ + Single media file detail view for PhoenixKit admin panel. + + Provides a shareable view for a specific uploaded media file by file_id. + """ + use PhoenixKitWeb, :live_view + use Gettext, backend: PhoenixKitWeb.Gettext + + require Logger + + alias PhoenixKit.Settings + alias PhoenixKit.Storage.File + alias PhoenixKit.Storage.FileInstance + alias PhoenixKit.Storage.URLSigner + alias PhoenixKit.Utils.Routes + alias PhoenixKit.Utils.Date, as: UtilsDate + + def mount(params, _session, socket) do + # Set locale for LiveView process + locale = params["locale"] || socket.assigns[:current_locale] || "en" + Gettext.put_locale(PhoenixKitWeb.Gettext, locale) + Process.put(:phoenix_kit_current_locale, locale) + + # Get file_id from params + file_id = params["file_id"] + + # Batch load all settings needed for this page + settings = + Settings.get_settings_cached( + ["project_title"], + %{"project_title" => "PhoenixKit"} + ) + + socket = + socket + |> assign(:page_title, "Media Detail") + |> assign(:project_title, settings["project_title"]) + |> assign(:current_locale, locale) + |> assign(:file_id, file_id) + |> load_file_data(file_id) + + {:ok, socket} + end + + defp load_file_data(socket, file_id) do + repo = Application.get_env(:phoenix_kit, :repo) + import Ecto.Query + + case repo.get(File, file_id) do + nil -> + socket + |> assign(:file, nil) + |> assign(:file_data, nil) + + file -> + # Load file instances for this file + instances = + FileInstance + |> where([fi], fi.file_id == ^file_id) + |> repo.all() + + # Generate URLs from instances + urls = generate_urls_from_instances(instances, file_id) + + # Build file data map + file_data = %{ + file_id: file.id, + filename: file.original_file_name || file.file_name || "Unknown", + original_filename: file.original_file_name, + file_type: file.file_type, + mime_type: file.mime_type, + size: file.size || 0, + status: file.status, + urls: urls, + inserted_at: file.inserted_at, + updated_at: file.updated_at + } + + socket + |> assign(:file, file) + |> assign(:file_data, file_data) + end + end + + # Generate URLs from pre-loaded instances (no database query needed) + defp generate_urls_from_instances(instances, file_id) do + Enum.reduce(instances, %{}, fn instance, acc -> + url = URLSigner.signed_url(file_id, instance.variant_name) + Map.put(acc, instance.variant_name, url) + end) + end + + # Format file size in human-readable format + defp format_file_size(bytes) when is_integer(bytes) do + cond do + bytes >= 1_000_000_000 -> "#{Float.round(bytes / 1_000_000_000, 2)} GB" + bytes >= 1_000_000 -> "#{Float.round(bytes / 1_000_000, 2)} MB" + bytes >= 1_000 -> "#{Float.round(bytes / 1_000, 2)} KB" + true -> "#{bytes} B" + end + end + + # Get icon for file type + defp file_icon("image"), do: "hero-photo" + defp file_icon("video"), do: "hero-play-circle" + defp file_icon("pdf"), do: "hero-document-text" + defp file_icon("document"), do: "hero-document" + defp file_icon(_), do: "hero-document-arrow-down" +end diff --git a/lib/phoenix_kit_web/live/users/media_detail.html.heex b/lib/phoenix_kit_web/live/users/media_detail.html.heex new file mode 100644 index 000000000..09c3efb76 --- /dev/null +++ b/lib/phoenix_kit_web/live/users/media_detail.html.heex @@ -0,0 +1,181 @@ + +
+
+ <%!-- Back Button --%> + <.link + navigate={Routes.path("/admin/users/media")} + class="btn btn-outline btn-sm mb-4" + > + <.icon name="hero-arrow-left" class="w-4 h-4 mr-2" /> Back to Media + + + <%= if @file_data do %> +

{@file_data.filename}

+

Shareable media file

+ <% else %> +

File Not Found

+

The requested media file could not be found

+ <% end %> +
+ + <%= if @file_data do %> +
+
+ <%!-- File Preview --%> +
+ <%= if String.starts_with?(@file_data.mime_type, "image/") do %> + {@file_data.filename} + + <% else %> +
+ <.icon name={file_icon(@file_data.file_type)} class="w-20 h-20 mb-4" /> +

{String.upcase(@file_data.file_type)}

+
+ <% end %> +
+ + <%!-- File Information --%> +
+
+ +

{@file_data.filename}

+
+ +
+ + + {String.upcase(@file_data.file_type)} + +
+ +
+ +

{format_file_size(@file_data.size)}

+
+ +
+ + + {String.capitalize(@file_data.status)} + +
+ +
+ +

{@file_data.mime_type}

+
+ +
+ +

+ {@file_data.file_id} +

+
+
+ + <%!-- Timestamps --%> +
+ +
+
+ +

+ {UtilsDate.format_datetime_with_user_format(@file_data.inserted_at)} +

+
+ +
+ +

+ {UtilsDate.format_datetime_with_user_format(@file_data.updated_at)} +

+
+
+ + <%!-- Download Links --%> + <%= if map_size(@file_data.urls) > 0 do %> +
+ +
+

Download Variants:

+
+ <%!-- Sort variants with original first --%> + <% variant_order = ["original", "large", "medium", "small", "thumbnail"] %> + <% sorted_variants = + @file_data.urls + |> Enum.sort_by(fn {name, _url} -> + Enum.find_index(variant_order, &(&1 == name)) || 999 + end) %> + + <%= for {variant, url} <- sorted_variants do %> + + + + + + {String.capitalize(variant)} + + <% end %> +
+
+ <% end %> +
+
+ <% else %> +
+ <.icon name="hero-photo" class="w-12 h-12 mx-auto mb-4 text-base-content/50" /> +

+ The file you're looking for doesn't exist or has been deleted. +

+ <.link navigate={Routes.path("/admin/users/media")} class="btn btn-primary btn-sm mt-4"> + Back to Media Library + +
+ <% end %> +
+
From 7f6cefa4e96efe5f626760eade82d5bf8109dc2c Mon Sep 17 00:00:00 2001 From: Alexander Don Date: Mon, 10 Nov 2025 23:31:43 +0200 Subject: [PATCH 7/7] Media Redundant copies set to 1 from 2 --- lib/phoenix_kit/storage.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/phoenix_kit/storage.ex b/lib/phoenix_kit/storage.ex index 063111ef0..f20e223c3 100644 --- a/lib/phoenix_kit/storage.ex +++ b/lib/phoenix_kit/storage.ex @@ -871,7 +871,7 @@ defmodule PhoenixKit.Storage do end defp get_redundancy_copies do - Settings.get_setting("storage_redundancy_copies", "2") + Settings.get_setting("storage_redundancy_copies", "1") |> String.to_integer() |> max(1) |> min(5)