diff --git a/dashboard/src/lib/api.ts b/dashboard/src/lib/api.ts index 770f609..0be7376 100644 --- a/dashboard/src/lib/api.ts +++ b/dashboard/src/lib/api.ts @@ -907,7 +907,11 @@ export const api = { adminRequest( `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/files/${encodeURIComponent(collection)}/${fileId}`, { method: 'DELETE' } - ) + ), + /// F-U-009: build the admin download URL. GET returns the file + /// bytes inline; the browser handles content-disposition. + downloadUrl: (idOrSlug: string, collection: string, fileId: string): string => + `/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/files/${encodeURIComponent(collection)}/${fileId}` }, secrets: { diff --git a/dashboard/src/routes/apps/[slug]/files/+page.svelte b/dashboard/src/routes/apps/[slug]/files/+page.svelte index 0a37797..352e4dc 100644 --- a/dashboard/src/routes/apps/[slug]/files/+page.svelte +++ b/dashboard/src/routes/apps/[slug]/files/+page.svelte @@ -91,6 +91,12 @@ return new Date(iso).toLocaleString(); } + // F-U-009: best-effort clipboard copy; silently no-ops where the + // browser doesn't expose navigator.clipboard (e.g. plain-http LAN). + function copyToClipboard(text: string) { + void navigator.clipboard?.writeText(text); + } + function fmtSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; @@ -180,8 +186,25 @@ {f.content_type} {fmtSize(f.size)} {fmtTime(f.created_at)} - {f.id} + + {f.id} + + + + Download +