feat: Backup & Restore - Manual backup tested complete.
This commit is contained in:
@@ -24,9 +24,6 @@ import {
|
||||
UpdateCatalogTrimRequest,
|
||||
CreateCatalogEngineRequest,
|
||||
UpdateCatalogEngineRequest,
|
||||
StationOverview,
|
||||
CreateStationRequest,
|
||||
UpdateStationRequest,
|
||||
CatalogSearchResponse,
|
||||
ImportPreviewResult,
|
||||
ImportApplyResult,
|
||||
@@ -41,6 +38,15 @@ import {
|
||||
DeactivateUserRequest,
|
||||
UpdateUserProfileRequest,
|
||||
PromoteToAdminRequest,
|
||||
// Backup types
|
||||
BackupHistory,
|
||||
BackupSchedule,
|
||||
BackupSettings,
|
||||
ListBackupsResponse,
|
||||
CreateBackupRequest,
|
||||
CreateScheduleRequest,
|
||||
UpdateScheduleRequest,
|
||||
RestorePreviewResponse,
|
||||
} from '../types/admin.types';
|
||||
|
||||
export interface AuditLogsResponse {
|
||||
@@ -189,26 +195,6 @@ export const adminApi = {
|
||||
await apiClient.delete(`/admin/catalog/engines/${id}`);
|
||||
},
|
||||
|
||||
// Stations
|
||||
listStations: async (): Promise<StationOverview[]> => {
|
||||
const response = await apiClient.get<StationOverview[]>('/admin/stations');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
createStation: async (data: CreateStationRequest): Promise<StationOverview> => {
|
||||
const response = await apiClient.post<StationOverview>('/admin/stations', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateStation: async (id: string, data: UpdateStationRequest): Promise<StationOverview> => {
|
||||
const response = await apiClient.put<StationOverview>(`/admin/stations/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
deleteStation: async (id: string): Promise<void> => {
|
||||
await apiClient.delete(`/admin/stations/${id}`);
|
||||
},
|
||||
|
||||
// Catalog Search
|
||||
searchCatalog: async (
|
||||
query: string,
|
||||
@@ -368,4 +354,114 @@ export const adminApi = {
|
||||
return response.data;
|
||||
},
|
||||
},
|
||||
|
||||
// Backup & Restore
|
||||
backups: {
|
||||
// List backups with pagination
|
||||
list: async (params: { page?: number; pageSize?: number } = {}): Promise<ListBackupsResponse> => {
|
||||
const response = await apiClient.get<ListBackupsResponse>('/admin/backups', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get backup details
|
||||
get: async (id: string): Promise<BackupHistory> => {
|
||||
const response = await apiClient.get<BackupHistory>(`/admin/backups/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Create manual backup
|
||||
create: async (data: CreateBackupRequest = {}): Promise<BackupHistory> => {
|
||||
const response = await apiClient.post<BackupHistory>('/admin/backups', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Download backup file
|
||||
download: async (id: string): Promise<Blob> => {
|
||||
const response = await apiClient.get(`/admin/backups/${id}/download`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Delete backup
|
||||
delete: async (id: string): Promise<void> => {
|
||||
await apiClient.delete(`/admin/backups/${id}`);
|
||||
},
|
||||
|
||||
// Upload backup file
|
||||
upload: async (file: File): Promise<BackupHistory> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const response = await apiClient.post<BackupHistory>('/admin/backups/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Preview restore
|
||||
previewRestore: async (id: string): Promise<RestorePreviewResponse> => {
|
||||
const response = await apiClient.post<RestorePreviewResponse>(
|
||||
`/admin/backups/${id}/restore/preview`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Execute restore
|
||||
restore: async (id: string): Promise<{ message: string }> => {
|
||||
const response = await apiClient.post<{ message: string }>(
|
||||
`/admin/backups/${id}/restore`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Schedules
|
||||
schedules: {
|
||||
list: async (): Promise<BackupSchedule[]> => {
|
||||
const response = await apiClient.get<BackupSchedule[]>('/admin/backups/schedules');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
get: async (id: string): Promise<BackupSchedule> => {
|
||||
const response = await apiClient.get<BackupSchedule>(`/admin/backups/schedules/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: CreateScheduleRequest): Promise<BackupSchedule> => {
|
||||
const response = await apiClient.post<BackupSchedule>('/admin/backups/schedules', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string, data: UpdateScheduleRequest): Promise<BackupSchedule> => {
|
||||
const response = await apiClient.put<BackupSchedule>(
|
||||
`/admin/backups/schedules/${id}`,
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
delete: async (id: string): Promise<void> => {
|
||||
await apiClient.delete(`/admin/backups/schedules/${id}`);
|
||||
},
|
||||
|
||||
toggle: async (id: string): Promise<BackupSchedule> => {
|
||||
const response = await apiClient.patch<BackupSchedule>(
|
||||
`/admin/backups/schedules/${id}/toggle`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
},
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
get: async (): Promise<BackupSettings> => {
|
||||
const response = await apiClient.get<BackupSettings>('/admin/backups/settings');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (data: Partial<BackupSettings>): Promise<BackupSettings> => {
|
||||
const response = await apiClient.put<BackupSettings>('/admin/backups/settings', data);
|
||||
return response.data;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
239
frontend/src/features/admin/hooks/useBackups.ts
Normal file
239
frontend/src/features/admin/hooks/useBackups.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* @ai-summary React Query hooks for Backup & Restore admin feature
|
||||
* @ai-context Provides hooks for managing backups, schedules, and settings
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { adminApi } from '../api/admin.api';
|
||||
import {
|
||||
BackupSettings,
|
||||
CreateBackupRequest,
|
||||
CreateScheduleRequest,
|
||||
UpdateScheduleRequest,
|
||||
} from '../types/admin.types';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Query keys
|
||||
export const backupKeys = {
|
||||
all: ['admin', 'backups'] as const,
|
||||
lists: () => [...backupKeys.all, 'list'] as const,
|
||||
list: (params: { page?: number; pageSize?: number }) =>
|
||||
[...backupKeys.lists(), params] as const,
|
||||
details: () => [...backupKeys.all, 'detail'] as const,
|
||||
detail: (id: string) => [...backupKeys.details(), id] as const,
|
||||
schedules: () => [...backupKeys.all, 'schedules'] as const,
|
||||
settings: () => [...backupKeys.all, 'settings'] as const,
|
||||
};
|
||||
|
||||
// List backups with pagination
|
||||
export const useBackups = (params: { page?: number; pageSize?: number } = {}) => {
|
||||
return useQuery({
|
||||
queryKey: backupKeys.list(params),
|
||||
queryFn: () => adminApi.backups.list(params),
|
||||
});
|
||||
};
|
||||
|
||||
// Get backup details
|
||||
export const useBackupDetails = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: backupKeys.detail(id),
|
||||
queryFn: () => adminApi.backups.get(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
// List backup schedules
|
||||
export const useBackupSchedules = () => {
|
||||
return useQuery({
|
||||
queryKey: backupKeys.schedules(),
|
||||
queryFn: () => adminApi.backups.schedules.list(),
|
||||
});
|
||||
};
|
||||
|
||||
// Get backup settings
|
||||
export const useBackupSettings = () => {
|
||||
return useQuery({
|
||||
queryKey: backupKeys.settings(),
|
||||
queryFn: () => adminApi.backups.settings.get(),
|
||||
});
|
||||
};
|
||||
|
||||
// Create manual backup
|
||||
export const useCreateBackup = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateBackupRequest) => adminApi.backups.create(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.lists() });
|
||||
toast.success('Backup created successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to create backup');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Delete backup
|
||||
export const useDeleteBackup = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.backups.delete(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.lists() });
|
||||
toast.success('Backup deleted successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to delete backup');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Download backup
|
||||
export const useDownloadBackup = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, filename }: { id: string; filename: string }) => {
|
||||
const blob = await adminApi.backups.download(id);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Backup download started');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to download backup');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Upload backup
|
||||
export const useUploadBackup = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (file: File) => adminApi.backups.upload(file),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.lists() });
|
||||
toast.success('Backup uploaded successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to upload backup');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Preview restore
|
||||
export const useRestorePreview = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.backups.previewRestore(id),
|
||||
onError: () => {
|
||||
toast.error('Failed to generate restore preview');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Execute restore
|
||||
export const useExecuteRestore = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.backups.restore(id),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.all });
|
||||
toast.success(data.message || 'Restore completed successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to restore backup');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Create schedule
|
||||
export const useCreateSchedule = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateScheduleRequest) => adminApi.backups.schedules.create(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.schedules() });
|
||||
toast.success('Schedule created successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to create schedule');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Update schedule
|
||||
export const useUpdateSchedule = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: UpdateScheduleRequest }) =>
|
||||
adminApi.backups.schedules.update(id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.schedules() });
|
||||
toast.success('Schedule updated successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to update schedule');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Delete schedule
|
||||
export const useDeleteSchedule = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.backups.schedules.delete(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.schedules() });
|
||||
toast.success('Schedule deleted successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to delete schedule');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Toggle schedule enabled/disabled
|
||||
export const useToggleSchedule = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.backups.schedules.toggle(id),
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.schedules() });
|
||||
toast.success(
|
||||
data.isEnabled ? 'Schedule enabled successfully' : 'Schedule disabled successfully'
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to toggle schedule');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Update settings
|
||||
export const useUpdateSettings = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<BackupSettings>) => adminApi.backups.settings.update(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: backupKeys.settings() });
|
||||
toast.success('Settings updated successfully');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to update settings');
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* @ai-summary React Query hooks for station overview (admin)
|
||||
* @ai-context CRUD operations for global station management
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { adminApi } from '../api/admin.api';
|
||||
import { CreateStationRequest, UpdateStationRequest } from '../types/admin.types';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface ApiError {
|
||||
response?: {
|
||||
data?: {
|
||||
error?: string;
|
||||
};
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const useStationOverview = () => {
|
||||
const { isAuthenticated, isLoading } = useAuth0();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['adminStations'],
|
||||
queryFn: () => adminApi.listStations(),
|
||||
enabled: isAuthenticated && !isLoading,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcTime: 10 * 60 * 1000,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateStation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateStationRequest) => adminApi.createStation(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminStations'] });
|
||||
toast.success('Station created successfully');
|
||||
},
|
||||
onError: (error: ApiError) => {
|
||||
toast.error(error.response?.data?.error || 'Failed to create station');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateStation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: UpdateStationRequest }) =>
|
||||
adminApi.updateStation(id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminStations'] });
|
||||
toast.success('Station updated successfully');
|
||||
},
|
||||
onError: (error: ApiError) => {
|
||||
toast.error(error.response?.data?.error || 'Failed to update station');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteStation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => adminApi.deleteStation(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['adminStations'] });
|
||||
toast.success('Station deleted successfully');
|
||||
},
|
||||
onError: (error: ApiError) => {
|
||||
toast.error(error.response?.data?.error || 'Failed to delete station');
|
||||
},
|
||||
});
|
||||
};
|
||||
1048
frontend/src/features/admin/mobile/AdminBackupMobileScreen.tsx
Normal file
1048
frontend/src/features/admin/mobile/AdminBackupMobileScreen.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @ai-summary Mobile admin screen for gas station management
|
||||
* @ai-context CRUD operations for global station data with mobile UI
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
|
||||
import { MobileContainer } from '../../../shared-minimal/components/mobile/MobileContainer';
|
||||
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
|
||||
|
||||
export const AdminStationsMobileScreen: React.FC = () => {
|
||||
const { isAdmin, loading } = useAdminAccess();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<MobileContainer>
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<div className="text-slate-500 mb-2">Loading admin access...</div>
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
</MobileContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
return <Navigate to="/garage/settings" replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileContainer>
|
||||
<div className="space-y-4 pb-20 p-4">
|
||||
<div className="text-center mb-6">
|
||||
<h1 className="text-2xl font-bold text-slate-800">Station Management</h1>
|
||||
<p className="text-slate-500 mt-2">Manage gas station data</p>
|
||||
</div>
|
||||
|
||||
<GlassCard padding="md">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-4">Gas Stations</h2>
|
||||
<p className="text-sm text-slate-600 mb-4">
|
||||
Station management interface coming soon.
|
||||
</p>
|
||||
<div className="space-y-2 text-sm text-slate-600">
|
||||
<p className="font-semibold">Features:</p>
|
||||
<ul className="list-disc pl-5 space-y-1">
|
||||
<li>View all gas stations</li>
|
||||
<li>Create new stations</li>
|
||||
<li>Update station information</li>
|
||||
<li>Delete stations</li>
|
||||
<li>View station usage statistics</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</MobileContainer>
|
||||
);
|
||||
};
|
||||
@@ -125,34 +125,6 @@ export interface UpdateCatalogEngineRequest {
|
||||
fuelType?: string;
|
||||
}
|
||||
|
||||
// Station types for admin
|
||||
export interface StationOverview {
|
||||
id: string;
|
||||
name: string;
|
||||
placeId: string;
|
||||
address: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateStationRequest {
|
||||
name: string;
|
||||
placeId: string;
|
||||
address: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
export interface UpdateStationRequest {
|
||||
name?: string;
|
||||
address?: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
}
|
||||
|
||||
// Admin access verification
|
||||
export interface AdminAccessResponse {
|
||||
isAdmin: boolean;
|
||||
@@ -300,3 +272,92 @@ export interface UpdateUserProfileRequest {
|
||||
export interface PromoteToAdminRequest {
|
||||
role?: 'admin' | 'super_admin';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Backup & Restore types
|
||||
// ============================================
|
||||
|
||||
// Backup types
|
||||
export type BackupFrequency = 'hourly' | 'daily' | 'weekly' | 'monthly';
|
||||
export type BackupType = 'scheduled' | 'manual';
|
||||
export type BackupStatus = 'in_progress' | 'completed' | 'failed';
|
||||
|
||||
export interface BackupHistory {
|
||||
id: string;
|
||||
scheduleId: string | null;
|
||||
backupType: BackupType;
|
||||
filename: string;
|
||||
filePath: string;
|
||||
fileSizeBytes: number;
|
||||
databaseTablesCount: number | null;
|
||||
documentsCount: number | null;
|
||||
status: BackupStatus;
|
||||
errorMessage: string | null;
|
||||
startedAt: string;
|
||||
completedAt: string | null;
|
||||
createdBy: string | null;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface BackupSchedule {
|
||||
id: string;
|
||||
name: string;
|
||||
frequency: BackupFrequency;
|
||||
cronExpression: string;
|
||||
retentionCount: number;
|
||||
isEnabled: boolean;
|
||||
lastRunAt: string | null;
|
||||
nextRunAt: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastBackup?: BackupHistory | null;
|
||||
}
|
||||
|
||||
export interface BackupSettings {
|
||||
emailOnSuccess: boolean;
|
||||
emailOnFailure: boolean;
|
||||
adminEmail: string;
|
||||
maxBackupSizeMb: number;
|
||||
compressionEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ListBackupsResponse {
|
||||
items: BackupHistory[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface CreateBackupRequest {
|
||||
name?: string;
|
||||
includeDocuments?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateScheduleRequest {
|
||||
name: string;
|
||||
frequency: BackupFrequency;
|
||||
retentionCount?: number;
|
||||
isEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateScheduleRequest {
|
||||
name?: string;
|
||||
frequency?: BackupFrequency;
|
||||
retentionCount?: number;
|
||||
isEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface RestorePreviewResponse {
|
||||
backupId: string;
|
||||
manifest: {
|
||||
version: string;
|
||||
createdAt: string;
|
||||
contents: {
|
||||
database: { tablesCount: number; sizeBytes: number };
|
||||
documents: { totalFiles: number; totalSizeBytes: number };
|
||||
};
|
||||
};
|
||||
warnings: string[];
|
||||
estimatedDuration: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user