From 5455e9b94cffe3d791f0cd2afbcceccf25bcdbbf Mon Sep 17 00:00:00 2001 From: Yashwin Date: Mon, 1 Jun 2026 16:38:09 +0530 Subject: [PATCH] feat: add storage cleanup tab and implement blob purging functionality in StorageDashboard --- src/pages/tenant/StorageDashboard.tsx | 173 +++++++++++++++++++++++- src/services/file-attachment-service.ts | 6 +- 2 files changed, 173 insertions(+), 6 deletions(-) diff --git a/src/pages/tenant/StorageDashboard.tsx b/src/pages/tenant/StorageDashboard.tsx index 443e103..bf1f857 100644 --- a/src/pages/tenant/StorageDashboard.tsx +++ b/src/pages/tenant/StorageDashboard.tsx @@ -12,6 +12,8 @@ import { CheckCircle2, Save, Loader2, + Trash2, + AlertTriangle, } from "lucide-react"; import { Layout } from "@/components/layout/Layout"; import { cn } from "@/lib/utils"; @@ -135,13 +137,44 @@ const QuotaEditModal = ({ const StorageDashboard = (): ReactElement => { const { primaryColor } = useAppTheme(); - const [activeTab, setActiveTab] = useState<"stats" | "quota">("stats"); + const [activeTab, setActiveTab] = useState<"stats" | "quota" | "cleanup">("stats"); const [stats, setStats] = useState(null); const [quota, setQuota] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isEditModalOpen, setIsEditModalOpen] = useState(false); + // Storage Cleanup (purgeSoftDeletedBlobs) states + const [olderThanHours, setOlderThanHours] = useState(24); + const [isPurging, setIsPurging] = useState(false); + const [purgeResult, setPurgeResult] = useState(null); + const [purgeError, setPurgeError] = useState(null); + const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); + + const handlePurge = async () => { + setIsConfirmModalOpen(false); + setIsPurging(true); + setPurgeResult(null); + setPurgeError(null); + try { + const res = await fileAttachmentService.purgeSoftDeletedBlobs(olderThanHours); + if (res.success) { + setPurgeResult(res.data.purged); + // Refresh usage stats and quota following successful purge + await loadData(); + } else { + setPurgeError("Failed to execute purge on the storage provider."); + } + } catch (err: any) { + setPurgeError( + err?.response?.data?.error?.message || "Failed to purge unreferenced files." + ); + console.error("Purge error:", err); + } finally { + setIsPurging(false); + } + }; + const loadData = async () => { setLoading(true); try { @@ -243,6 +276,22 @@ const StorageDashboard = (): ReactElement => { > Quota Details + {activeTab === "stats" && ( @@ -462,8 +511,130 @@ const StorageDashboard = (): ReactElement => { )} + + {activeTab === "cleanup" && ( +
+
+
+ +

+ Storage Maintenance & Cleanup +

+
+
+
+

+ When files are deleted, the system decrements their reference counts. Files marked as{" "} + soft-deleted with 0 active references are preserved temporarily on the storage provider + to allow background services (like AI embeddings, audits, or indexes) to finish processing them. +

+

+ Use this administrative tool to permanently purge these unreferenced physical files from the storage provider and reclaim space. +

+
+ +
+ +
+

Warning: Permanent Action

+

+ Purging files is irreversible. Once deleted from the physical storage provider (local disk, Azure, or AWS), + the file binaries cannot be recovered. +

+
+
+ +
+ setOlderThanHours(Math.max(0, parseInt(e.target.value) || 0))} + placeholder="e.g. 24 (0 = purge all eligible immediately)" + /> + + Set to 0 to instantly purge all unreferenced soft-deleted blobs, or specify hours (e.g., 24) to keep files deleted within that timeframe. + + + {purgeResult !== null && ( +
+ + + Successfully purged {purgeResult} orphaned file binary/binaries, freeing up storage space! + +
+ )} + + {purgeError && ( +
+ {purgeError} +
+ )} + +
+ setIsConfirmModalOpen(true)} + disabled={isPurging} + className="bg-red-600 hover:bg-red-700 text-white font-bold flex items-center gap-2 border-red-600 shadow-sm" + > + {isPurging ? ( + + ) : ( + + )} + Purge Unreferenced Files + +
+
+
+
+
+ )} + {isConfirmModalOpen && ( + setIsConfirmModalOpen(false)} + title="Confirm Storage Purge" + maxWidth="sm" + footer={ +
+ + +
+ } + > +
+
+ +
+
+

Are you absolutely sure?

+

+ This will permanently delete all unreferenced physical file binaries older than{" "} + {olderThanHours} hours from your storage provider. This action is irreversible. +

+
+
+
+ )} + {isEditModalOpen && ( formData.append('files', file)); formData.append('entity_type', params.entity_type); formData.append('entity_id', params.entity_id); - if (params.supplier_id) formData.append('supplier_id', params.supplier_id); if (params.category) formData.append('category', params.category); if (params.category_id) formData.append('category_id', params.category_id); if (params.description) formData.append('description', params.description); @@ -409,7 +405,7 @@ export const fileAttachmentService = { * @param olderThanHours - 0 means purge all eligible blobs regardless of age. */ purgeSoftDeletedBlobs: async (olderThanHours = 0): Promise<{ success: boolean; data: { purged: number } }> => { - const response = await apiClient.post('/files/blobs/purge-soft-deleted', null, { + const response = await apiClient.post('/files/blobs/purge-soft-deleted', {}, { params: olderThanHours > 0 ? { older_than_hours: olderThanHours } : {}, }); return response.data;