mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 04:38:11 +00:00
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.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
RefreshCw, Search, Camera, Plus,
|
||||
RefreshCw, Search, Camera, Plus, FileDown,
|
||||
Network, SlidersHorizontal,
|
||||
Send, KeyRound, ArrowLeftRight, Wrench, Workflow,
|
||||
} from 'lucide-react';
|
||||
@@ -12,6 +12,7 @@ import { useFleetPreferences } from './FleetView/hooks/useFleetPreferences';
|
||||
import { useFleetUpdateStatus } from './FleetView/hooks/useFleetUpdateStatus';
|
||||
import { useFleetPolling } from './FleetView/hooks/useFleetPolling';
|
||||
import { useFleetOverview } from './FleetView/hooks/useFleetOverview';
|
||||
import { useFleetDossierExport } from './FleetView/hooks/useFleetDossierExport';
|
||||
import { useTopologyPreferences } from '@/hooks/useTopologyPreferences';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs';
|
||||
@@ -42,6 +43,7 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
|
||||
const updateStatus = useFleetUpdateStatus();
|
||||
const overview = useFleetOverview({ isPaid, prefs, updatePrefs, updateStatuses: updateStatus.updateStatuses });
|
||||
const topology = useTopologyPreferences();
|
||||
const { exporting, exportDossier } = useFleetDossierExport();
|
||||
|
||||
useFleetPolling({
|
||||
fetchOverview: overview.fetchOverview,
|
||||
@@ -151,6 +153,18 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
|
||||
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => { void exportDossier(); }}
|
||||
disabled={exporting}
|
||||
className="gap-2"
|
||||
>
|
||||
<FileDown className={`w-4 h-4 ${exporting ? 'animate-pulse' : ''}`} />
|
||||
{exporting ? 'Exporting…' : 'Export Dossier'}
|
||||
</Button>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<SettingsPrimaryButton
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user