fix(dashboard): F-U-009 add download link + copy-id button per file row
Listing showed name/content-type/size/created/id; only mutation was Delete. No download link, no copy-id button (the UUID was rendered unclickable), no preview, no metadata refresh. Operators had to use the admin API directly. - Add api.files.downloadUrl(slug, collection, id) helper that builds the admin GET URL. The anchor uses HTML `download` so the browser saves with the original filename. - Add a ⧉ copy-id button next to the UUID column using navigator.clipboard.writeText. Silently no-ops where the browser doesn't expose clipboard (plain-http LAN). - Preview / metadata-refresh are deferred to a UI overhaul. AUDIT.md anchor: F-U-009 (partial — download + copy; preview deferred). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -907,7 +907,11 @@ export const api = {
|
|||||||
adminRequest<null>(
|
adminRequest<null>(
|
||||||
`/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/files/${encodeURIComponent(collection)}/${fileId}`,
|
`/api/v1/admin/apps/${encodeURIComponent(idOrSlug)}/files/${encodeURIComponent(collection)}/${fileId}`,
|
||||||
{ method: 'DELETE' }
|
{ 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: {
|
secrets: {
|
||||||
|
|||||||
@@ -91,6 +91,12 @@
|
|||||||
return new Date(iso).toLocaleString();
|
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 {
|
function fmtSize(bytes: number): string {
|
||||||
if (bytes < 1024) return `${bytes} B`;
|
if (bytes < 1024) return `${bytes} B`;
|
||||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||||
@@ -180,8 +186,25 @@
|
|||||||
<td><code>{f.content_type}</code></td>
|
<td><code>{f.content_type}</code></td>
|
||||||
<td>{fmtSize(f.size)}</td>
|
<td>{fmtSize(f.size)}</td>
|
||||||
<td>{fmtTime(f.created_at)}</td>
|
<td>{fmtTime(f.created_at)}</td>
|
||||||
<td class="mono small">{f.id}</td>
|
<td class="mono small">
|
||||||
|
{f.id}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="copy-btn"
|
||||||
|
title="Copy ID"
|
||||||
|
onclick={() => copyToClipboard(f.id)}
|
||||||
|
>
|
||||||
|
⧉
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<a
|
||||||
|
class="secondary"
|
||||||
|
href={api.files.downloadUrl(slug, activeCollection, f.id)}
|
||||||
|
download={f.name}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
<button type="button" class="danger" onclick={() => (fileToRemove = f)}>
|
<button type="button" class="danger" onclick={() => (fileToRemove = f)}>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user