Files
sencho/frontend/src/lib/download.ts
T
Anso 842ee7dd0c feat(fleet): export a whole-fleet Markdown dossier (#1334)
* feat(fleet): export a whole-fleet Markdown dossier

Add an admin-only "Export Dossier" action to the Fleet view that walks
every node and stack, pairs each stack's generated Compose anatomy with
its operator notes, and downloads a folder-structured homelab-dossier.zip
(index, per-node and per-stack pages, plus fleet-wide port, volume,
network, env, access-URL, and VLAN/firewall maps).

Reuses the existing stack dossier and anatomy Markdown generators by
extracting the shared Compose parsers into a frontend lib module.
Unreachable nodes are recorded with a reason and never block the export;
only env variable names and counts are ever emitted, never values.

* fix(fleet): unique stack slugs and reproducible dossier archive

Disambiguate stack names on one node that slugify to the same value (e.g.
`Web` and `web` on a case-sensitive host) with a per-node slug map shared by
the node-page links and the file emission, so neither overwrites the other.
Pin a fixed entry timestamp on the zip so the archive bytes are a pure
function of the file map rather than the wall clock.
2026-06-07 21:10:02 -04:00

24 lines
888 B
TypeScript

/**
* Trigger a browser download of in-memory text as a file, using the standard
* object-URL + anchor-click idiom (the same pattern used elsewhere for exports,
* e.g. AuditLogView). Suited to client-side text such as a Markdown export.
*/
export function downloadTextFile(filename: string, text: string, mime = 'text/markdown'): void {
downloadBlob(filename, new Blob([text], { type: `${mime};charset=utf-8` }));
}
/**
* Trigger a browser download of an in-memory Blob (e.g. a zip archive built
* client-side). Same object-URL + anchor-click idiom as {@link downloadTextFile}.
*/
export function downloadBlob(filename: string, blob: Blob): void {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}