diff --git a/frontend/src/components/buckets/ObjectDetailsView.tsx b/frontend/src/components/buckets/ObjectDetailsView.tsx
index 47e2d9f..4bd330f 100644
--- a/frontend/src/components/buckets/ObjectDetailsView.tsx
+++ b/frontend/src/components/buckets/ObjectDetailsView.tsx
@@ -8,7 +8,7 @@ import { IconTile } from '@/components/ui/icon-tile';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { ArrowLeft, ChevronRight, Copy, Download, File, Loader2, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
-import { formatBytes } from '@/lib/file-utils';
+import { downloadObject, formatBytes } from '@/lib/file-utils';
import { formatDate } from '@/lib/utils';
function CardSection({ title, children }: { title: string; children: React.ReactNode }) {
@@ -73,22 +73,9 @@ export function ObjectDetailsView() {
toast.success(label);
};
- const handleDownload = async () => {
+ const handleDownload = () => {
if (!bucketName || !objectKey) return;
- try {
- const blob = await objectsApi.get(bucketName, objectKey);
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = fileName || 'download';
- document.body.appendChild(a);
- a.click();
- window.URL.revokeObjectURL(url);
- document.body.removeChild(a);
- toast.success('Download started');
- } catch {
- // error toast handled by axios interceptor
- }
+ downloadObject(bucketName, objectKey);
};
const handleDelete = async () => {
diff --git a/frontend/src/components/buckets/ObjectsTable.tsx b/frontend/src/components/buckets/ObjectsTable.tsx
index 4c5dea8..f063442 100644
--- a/frontend/src/components/buckets/ObjectsTable.tsx
+++ b/frontend/src/components/buckets/ObjectsTable.tsx
@@ -14,7 +14,7 @@ import {
} from '@/components/ui/dropdown-menu';
import {ChevronLeft, ChevronRight, Download, Eye, FileIcon, FolderIcon, Loader2, MoreVertical, Trash2} from 'lucide-react';
import {Select, SelectOption} from '@/components/ui/select';
-import {formatBytes, formatRelativeTime} from '@/lib/file-utils';
+import {downloadObject, formatBytes, formatRelativeTime} from '@/lib/file-utils';
import type {S3Object} from '@/types';
interface ObjectsTableProps {
@@ -353,7 +353,7 @@ export function ObjectsTable({
View Details
-
+ downloadObject(bucketName, obj.key)}>
Download
diff --git a/frontend/src/lib/file-utils.ts b/frontend/src/lib/file-utils.ts
index f9a015b..93d0f39 100644
--- a/frontend/src/lib/file-utils.ts
+++ b/frontend/src/lib/file-utils.ts
@@ -1,3 +1,27 @@
+import { objectsApi } from './api';
+import { toast } from 'sonner';
+
+/**
+ * Download an object from a bucket by fetching it as a blob and clicking a
+ * temporary anchor element. Errors are surfaced by the axios interceptor.
+ */
+export async function downloadObject(bucket: string, key: string): Promise {
+ try {
+ const blob = await objectsApi.get(bucket, key);
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = key.split('/').pop() || 'download';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ toast.success('Download started');
+ } catch {
+ // error toast handled by axios interceptor
+ }
+}
+
/**
* Get the file type based on file extension
*/