import { useState } from 'react'; import { useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { HardDrive, Plus, Download, Trash2, Lock, Unlock, RotateCcw, CheckCircle2, Clock, } from 'lucide-react'; import { api } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; interface Backup { id: string; name: string; sizeBytes: number | null; cdnPath: string | null; checksum: string | null; isLocked: boolean; completedAt: string | null; createdAt: string; } function formatBytes(bytes: number | null): string { if (bytes === null || bytes === 0) return '—'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i] ?? 'B'}`; } export function BackupsPage() { const { orgId, serverId } = useParams(); const queryClient = useQueryClient(); const [showCreate, setShowCreate] = useState(false); const [confirmRestore, setConfirmRestore] = useState(null); const { data } = useQuery({ queryKey: ['backups', orgId, serverId], queryFn: () => api.get<{ backups: Backup[] }>(`/organizations/${orgId}/servers/${serverId}/backups`), }); const deleteMutation = useMutation({ mutationFn: (backupId: string) => api.delete(`/organizations/${orgId}/servers/${serverId}/backups/${backupId}`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['backups', orgId, serverId] }), }); const restoreMutation = useMutation({ mutationFn: (backupId: string) => api.post(`/organizations/${orgId}/servers/${serverId}/backups/${backupId}/restore`, {}), onSuccess: () => setConfirmRestore(null), }); const lockMutation = useMutation({ mutationFn: (backupId: string) => api.patch(`/organizations/${orgId}/servers/${serverId}/backups/${backupId}/lock`, {}), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['backups', orgId, serverId] }), }); const backupList = data?.backups ?? []; const totalSize = backupList.reduce((sum, b) => sum + (b.sizeBytes ?? 0), 0); return (

Backups

{backupList.length} backup{backupList.length !== 1 ? 's' : ''} —{' '} {formatBytes(totalSize)} total

Create Backup setShowCreate(false)} />
{backupList.length === 0 ? (

No backups yet

Create a backup to save the current state of your server

) : (
{backupList.map((backup) => (
{backup.completedAt ? ( ) : ( )}

{backup.name}

{backup.isLocked && ( Locked )} {!backup.completedAt && ( In Progress )}
{formatBytes(backup.sizeBytes)} {new Date(backup.createdAt).toLocaleString()} {backup.checksum && ( {backup.checksum.slice(0, 12)}... )}
{backup.completedAt && ( <> {confirmRestore === backup.id ? (
Confirm?
) : ( )} )} {!backup.isLocked && ( )}
))}
)}
); } function CreateBackupForm({ orgId, serverId, onClose, }: { orgId: string; serverId: string; onClose: () => void; }) { const queryClient = useQueryClient(); const [name, setName] = useState(`backup-${new Date().toISOString().slice(0, 10)}`); const createMutation = useMutation({ mutationFn: (data: { name: string }) => api.post(`/organizations/${orgId}/servers/${serverId}/backups`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['backups', orgId, serverId] }); onClose(); }, }); return (
{ e.preventDefault(); createMutation.mutate({ name }); }} className="space-y-4" >
setName(e.target.value)} required />
); }