diff --git a/frontend/src/components/config/ConfigHistorySection.tsx b/frontend/src/components/config/ConfigHistorySection.tsx index 153968a..f5044b6 100644 --- a/frontend/src/components/config/ConfigHistorySection.tsx +++ b/frontend/src/components/config/ConfigHistorySection.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { useQuery } from '@tanstack/react-query' -import { History } from 'lucide-react' +import { Download, History } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { TableSkeleton } from '@/components/ui/page-skeleton' import { configHistoryApi } from '@/lib/api' @@ -9,6 +9,7 @@ import { DiffViewer } from './DiffViewer' interface ConfigHistorySectionProps { tenantId: string deviceId: string + deviceName: string } function formatRelativeTime(isoDate: string): string { @@ -41,8 +42,22 @@ function LineDelta({ added, removed }: { added: number; removed: number }) { ) } -export function ConfigHistorySection({ tenantId, deviceId }: ConfigHistorySectionProps) { +export function ConfigHistorySection({ tenantId, deviceId, deviceName }: ConfigHistorySectionProps) { const [selectedSnapshotId, setSelectedSnapshotId] = useState(null) + + async function handleDownload(snapshotId: string, collectedAt: string) { + const snapshot = await configHistoryApi.getSnapshot(tenantId, deviceId, snapshotId) + const timestamp = new Date(collectedAt).toISOString().replace(/[:.]/g, '-').slice(0, 19) + const filename = `router-${deviceName}-${timestamp}.rsc` + const blob = new Blob([snapshot.config_text], { type: 'text/plain' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = filename + a.click() + URL.revokeObjectURL(url) + } + const { data: changes, isLoading } = useQuery({ queryKey: ['config-history', tenantId, deviceId], queryFn: () => configHistoryApi.list(tenantId, deviceId), @@ -102,6 +117,18 @@ export function ConfigHistorySection({ tenantId, deviceId }: ConfigHistorySectio {formatRelativeTime(entry.created_at)} + + {/* Download button */} + ))} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 787ba88..c60c842 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -991,6 +991,13 @@ export interface DiffResponse { created_at: string } +export interface SnapshotResponse { + id: string + config_text: string + sha256_hash: string + collected_at: string +} + export const configHistoryApi = { list: (tenantId: string, deviceId: string, limit = 50, offset = 0) => api @@ -1006,6 +1013,13 @@ export const configHistoryApi = { `/api/tenants/${tenantId}/devices/${deviceId}/config/${snapshotId}/diff`, ) .then((r) => r.data), + + getSnapshot: (tenantId: string, deviceId: string, snapshotId: string) => + api + .get( + `/api/tenants/${tenantId}/devices/${deviceId}/config/${snapshotId}`, + ) + .then((r) => r.data), } // ─── VPN (WireGuard) ──────────────────────────────────────────────────────── diff --git a/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx b/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx index f4acab0..7bc504b 100644 --- a/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx +++ b/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx @@ -649,7 +649,7 @@ function DeviceDetailPage() { {/* Configuration History */} - + } alertsContent={