feat: naming backup schedules

This commit is contained in:
Nicolas Meienberger 2025-12-04 18:10:14 +01:00
parent c9ac2fda15
commit 2a92e26223
18 changed files with 1113 additions and 433 deletions

View file

@ -87,12 +87,10 @@ const createQueryKey = <TOptions extends Options>(id: string, options?: TOptions
if (options?.query) { if (options?.query) {
params.query = options.query; params.query = options.query;
} }
return [ return [params];
params
];
}; };
export const getMeQueryKey = (options?: Options<GetMeData>) => createQueryKey("getMe", options); export const getMeQueryKey = (options?: Options<GetMeData>) => createQueryKey('getMe', options);
/** /**
* Get current authenticated user * Get current authenticated user
@ -110,7 +108,7 @@ export const getMeOptions = (options?: Options<GetMeData>) => queryOptions<GetMe
queryKey: getMeQueryKey(options) queryKey: getMeQueryKey(options)
}); });
export const getStatusQueryKey = (options?: Options<GetStatusData>) => createQueryKey("getStatus", options); export const getStatusQueryKey = (options?: Options<GetStatusData>) => createQueryKey('getStatus', options);
/** /**
* Get authentication system status * Get authentication system status
@ -145,7 +143,7 @@ export const changePasswordMutation = (options?: Partial<Options<ChangePasswordD
return mutationOptions; return mutationOptions;
}; };
export const listVolumesQueryKey = (options?: Options<ListVolumesData>) => createQueryKey("listVolumes", options); export const listVolumesQueryKey = (options?: Options<ListVolumesData>) => createQueryKey('listVolumes', options);
/** /**
* List all volumes * List all volumes
@ -214,7 +212,7 @@ export const deleteVolumeMutation = (options?: Partial<Options<DeleteVolumeData>
return mutationOptions; return mutationOptions;
}; };
export const getVolumeQueryKey = (options: Options<GetVolumeData>) => createQueryKey("getVolume", options); export const getVolumeQueryKey = (options: Options<GetVolumeData>) => createQueryKey('getVolume', options);
/** /**
* Get a volume by name * Get a volume by name
@ -249,7 +247,7 @@ export const updateVolumeMutation = (options?: Partial<Options<UpdateVolumeData>
return mutationOptions; return mutationOptions;
}; };
export const getContainersUsingVolumeQueryKey = (options: Options<GetContainersUsingVolumeData>) => createQueryKey("getContainersUsingVolume", options); export const getContainersUsingVolumeQueryKey = (options: Options<GetContainersUsingVolumeData>) => createQueryKey('getContainersUsingVolume', options);
/** /**
* Get containers using a volume by name * Get containers using a volume by name
@ -318,7 +316,7 @@ export const healthCheckVolumeMutation = (options?: Partial<Options<HealthCheckV
return mutationOptions; return mutationOptions;
}; };
export const listFilesQueryKey = (options: Options<ListFilesData>) => createQueryKey("listFiles", options); export const listFilesQueryKey = (options: Options<ListFilesData>) => createQueryKey('listFiles', options);
/** /**
* List files in a volume directory * List files in a volume directory
@ -336,7 +334,7 @@ export const listFilesOptions = (options: Options<ListFilesData>) => queryOption
queryKey: listFilesQueryKey(options) queryKey: listFilesQueryKey(options)
}); });
export const browseFilesystemQueryKey = (options?: Options<BrowseFilesystemData>) => createQueryKey("browseFilesystem", options); export const browseFilesystemQueryKey = (options?: Options<BrowseFilesystemData>) => createQueryKey('browseFilesystem', options);
/** /**
* Browse directories on the host filesystem * Browse directories on the host filesystem
@ -354,7 +352,7 @@ export const browseFilesystemOptions = (options?: Options<BrowseFilesystemData>)
queryKey: browseFilesystemQueryKey(options) queryKey: browseFilesystemQueryKey(options)
}); });
export const listRepositoriesQueryKey = (options?: Options<ListRepositoriesData>) => createQueryKey("listRepositories", options); export const listRepositoriesQueryKey = (options?: Options<ListRepositoriesData>) => createQueryKey('listRepositories', options);
/** /**
* List all repositories * List all repositories
@ -389,7 +387,7 @@ export const createRepositoryMutation = (options?: Partial<Options<CreateReposit
return mutationOptions; return mutationOptions;
}; };
export const listRcloneRemotesQueryKey = (options?: Options<ListRcloneRemotesData>) => createQueryKey("listRcloneRemotes", options); export const listRcloneRemotesQueryKey = (options?: Options<ListRcloneRemotesData>) => createQueryKey('listRcloneRemotes', options);
/** /**
* List all configured rclone remotes on the host system * List all configured rclone remotes on the host system
@ -424,7 +422,7 @@ export const deleteRepositoryMutation = (options?: Partial<Options<DeleteReposit
return mutationOptions; return mutationOptions;
}; };
export const getRepositoryQueryKey = (options: Options<GetRepositoryData>) => createQueryKey("getRepository", options); export const getRepositoryQueryKey = (options: Options<GetRepositoryData>) => createQueryKey('getRepository', options);
/** /**
* Get a single repository by name * Get a single repository by name
@ -459,7 +457,7 @@ export const updateRepositoryMutation = (options?: Partial<Options<UpdateReposit
return mutationOptions; return mutationOptions;
}; };
export const listSnapshotsQueryKey = (options: Options<ListSnapshotsData>) => createQueryKey("listSnapshots", options); export const listSnapshotsQueryKey = (options: Options<ListSnapshotsData>) => createQueryKey('listSnapshots', options);
/** /**
* List all snapshots in a repository * List all snapshots in a repository
@ -494,7 +492,7 @@ export const deleteSnapshotMutation = (options?: Partial<Options<DeleteSnapshotD
return mutationOptions; return mutationOptions;
}; };
export const getSnapshotDetailsQueryKey = (options: Options<GetSnapshotDetailsData>) => createQueryKey("getSnapshotDetails", options); export const getSnapshotDetailsQueryKey = (options: Options<GetSnapshotDetailsData>) => createQueryKey('getSnapshotDetails', options);
/** /**
* Get details of a specific snapshot * Get details of a specific snapshot
@ -512,7 +510,7 @@ export const getSnapshotDetailsOptions = (options: Options<GetSnapshotDetailsDat
queryKey: getSnapshotDetailsQueryKey(options) queryKey: getSnapshotDetailsQueryKey(options)
}); });
export const listSnapshotFilesQueryKey = (options: Options<ListSnapshotFilesData>) => createQueryKey("listSnapshotFiles", options); export const listSnapshotFilesQueryKey = (options: Options<ListSnapshotFilesData>) => createQueryKey('listSnapshotFiles', options);
/** /**
* List files and directories in a snapshot * List files and directories in a snapshot
@ -564,7 +562,7 @@ export const doctorRepositoryMutation = (options?: Partial<Options<DoctorReposit
return mutationOptions; return mutationOptions;
}; };
export const listBackupSchedulesQueryKey = (options?: Options<ListBackupSchedulesData>) => createQueryKey("listBackupSchedules", options); export const listBackupSchedulesQueryKey = (options?: Options<ListBackupSchedulesData>) => createQueryKey('listBackupSchedules', options);
/** /**
* List all backup schedules * List all backup schedules
@ -616,7 +614,7 @@ export const deleteBackupScheduleMutation = (options?: Partial<Options<DeleteBac
return mutationOptions; return mutationOptions;
}; };
export const getBackupScheduleQueryKey = (options: Options<GetBackupScheduleData>) => createQueryKey("getBackupSchedule", options); export const getBackupScheduleQueryKey = (options: Options<GetBackupScheduleData>) => createQueryKey('getBackupSchedule', options);
/** /**
* Get a backup schedule by ID * Get a backup schedule by ID
@ -651,7 +649,7 @@ export const updateBackupScheduleMutation = (options?: Partial<Options<UpdateBac
return mutationOptions; return mutationOptions;
}; };
export const getBackupScheduleForVolumeQueryKey = (options: Options<GetBackupScheduleForVolumeData>) => createQueryKey("getBackupScheduleForVolume", options); export const getBackupScheduleForVolumeQueryKey = (options: Options<GetBackupScheduleForVolumeData>) => createQueryKey('getBackupScheduleForVolume', options);
/** /**
* Get a backup schedule for a specific volume * Get a backup schedule for a specific volume
@ -720,7 +718,7 @@ export const runForgetMutation = (options?: Partial<Options<RunForgetData>>): Us
return mutationOptions; return mutationOptions;
}; };
export const getScheduleNotificationsQueryKey = (options: Options<GetScheduleNotificationsData>) => createQueryKey("getScheduleNotifications", options); export const getScheduleNotificationsQueryKey = (options: Options<GetScheduleNotificationsData>) => createQueryKey('getScheduleNotifications', options);
/** /**
* Get notification assignments for a backup schedule * Get notification assignments for a backup schedule
@ -755,7 +753,7 @@ export const updateScheduleNotificationsMutation = (options?: Partial<Options<Up
return mutationOptions; return mutationOptions;
}; };
export const getScheduleMirrorsQueryKey = (options: Options<GetScheduleMirrorsData>) => createQueryKey("getScheduleMirrors", options); export const getScheduleMirrorsQueryKey = (options: Options<GetScheduleMirrorsData>) => createQueryKey('getScheduleMirrors', options);
/** /**
* Get mirror repository assignments for a backup schedule * Get mirror repository assignments for a backup schedule
@ -790,7 +788,7 @@ export const updateScheduleMirrorsMutation = (options?: Partial<Options<UpdateSc
return mutationOptions; return mutationOptions;
}; };
export const getMirrorCompatibilityQueryKey = (options: Options<GetMirrorCompatibilityData>) => createQueryKey("getMirrorCompatibility", options); export const getMirrorCompatibilityQueryKey = (options: Options<GetMirrorCompatibilityData>) => createQueryKey('getMirrorCompatibility', options);
/** /**
* Get mirror compatibility info for all repositories relative to a backup schedule's primary repository * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository
@ -808,7 +806,7 @@ export const getMirrorCompatibilityOptions = (options: Options<GetMirrorCompatib
queryKey: getMirrorCompatibilityQueryKey(options) queryKey: getMirrorCompatibilityQueryKey(options)
}); });
export const listNotificationDestinationsQueryKey = (options?: Options<ListNotificationDestinationsData>) => createQueryKey("listNotificationDestinations", options); export const listNotificationDestinationsQueryKey = (options?: Options<ListNotificationDestinationsData>) => createQueryKey('listNotificationDestinations', options);
/** /**
* List all notification destinations * List all notification destinations
@ -860,7 +858,7 @@ export const deleteNotificationDestinationMutation = (options?: Partial<Options<
return mutationOptions; return mutationOptions;
}; };
export const getNotificationDestinationQueryKey = (options: Options<GetNotificationDestinationData>) => createQueryKey("getNotificationDestination", options); export const getNotificationDestinationQueryKey = (options: Options<GetNotificationDestinationData>) => createQueryKey('getNotificationDestination', options);
/** /**
* Get a notification destination by ID * Get a notification destination by ID
@ -912,7 +910,7 @@ export const testNotificationDestinationMutation = (options?: Partial<Options<Te
return mutationOptions; return mutationOptions;
}; };
export const getSystemInfoQueryKey = (options?: Options<GetSystemInfoData>) => createQueryKey("getSystemInfo", options); export const getSystemInfoQueryKey = (options?: Options<GetSystemInfoData>) => createQueryKey('getSystemInfo', options);
/** /**
* Get system information including available capabilities * Get system information including available capabilities

View file

@ -13,6 +13,4 @@ import type { ClientOptions as ClientOptions2 } from './types.gen';
*/ */
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>; export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
export const client = createClient(createConfig<ClientOptions2>({ export const client = createClient(createConfig<ClientOptions2>({ baseUrl: 'http://192.168.2.42:4096' }));
baseUrl: 'http://192.168.2.42:4096'
}));

View file

@ -21,583 +21,371 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
/** /**
* Register a new user * Register a new user
*/ */
export const register = <ThrowOnError extends boolean = false>(options?: Options<RegisterData, ThrowOnError>) => { export const register = <ThrowOnError extends boolean = false>(options?: Options<RegisterData, ThrowOnError>) => (options?.client ?? client).post<RegisterResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<RegisterResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/register',
url: '/api/v1/auth/register', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Login with username and password * Login with username and password
*/ */
export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => { export const login = <ThrowOnError extends boolean = false>(options?: Options<LoginData, ThrowOnError>) => (options?.client ?? client).post<LoginResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<LoginResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/login',
url: '/api/v1/auth/login', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Logout current user * Logout current user
*/ */
export const logout = <ThrowOnError extends boolean = false>(options?: Options<LogoutData, ThrowOnError>) => { export const logout = <ThrowOnError extends boolean = false>(options?: Options<LogoutData, ThrowOnError>) => (options?.client ?? client).post<LogoutResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/logout', ...options });
return (options?.client ?? client).post<LogoutResponses, unknown, ThrowOnError>({
url: '/api/v1/auth/logout',
...options
});
};
/** /**
* Get current authenticated user * Get current authenticated user
*/ */
export const getMe = <ThrowOnError extends boolean = false>(options?: Options<GetMeData, ThrowOnError>) => { export const getMe = <ThrowOnError extends boolean = false>(options?: Options<GetMeData, ThrowOnError>) => (options?.client ?? client).get<GetMeResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/me', ...options });
return (options?.client ?? client).get<GetMeResponses, unknown, ThrowOnError>({
url: '/api/v1/auth/me',
...options
});
};
/** /**
* Get authentication system status * Get authentication system status
*/ */
export const getStatus = <ThrowOnError extends boolean = false>(options?: Options<GetStatusData, ThrowOnError>) => { export const getStatus = <ThrowOnError extends boolean = false>(options?: Options<GetStatusData, ThrowOnError>) => (options?.client ?? client).get<GetStatusResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/status', ...options });
return (options?.client ?? client).get<GetStatusResponses, unknown, ThrowOnError>({
url: '/api/v1/auth/status',
...options
});
};
/** /**
* Change current user password * Change current user password
*/ */
export const changePassword = <ThrowOnError extends boolean = false>(options?: Options<ChangePasswordData, ThrowOnError>) => { export const changePassword = <ThrowOnError extends boolean = false>(options?: Options<ChangePasswordData, ThrowOnError>) => (options?.client ?? client).post<ChangePasswordResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<ChangePasswordResponses, unknown, ThrowOnError>({ url: '/api/v1/auth/change-password',
url: '/api/v1/auth/change-password', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* List all volumes * List all volumes
*/ */
export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => { export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => (options?.client ?? client).get<ListVolumesResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes', ...options });
return (options?.client ?? client).get<ListVolumesResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes',
...options
});
};
/** /**
* Create a new volume * Create a new volume
*/ */
export const createVolume = <ThrowOnError extends boolean = false>(options?: Options<CreateVolumeData, ThrowOnError>) => { export const createVolume = <ThrowOnError extends boolean = false>(options?: Options<CreateVolumeData, ThrowOnError>) => (options?.client ?? client).post<CreateVolumeResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<CreateVolumeResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes',
url: '/api/v1/volumes', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Test connection to backend * Test connection to backend
*/ */
export const testConnection = <ThrowOnError extends boolean = false>(options?: Options<TestConnectionData, ThrowOnError>) => { export const testConnection = <ThrowOnError extends boolean = false>(options?: Options<TestConnectionData, ThrowOnError>) => (options?.client ?? client).post<TestConnectionResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<TestConnectionResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/test-connection',
url: '/api/v1/volumes/test-connection', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Delete a volume * Delete a volume
*/ */
export const deleteVolume = <ThrowOnError extends boolean = false>(options: Options<DeleteVolumeData, ThrowOnError>) => { export const deleteVolume = <ThrowOnError extends boolean = false>(options: Options<DeleteVolumeData, ThrowOnError>) => (options.client ?? client).delete<DeleteVolumeResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/{name}', ...options });
return (options.client ?? client).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes/{name}',
...options
});
};
/** /**
* Get a volume by name * Get a volume by name
*/ */
export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) => { export const getVolume = <ThrowOnError extends boolean = false>(options: Options<GetVolumeData, ThrowOnError>) => (options.client ?? client).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({ url: '/api/v1/volumes/{name}', ...options });
return (options.client ?? client).get<GetVolumeResponses, GetVolumeErrors, ThrowOnError>({
url: '/api/v1/volumes/{name}',
...options
});
};
/** /**
* Update a volume's configuration * Update a volume's configuration
*/ */
export const updateVolume = <ThrowOnError extends boolean = false>(options: Options<UpdateVolumeData, ThrowOnError>) => { export const updateVolume = <ThrowOnError extends boolean = false>(options: Options<UpdateVolumeData, ThrowOnError>) => (options.client ?? client).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({
return (options.client ?? client).put<UpdateVolumeResponses, UpdateVolumeErrors, ThrowOnError>({ url: '/api/v1/volumes/{name}',
url: '/api/v1/volumes/{name}', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Get containers using a volume by name * Get containers using a volume by name
*/ */
export const getContainersUsingVolume = <ThrowOnError extends boolean = false>(options: Options<GetContainersUsingVolumeData, ThrowOnError>) => { export const getContainersUsingVolume = <ThrowOnError extends boolean = false>(options: Options<GetContainersUsingVolumeData, ThrowOnError>) => (options.client ?? client).get<GetContainersUsingVolumeResponses, GetContainersUsingVolumeErrors, ThrowOnError>({ url: '/api/v1/volumes/{name}/containers', ...options });
return (options.client ?? client).get<GetContainersUsingVolumeResponses, GetContainersUsingVolumeErrors, ThrowOnError>({
url: '/api/v1/volumes/{name}/containers',
...options
});
};
/** /**
* Mount a volume * Mount a volume
*/ */
export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) => { export const mountVolume = <ThrowOnError extends boolean = false>(options: Options<MountVolumeData, ThrowOnError>) => (options.client ?? client).post<MountVolumeResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/{name}/mount', ...options });
return (options.client ?? client).post<MountVolumeResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes/{name}/mount',
...options
});
};
/** /**
* Unmount a volume * Unmount a volume
*/ */
export const unmountVolume = <ThrowOnError extends boolean = false>(options: Options<UnmountVolumeData, ThrowOnError>) => { export const unmountVolume = <ThrowOnError extends boolean = false>(options: Options<UnmountVolumeData, ThrowOnError>) => (options.client ?? client).post<UnmountVolumeResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/{name}/unmount', ...options });
return (options.client ?? client).post<UnmountVolumeResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes/{name}/unmount',
...options
});
};
/** /**
* Perform a health check on a volume * Perform a health check on a volume
*/ */
export const healthCheckVolume = <ThrowOnError extends boolean = false>(options: Options<HealthCheckVolumeData, ThrowOnError>) => { export const healthCheckVolume = <ThrowOnError extends boolean = false>(options: Options<HealthCheckVolumeData, ThrowOnError>) => (options.client ?? client).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({ url: '/api/v1/volumes/{name}/health-check', ...options });
return (options.client ?? client).post<HealthCheckVolumeResponses, HealthCheckVolumeErrors, ThrowOnError>({
url: '/api/v1/volumes/{name}/health-check',
...options
});
};
/** /**
* List files in a volume directory * List files in a volume directory
*/ */
export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) => { export const listFiles = <ThrowOnError extends boolean = false>(options: Options<ListFilesData, ThrowOnError>) => (options.client ?? client).get<ListFilesResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/{name}/files', ...options });
return (options.client ?? client).get<ListFilesResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes/{name}/files',
...options
});
};
/** /**
* Browse directories on the host filesystem * Browse directories on the host filesystem
*/ */
export const browseFilesystem = <ThrowOnError extends boolean = false>(options?: Options<BrowseFilesystemData, ThrowOnError>) => { export const browseFilesystem = <ThrowOnError extends boolean = false>(options?: Options<BrowseFilesystemData, ThrowOnError>) => (options?.client ?? client).get<BrowseFilesystemResponses, unknown, ThrowOnError>({ url: '/api/v1/volumes/filesystem/browse', ...options });
return (options?.client ?? client).get<BrowseFilesystemResponses, unknown, ThrowOnError>({
url: '/api/v1/volumes/filesystem/browse',
...options
});
};
/** /**
* List all repositories * List all repositories
*/ */
export const listRepositories = <ThrowOnError extends boolean = false>(options?: Options<ListRepositoriesData, ThrowOnError>) => { export const listRepositories = <ThrowOnError extends boolean = false>(options?: Options<ListRepositoriesData, ThrowOnError>) => (options?.client ?? client).get<ListRepositoriesResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories', ...options });
return (options?.client ?? client).get<ListRepositoriesResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories',
...options
});
};
/** /**
* Create a new restic repository * Create a new restic repository
*/ */
export const createRepository = <ThrowOnError extends boolean = false>(options?: Options<CreateRepositoryData, ThrowOnError>) => { export const createRepository = <ThrowOnError extends boolean = false>(options?: Options<CreateRepositoryData, ThrowOnError>) => (options?.client ?? client).post<CreateRepositoryResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<CreateRepositoryResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories',
url: '/api/v1/repositories', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* List all configured rclone remotes on the host system * List all configured rclone remotes on the host system
*/ */
export const listRcloneRemotes = <ThrowOnError extends boolean = false>(options?: Options<ListRcloneRemotesData, ThrowOnError>) => { export const listRcloneRemotes = <ThrowOnError extends boolean = false>(options?: Options<ListRcloneRemotesData, ThrowOnError>) => (options?.client ?? client).get<ListRcloneRemotesResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/rclone-remotes', ...options });
return (options?.client ?? client).get<ListRcloneRemotesResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/rclone-remotes',
...options
});
};
/** /**
* Delete a repository * Delete a repository
*/ */
export const deleteRepository = <ThrowOnError extends boolean = false>(options: Options<DeleteRepositoryData, ThrowOnError>) => { export const deleteRepository = <ThrowOnError extends boolean = false>(options: Options<DeleteRepositoryData, ThrowOnError>) => (options.client ?? client).delete<DeleteRepositoryResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}', ...options });
return (options.client ?? client).delete<DeleteRepositoryResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}',
...options
});
};
/** /**
* Get a single repository by name * Get a single repository by name
*/ */
export const getRepository = <ThrowOnError extends boolean = false>(options: Options<GetRepositoryData, ThrowOnError>) => { export const getRepository = <ThrowOnError extends boolean = false>(options: Options<GetRepositoryData, ThrowOnError>) => (options.client ?? client).get<GetRepositoryResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}', ...options });
return (options.client ?? client).get<GetRepositoryResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}',
...options
});
};
/** /**
* Update a repository's name or settings * Update a repository's name or settings
*/ */
export const updateRepository = <ThrowOnError extends boolean = false>(options: Options<UpdateRepositoryData, ThrowOnError>) => { export const updateRepository = <ThrowOnError extends boolean = false>(options: Options<UpdateRepositoryData, ThrowOnError>) => (options.client ?? client).patch<UpdateRepositoryResponses, UpdateRepositoryErrors, ThrowOnError>({
return (options.client ?? client).patch<UpdateRepositoryResponses, UpdateRepositoryErrors, ThrowOnError>({ url: '/api/v1/repositories/{name}',
url: '/api/v1/repositories/{name}', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* List all snapshots in a repository * List all snapshots in a repository
*/ */
export const listSnapshots = <ThrowOnError extends boolean = false>(options: Options<ListSnapshotsData, ThrowOnError>) => { export const listSnapshots = <ThrowOnError extends boolean = false>(options: Options<ListSnapshotsData, ThrowOnError>) => (options.client ?? client).get<ListSnapshotsResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/snapshots', ...options });
return (options.client ?? client).get<ListSnapshotsResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}/snapshots',
...options
});
};
/** /**
* Delete a specific snapshot from a repository * Delete a specific snapshot from a repository
*/ */
export const deleteSnapshot = <ThrowOnError extends boolean = false>(options: Options<DeleteSnapshotData, ThrowOnError>) => { export const deleteSnapshot = <ThrowOnError extends boolean = false>(options: Options<DeleteSnapshotData, ThrowOnError>) => (options.client ?? client).delete<DeleteSnapshotResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/snapshots/{snapshotId}', ...options });
return (options.client ?? client).delete<DeleteSnapshotResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}/snapshots/{snapshotId}',
...options
});
};
/** /**
* Get details of a specific snapshot * Get details of a specific snapshot
*/ */
export const getSnapshotDetails = <ThrowOnError extends boolean = false>(options: Options<GetSnapshotDetailsData, ThrowOnError>) => { export const getSnapshotDetails = <ThrowOnError extends boolean = false>(options: Options<GetSnapshotDetailsData, ThrowOnError>) => (options.client ?? client).get<GetSnapshotDetailsResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/snapshots/{snapshotId}', ...options });
return (options.client ?? client).get<GetSnapshotDetailsResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}/snapshots/{snapshotId}',
...options
});
};
/** /**
* List files and directories in a snapshot * List files and directories in a snapshot
*/ */
export const listSnapshotFiles = <ThrowOnError extends boolean = false>(options: Options<ListSnapshotFilesData, ThrowOnError>) => { export const listSnapshotFiles = <ThrowOnError extends boolean = false>(options: Options<ListSnapshotFilesData, ThrowOnError>) => (options.client ?? client).get<ListSnapshotFilesResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/snapshots/{snapshotId}/files', ...options });
return (options.client ?? client).get<ListSnapshotFilesResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}/snapshots/{snapshotId}/files',
...options
});
};
/** /**
* Restore a snapshot to a target path on the filesystem * Restore a snapshot to a target path on the filesystem
*/ */
export const restoreSnapshot = <ThrowOnError extends boolean = false>(options: Options<RestoreSnapshotData, ThrowOnError>) => { export const restoreSnapshot = <ThrowOnError extends boolean = false>(options: Options<RestoreSnapshotData, ThrowOnError>) => (options.client ?? client).post<RestoreSnapshotResponses, unknown, ThrowOnError>({
return (options.client ?? client).post<RestoreSnapshotResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/restore',
url: '/api/v1/repositories/{name}/restore', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Run doctor operations on a repository to fix common issues (unlock, check, repair index). Use this when the repository is locked or has errors. * Run doctor operations on a repository to fix common issues (unlock, check, repair index). Use this when the repository is locked or has errors.
*/ */
export const doctorRepository = <ThrowOnError extends boolean = false>(options: Options<DoctorRepositoryData, ThrowOnError>) => { export const doctorRepository = <ThrowOnError extends boolean = false>(options: Options<DoctorRepositoryData, ThrowOnError>) => (options.client ?? client).post<DoctorRepositoryResponses, unknown, ThrowOnError>({ url: '/api/v1/repositories/{name}/doctor', ...options });
return (options.client ?? client).post<DoctorRepositoryResponses, unknown, ThrowOnError>({
url: '/api/v1/repositories/{name}/doctor',
...options
});
};
/** /**
* List all backup schedules * List all backup schedules
*/ */
export const listBackupSchedules = <ThrowOnError extends boolean = false>(options?: Options<ListBackupSchedulesData, ThrowOnError>) => { export const listBackupSchedules = <ThrowOnError extends boolean = false>(options?: Options<ListBackupSchedulesData, ThrowOnError>) => (options?.client ?? client).get<ListBackupSchedulesResponses, unknown, ThrowOnError>({ url: '/api/v1/backups', ...options });
return (options?.client ?? client).get<ListBackupSchedulesResponses, unknown, ThrowOnError>({
url: '/api/v1/backups',
...options
});
};
/** /**
* Create a new backup schedule for a volume * Create a new backup schedule for a volume
*/ */
export const createBackupSchedule = <ThrowOnError extends boolean = false>(options?: Options<CreateBackupScheduleData, ThrowOnError>) => { export const createBackupSchedule = <ThrowOnError extends boolean = false>(options?: Options<CreateBackupScheduleData, ThrowOnError>) => (options?.client ?? client).post<CreateBackupScheduleResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<CreateBackupScheduleResponses, unknown, ThrowOnError>({ url: '/api/v1/backups',
url: '/api/v1/backups', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Delete a backup schedule * Delete a backup schedule
*/ */
export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<DeleteBackupScheduleData, ThrowOnError>) => { export const deleteBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<DeleteBackupScheduleData, ThrowOnError>) => (options.client ?? client).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}', ...options });
return (options.client ?? client).delete<DeleteBackupScheduleResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}',
...options
});
};
/** /**
* Get a backup schedule by ID * Get a backup schedule by ID
*/ */
export const getBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<GetBackupScheduleData, ThrowOnError>) => { export const getBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<GetBackupScheduleData, ThrowOnError>) => (options.client ?? client).get<GetBackupScheduleResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}', ...options });
return (options.client ?? client).get<GetBackupScheduleResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}',
...options
});
};
/** /**
* Update a backup schedule * Update a backup schedule
*/ */
export const updateBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<UpdateBackupScheduleData, ThrowOnError>) => { export const updateBackupSchedule = <ThrowOnError extends boolean = false>(options: Options<UpdateBackupScheduleData, ThrowOnError>) => (options.client ?? client).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({
return (options.client ?? client).patch<UpdateBackupScheduleResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}',
url: '/api/v1/backups/{scheduleId}', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Get a backup schedule for a specific volume * Get a backup schedule for a specific volume
*/ */
export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>(options: Options<GetBackupScheduleForVolumeData, ThrowOnError>) => { export const getBackupScheduleForVolume = <ThrowOnError extends boolean = false>(options: Options<GetBackupScheduleForVolumeData, ThrowOnError>) => (options.client ?? client).get<GetBackupScheduleForVolumeResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/volume/{volumeId}', ...options });
return (options.client ?? client).get<GetBackupScheduleForVolumeResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/volume/{volumeId}',
...options
});
};
/** /**
* Trigger a backup immediately for a schedule * Trigger a backup immediately for a schedule
*/ */
export const runBackupNow = <ThrowOnError extends boolean = false>(options: Options<RunBackupNowData, ThrowOnError>) => { export const runBackupNow = <ThrowOnError extends boolean = false>(options: Options<RunBackupNowData, ThrowOnError>) => (options.client ?? client).post<RunBackupNowResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/run', ...options });
return (options.client ?? client).post<RunBackupNowResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/run',
...options
});
};
/** /**
* Stop a backup that is currently in progress * Stop a backup that is currently in progress
*/ */
export const stopBackup = <ThrowOnError extends boolean = false>(options: Options<StopBackupData, ThrowOnError>) => { export const stopBackup = <ThrowOnError extends boolean = false>(options: Options<StopBackupData, ThrowOnError>) => (options.client ?? client).post<StopBackupResponses, StopBackupErrors, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/stop', ...options });
return (options.client ?? client).post<StopBackupResponses, StopBackupErrors, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/stop',
...options
});
};
/** /**
* Manually apply retention policy to clean up old snapshots * Manually apply retention policy to clean up old snapshots
*/ */
export const runForget = <ThrowOnError extends boolean = false>(options: Options<RunForgetData, ThrowOnError>) => { export const runForget = <ThrowOnError extends boolean = false>(options: Options<RunForgetData, ThrowOnError>) => (options.client ?? client).post<RunForgetResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/forget', ...options });
return (options.client ?? client).post<RunForgetResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/forget',
...options
});
};
/** /**
* Get notification assignments for a backup schedule * Get notification assignments for a backup schedule
*/ */
export const getScheduleNotifications = <ThrowOnError extends boolean = false>(options: Options<GetScheduleNotificationsData, ThrowOnError>) => { export const getScheduleNotifications = <ThrowOnError extends boolean = false>(options: Options<GetScheduleNotificationsData, ThrowOnError>) => (options.client ?? client).get<GetScheduleNotificationsResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/notifications', ...options });
return (options.client ?? client).get<GetScheduleNotificationsResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/notifications',
...options
});
};
/** /**
* Update notification assignments for a backup schedule * Update notification assignments for a backup schedule
*/ */
export const updateScheduleNotifications = <ThrowOnError extends boolean = false>(options: Options<UpdateScheduleNotificationsData, ThrowOnError>) => { export const updateScheduleNotifications = <ThrowOnError extends boolean = false>(options: Options<UpdateScheduleNotificationsData, ThrowOnError>) => (options.client ?? client).put<UpdateScheduleNotificationsResponses, unknown, ThrowOnError>({
return (options.client ?? client).put<UpdateScheduleNotificationsResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/notifications',
url: '/api/v1/backups/{scheduleId}/notifications', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Get mirror repository assignments for a backup schedule * Get mirror repository assignments for a backup schedule
*/ */
export const getScheduleMirrors = <ThrowOnError extends boolean = false>(options: Options<GetScheduleMirrorsData, ThrowOnError>) => { export const getScheduleMirrors = <ThrowOnError extends boolean = false>(options: Options<GetScheduleMirrorsData, ThrowOnError>) => (options.client ?? client).get<GetScheduleMirrorsResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/mirrors', ...options });
return (options.client ?? client).get<GetScheduleMirrorsResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/mirrors',
...options
});
};
/** /**
* Update mirror repository assignments for a backup schedule * Update mirror repository assignments for a backup schedule
*/ */
export const updateScheduleMirrors = <ThrowOnError extends boolean = false>(options: Options<UpdateScheduleMirrorsData, ThrowOnError>) => { export const updateScheduleMirrors = <ThrowOnError extends boolean = false>(options: Options<UpdateScheduleMirrorsData, ThrowOnError>) => (options.client ?? client).put<UpdateScheduleMirrorsResponses, unknown, ThrowOnError>({
return (options.client ?? client).put<UpdateScheduleMirrorsResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/mirrors',
url: '/api/v1/backups/{scheduleId}/mirrors', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Get mirror compatibility info for all repositories relative to a backup schedule's primary repository * Get mirror compatibility info for all repositories relative to a backup schedule's primary repository
*/ */
export const getMirrorCompatibility = <ThrowOnError extends boolean = false>(options: Options<GetMirrorCompatibilityData, ThrowOnError>) => { export const getMirrorCompatibility = <ThrowOnError extends boolean = false>(options: Options<GetMirrorCompatibilityData, ThrowOnError>) => (options.client ?? client).get<GetMirrorCompatibilityResponses, unknown, ThrowOnError>({ url: '/api/v1/backups/{scheduleId}/mirrors/compatibility', ...options });
return (options.client ?? client).get<GetMirrorCompatibilityResponses, unknown, ThrowOnError>({
url: '/api/v1/backups/{scheduleId}/mirrors/compatibility',
...options
});
};
/** /**
* List all notification destinations * List all notification destinations
*/ */
export const listNotificationDestinations = <ThrowOnError extends boolean = false>(options?: Options<ListNotificationDestinationsData, ThrowOnError>) => { export const listNotificationDestinations = <ThrowOnError extends boolean = false>(options?: Options<ListNotificationDestinationsData, ThrowOnError>) => (options?.client ?? client).get<ListNotificationDestinationsResponses, unknown, ThrowOnError>({ url: '/api/v1/notifications/destinations', ...options });
return (options?.client ?? client).get<ListNotificationDestinationsResponses, unknown, ThrowOnError>({
url: '/api/v1/notifications/destinations',
...options
});
};
/** /**
* Create a new notification destination * Create a new notification destination
*/ */
export const createNotificationDestination = <ThrowOnError extends boolean = false>(options?: Options<CreateNotificationDestinationData, ThrowOnError>) => { export const createNotificationDestination = <ThrowOnError extends boolean = false>(options?: Options<CreateNotificationDestinationData, ThrowOnError>) => (options?.client ?? client).post<CreateNotificationDestinationResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<CreateNotificationDestinationResponses, unknown, ThrowOnError>({ url: '/api/v1/notifications/destinations',
url: '/api/v1/notifications/destinations', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};
/** /**
* Delete a notification destination * Delete a notification destination
*/ */
export const deleteNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<DeleteNotificationDestinationData, ThrowOnError>) => { export const deleteNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<DeleteNotificationDestinationData, ThrowOnError>) => (options.client ?? client).delete<DeleteNotificationDestinationResponses, DeleteNotificationDestinationErrors, ThrowOnError>({ url: '/api/v1/notifications/destinations/{id}', ...options });
return (options.client ?? client).delete<DeleteNotificationDestinationResponses, DeleteNotificationDestinationErrors, ThrowOnError>({
url: '/api/v1/notifications/destinations/{id}',
...options
});
};
/** /**
* Get a notification destination by ID * Get a notification destination by ID
*/ */
export const getNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<GetNotificationDestinationData, ThrowOnError>) => { export const getNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<GetNotificationDestinationData, ThrowOnError>) => (options.client ?? client).get<GetNotificationDestinationResponses, GetNotificationDestinationErrors, ThrowOnError>({ url: '/api/v1/notifications/destinations/{id}', ...options });
return (options.client ?? client).get<GetNotificationDestinationResponses, GetNotificationDestinationErrors, ThrowOnError>({
url: '/api/v1/notifications/destinations/{id}',
...options
});
};
/** /**
* Update a notification destination * Update a notification destination
*/ */
export const updateNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<UpdateNotificationDestinationData, ThrowOnError>) => { export const updateNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<UpdateNotificationDestinationData, ThrowOnError>) => (options.client ?? client).patch<UpdateNotificationDestinationResponses, UpdateNotificationDestinationErrors, ThrowOnError>({
return (options.client ?? client).patch<UpdateNotificationDestinationResponses, UpdateNotificationDestinationErrors, ThrowOnError>({ url: '/api/v1/notifications/destinations/{id}',
url: '/api/v1/notifications/destinations/{id}', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options.headers
...options.headers }
} });
});
};
/** /**
* Test a notification destination by sending a test message * Test a notification destination by sending a test message
*/ */
export const testNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<TestNotificationDestinationData, ThrowOnError>) => { export const testNotificationDestination = <ThrowOnError extends boolean = false>(options: Options<TestNotificationDestinationData, ThrowOnError>) => (options.client ?? client).post<TestNotificationDestinationResponses, TestNotificationDestinationErrors, ThrowOnError>({ url: '/api/v1/notifications/destinations/{id}/test', ...options });
return (options.client ?? client).post<TestNotificationDestinationResponses, TestNotificationDestinationErrors, ThrowOnError>({
url: '/api/v1/notifications/destinations/{id}/test',
...options
});
};
/** /**
* Get system information including available capabilities * Get system information including available capabilities
*/ */
export const getSystemInfo = <ThrowOnError extends boolean = false>(options?: Options<GetSystemInfoData, ThrowOnError>) => { export const getSystemInfo = <ThrowOnError extends boolean = false>(options?: Options<GetSystemInfoData, ThrowOnError>) => (options?.client ?? client).get<GetSystemInfoResponses, unknown, ThrowOnError>({ url: '/api/v1/system/info', ...options });
return (options?.client ?? client).get<GetSystemInfoResponses, unknown, ThrowOnError>({
url: '/api/v1/system/info',
...options
});
};
/** /**
* Download the Restic password file for backup recovery. Requires password re-authentication. * Download the Restic password file for backup recovery. Requires password re-authentication.
*/ */
export const downloadResticPassword = <ThrowOnError extends boolean = false>(options?: Options<DownloadResticPasswordData, ThrowOnError>) => { export const downloadResticPassword = <ThrowOnError extends boolean = false>(options?: Options<DownloadResticPasswordData, ThrowOnError>) => (options?.client ?? client).post<DownloadResticPasswordResponses, unknown, ThrowOnError>({
return (options?.client ?? client).post<DownloadResticPasswordResponses, unknown, ThrowOnError>({ url: '/api/v1/system/restic-password',
url: '/api/v1/system/restic-password', ...options,
...options, headers: {
headers: { 'Content-Type': 'application/json',
'Content-Type': 'application/json', ...options?.headers
...options?.headers }
} });
});
};

View file

@ -1297,6 +1297,7 @@ export type ListBackupSchedulesResponses = {
lastBackupAt: number | null; lastBackupAt: number | null;
lastBackupError: string | null; lastBackupError: string | null;
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
name: string;
nextBackupAt: number | null; nextBackupAt: number | null;
repository: { repository: {
compressionMode: 'auto' | 'max' | 'off' | null; compressionMode: 'auto' | 'max' | 'off' | null;
@ -1435,6 +1436,7 @@ export type CreateBackupScheduleData = {
body?: { body?: {
cronExpression: string; cronExpression: string;
enabled: boolean; enabled: boolean;
name: string;
repositoryId: string; repositoryId: string;
volumeId: number; volumeId: number;
excludePatterns?: Array<string>; excludePatterns?: Array<string>;
@ -1469,6 +1471,7 @@ export type CreateBackupScheduleResponses = {
lastBackupAt: number | null; lastBackupAt: number | null;
lastBackupError: string | null; lastBackupError: string | null;
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
name: string;
nextBackupAt: number | null; nextBackupAt: number | null;
repositoryId: string; repositoryId: string;
retentionPolicy: { retentionPolicy: {
@ -1530,6 +1533,7 @@ export type GetBackupScheduleResponses = {
lastBackupAt: number | null; lastBackupAt: number | null;
lastBackupError: string | null; lastBackupError: string | null;
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
name: string;
nextBackupAt: number | null; nextBackupAt: number | null;
repository: { repository: {
compressionMode: 'auto' | 'max' | 'off' | null; compressionMode: 'auto' | 'max' | 'off' | null;
@ -1671,6 +1675,7 @@ export type UpdateBackupScheduleData = {
enabled?: boolean; enabled?: boolean;
excludePatterns?: Array<string>; excludePatterns?: Array<string>;
includePatterns?: Array<string>; includePatterns?: Array<string>;
name?: string;
retentionPolicy?: { retentionPolicy?: {
keepDaily?: number; keepDaily?: number;
keepHourly?: number; keepHourly?: number;
@ -1703,6 +1708,7 @@ export type UpdateBackupScheduleResponses = {
lastBackupAt: number | null; lastBackupAt: number | null;
lastBackupError: string | null; lastBackupError: string | null;
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
name: string;
nextBackupAt: number | null; nextBackupAt: number | null;
repositoryId: string; repositoryId: string;
retentionPolicy: { retentionPolicy: {
@ -1744,6 +1750,7 @@ export type GetBackupScheduleForVolumeResponses = {
lastBackupAt: number | null; lastBackupAt: number | null;
lastBackupError: string | null; lastBackupError: string | null;
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null; lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
name: string;
nextBackupAt: number | null; nextBackupAt: number | null;
repository: { repository: {
compressionMode: 'auto' | 'max' | 'off' | null; compressionMode: 'auto' | 'max' | 'off' | null;

View file

@ -23,6 +23,7 @@ import type { BackupSchedule, Volume } from "~/client/lib/types";
import { deepClean } from "~/utils/object"; import { deepClean } from "~/utils/object";
const internalFormSchema = type({ const internalFormSchema = type({
name: "1 <= string <= 32",
repositoryId: "string", repositoryId: "string",
excludePatternsText: "string?", excludePatternsText: "string?",
includePatterns: "string[]?", includePatterns: "string[]?",
@ -80,6 +81,7 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined; const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined;
return { return {
name: schedule.name,
repositoryId: schedule.repositoryId, repositoryId: schedule.repositoryId,
frequency, frequency,
dailyTime, dailyTime,
@ -148,6 +150,21 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="grid gap-6 md:grid-cols-2"> <CardContent className="grid gap-6 md:grid-cols-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="md:col-span-2">
<FormLabel>Backup name</FormLabel>
<FormControl>
<Input placeholder="My backup" {...field} />
</FormControl>
<FormDescription>A unique name to identify this backup schedule.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="repositoryId" name="repositoryId"

View file

@ -1,4 +1,4 @@
import { Eraser, Pencil, Play, Square, Trash2 } from "lucide-react"; import { Database, Eraser, HardDrive, Pencil, Play, Square, Trash2 } from "lucide-react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { OnOff } from "~/client/components/onoff"; import { OnOff } from "~/client/components/onoff";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
@ -18,6 +18,7 @@ import { runForgetMutation } from "~/client/api-client/@tanstack/react-query.gen
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { parseError } from "~/client/lib/errors"; import { parseError } from "~/client/lib/errors";
import { Link } from "react-router";
type Props = { type Props = {
schedule: BackupSchedule; schedule: BackupSchedule;
@ -82,10 +83,17 @@ export const ScheduleSummary = (props: Props) => {
<CardHeader className="space-y-4"> <CardHeader className="space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div> <div>
<CardTitle>Backup schedule</CardTitle> <CardTitle>{schedule.name}</CardTitle>
<CardDescription> <CardDescription className="mt-1">
Automated backup configuration for volume&nbsp; <Link to={`/volumes/${schedule.volume.name}`} className="hover:underline">
<strong className="text-strong-accent">{schedule.volume.name}</strong> <HardDrive className="inline h-4 w-4 mr-2" />
<span>{schedule.volume.name}</span>
</Link>
<span className="mx-2"></span>
<Link to={`/repositories/${schedule.repository.name}`} className="hover:underline">
<Database className="inline h-4 w-4 mr-2 text-strong-accent" />
<span className="text-strong-accent">{schedule.repository.name}</span>
</Link>
</CardDescription> </CardDescription>
</div> </div>
<div className="flex items-center gap-2 justify-between sm:justify-start"> <div className="flex items-center gap-2 justify-between sm:justify-start">

View file

@ -35,10 +35,10 @@ import { ScheduleMirrorsConfig } from "../components/schedule-mirrors-config";
import { cn } from "~/client/lib/utils"; import { cn } from "~/client/lib/utils";
export const handle = { export const handle = {
breadcrumb: (match: Route.MetaArgs) => [ breadcrumb: (match: Route.MetaArgs) => {
{ label: "Backups", href: "/backups" }, const data = match.loaderData;
{ label: `Schedule #${match.params.id}` }, return [{ label: "Backups", href: "/backups" }, { label: data.schedule.name }];
], },
}; };
export function meta(_: Route.MetaArgs) { export function meta(_: Route.MetaArgs) {
@ -153,6 +153,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
updateSchedule.mutate({ updateSchedule.mutate({
path: { scheduleId: schedule.id.toString() }, path: { scheduleId: schedule.id.toString() },
body: { body: {
name: formValues.name,
repositoryId: formValues.repositoryId, repositoryId: formValues.repositoryId,
enabled: schedule.enabled, enabled: schedule.enabled,
cronExpression, cronExpression,

View file

@ -67,13 +67,11 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
{schedules.map((schedule) => ( {schedules.map((schedule) => (
<Link key={schedule.id} to={`/backups/${schedule.id}`}> <Link key={schedule.id} to={`/backups/${schedule.id}`}>
<Card key={schedule.id} className="flex flex-col h-full"> <Card key={schedule.id} className="flex flex-col h-full">
<CardHeader className="pb-3"> <CardHeader className="pb-3 overflow-hidden">
<div className="flex items-start justify-between gap-2"> <div className="flex items-center justify-between gap-2 w-full">
<div className="flex items-center gap-2 flex-1 min-w-0"> <div className="flex items-center gap-2 flex-1 min-w-0 w-0">
<HardDrive className="h-5 w-5 text-muted-foreground shrink-0" /> <CalendarClock className="h-5 w-5 text-muted-foreground shrink-0" />
<CardTitle className="text-lg truncate"> <CardTitle className="text-lg truncate">{schedule.name}</CardTitle>
Volume <span className="text-strong-accent">{schedule.volume.name}</span>
</CardTitle>
</div> </div>
<BackupStatusDot <BackupStatusDot
enabled={schedule.enabled} enabled={schedule.enabled}
@ -81,9 +79,12 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
isInProgress={schedule.lastBackupStatus === "in_progress"} isInProgress={schedule.lastBackupStatus === "in_progress"}
/> />
</div> </div>
<CardDescription className="flex items-center gap-2 mt-2"> <CardDescription className="ml-0.5 flex items-center gap-2 mt-2 text-xs">
<Database className="h-4 w-4" /> <HardDrive className="h-3.5 w-3.5" />
<span className="truncate">{schedule.repository.name}</span> <span className="truncate">{schedule.volume.name}</span>
<span className="text-muted-foreground"></span>
<Database className="h-3.5 w-3.5 text-strong-accent" />
<span className="truncate text-strong-accent">{schedule.repository.name}</span>
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="flex-1 space-y-4"> <CardContent className="flex-1 space-y-4">

View file

@ -83,6 +83,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
createSchedule.mutate({ createSchedule.mutate({
body: { body: {
name: formValues.name,
volumeId: selectedVolumeId, volumeId: selectedVolumeId,
repositoryId: formValues.repositoryId, repositoryId: formValues.repositoryId,
enabled: true, enabled: true,

View file

@ -0,0 +1,24 @@
PRAGMA foreign_keys=OFF;--> statement-breakpoint
CREATE TABLE `__new_backup_schedules_table` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`volume_id` integer NOT NULL REFERENCES `volumes_table`(`id`) ON DELETE CASCADE,
`repository_id` text NOT NULL REFERENCES `repositories_table`(`id`) ON DELETE CASCADE,
`enabled` integer DEFAULT true NOT NULL,
`cron_expression` text NOT NULL,
`retention_policy` text,
`exclude_patterns` text DEFAULT '[]',
`include_patterns` text DEFAULT '[]',
`last_backup_at` integer,
`last_backup_status` text,
`last_backup_error` text,
`next_backup_at` integer,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL
);--> statement-breakpoint
INSERT INTO `__new_backup_schedules_table`(`id`, `name`, `volume_id`, `repository_id`, `enabled`, `cron_expression`, `retention_policy`, `exclude_patterns`, `include_patterns`, `last_backup_at`, `last_backup_status`, `last_backup_error`, `next_backup_at`, `created_at`, `updated_at`)
SELECT `id`, lower(hex(randomblob(3))), `volume_id`, `repository_id`, `enabled`, `cron_expression`, `retention_policy`, `exclude_patterns`, `include_patterns`, `last_backup_at`, `last_backup_status`, `last_backup_error`, `next_backup_at`, `created_at`, `updated_at` FROM `backup_schedules_table`;--> statement-breakpoint
DROP TABLE `backup_schedules_table`;--> statement-breakpoint
ALTER TABLE `__new_backup_schedules_table` RENAME TO `backup_schedules_table`;--> statement-breakpoint
PRAGMA foreign_keys=ON;--> statement-breakpoint
CREATE UNIQUE INDEX `backup_schedules_table_name_unique` ON `backup_schedules_table` (`name`);

View file

@ -0,0 +1,807 @@
{
"version": "6",
"dialect": "sqlite",
"id": "b5b3acff-51d7-45ae-b9d2-4b07a6286fc3",
"prevId": "d5a60aea-4490-423e-8725-6ace87a76c9b",
"tables": {
"app_metadata": {
"name": "app_metadata",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"backup_schedule_mirrors_table": {
"name": "backup_schedule_mirrors_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"schedule_id": {
"name": "schedule_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"repository_id": {
"name": "repository_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"last_copy_at": {
"name": "last_copy_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_copy_status": {
"name": "last_copy_status",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_copy_error": {
"name": "last_copy_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {
"backup_schedule_mirrors_table_schedule_id_repository_id_unique": {
"name": "backup_schedule_mirrors_table_schedule_id_repository_id_unique",
"columns": [
"schedule_id",
"repository_id"
],
"isUnique": true
}
},
"foreignKeys": {
"backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk": {
"name": "backup_schedule_mirrors_table_schedule_id_backup_schedules_table_id_fk",
"tableFrom": "backup_schedule_mirrors_table",
"tableTo": "backup_schedules_table",
"columnsFrom": [
"schedule_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"backup_schedule_mirrors_table_repository_id_repositories_table_id_fk": {
"name": "backup_schedule_mirrors_table_repository_id_repositories_table_id_fk",
"tableFrom": "backup_schedule_mirrors_table",
"tableTo": "repositories_table",
"columnsFrom": [
"repository_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"backup_schedule_notifications_table": {
"name": "backup_schedule_notifications_table",
"columns": {
"schedule_id": {
"name": "schedule_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"destination_id": {
"name": "destination_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"notify_on_start": {
"name": "notify_on_start",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"notify_on_success": {
"name": "notify_on_success",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"notify_on_failure": {
"name": "notify_on_failure",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {},
"foreignKeys": {
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
"tableFrom": "backup_schedule_notifications_table",
"tableTo": "backup_schedules_table",
"columnsFrom": [
"schedule_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
"tableFrom": "backup_schedule_notifications_table",
"tableTo": "notification_destinations_table",
"columnsFrom": [
"destination_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
"columns": [
"schedule_id",
"destination_id"
],
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"backup_schedules_table": {
"name": "backup_schedules_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"volume_id": {
"name": "volume_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"repository_id": {
"name": "repository_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"cron_expression": {
"name": "cron_expression",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"retention_policy": {
"name": "retention_policy",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"exclude_patterns": {
"name": "exclude_patterns",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'[]'"
},
"include_patterns": {
"name": "include_patterns",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'[]'"
},
"last_backup_at": {
"name": "last_backup_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_backup_status": {
"name": "last_backup_status",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_backup_error": {
"name": "last_backup_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"next_backup_at": {
"name": "next_backup_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {
"backup_schedules_table_name_unique": {
"name": "backup_schedules_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {
"backup_schedules_table_volume_id_volumes_table_id_fk": {
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
"tableFrom": "backup_schedules_table",
"tableTo": "volumes_table",
"columnsFrom": [
"volume_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"backup_schedules_table_repository_id_repositories_table_id_fk": {
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
"tableFrom": "backup_schedules_table",
"tableTo": "repositories_table",
"columnsFrom": [
"repository_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"notification_destinations_table": {
"name": "notification_destinations_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {
"notification_destinations_table_name_unique": {
"name": "notification_destinations_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"repositories_table": {
"name": "repositories_table",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"short_id": {
"name": "short_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"compression_mode": {
"name": "compression_mode",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'auto'"
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'unknown'"
},
"last_checked": {
"name": "last_checked",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {
"repositories_table_short_id_unique": {
"name": "repositories_table_short_id_unique",
"columns": [
"short_id"
],
"isUnique": true
},
"repositories_table_name_unique": {
"name": "repositories_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sessions_table": {
"name": "sessions_table",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {},
"foreignKeys": {
"sessions_table_user_id_users_table_id_fk": {
"name": "sessions_table_user_id_users_table_id_fk",
"tableFrom": "sessions_table",
"tableTo": "users_table",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users_table": {
"name": "users_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"has_downloaded_restic_password": {
"name": "has_downloaded_restic_password",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
}
},
"indexes": {
"users_table_username_unique": {
"name": "users_table_username_unique",
"columns": [
"username"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"volumes_table": {
"name": "volumes_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"short_id": {
"name": "short_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'unmounted'"
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_health_check": {
"name": "last_health_check",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch() * 1000)"
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"auto_remount": {
"name": "auto_remount",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
}
},
"indexes": {
"volumes_table_short_id_unique": {
"name": "volumes_table_short_id_unique",
"columns": [
"short_id"
],
"isUnique": true
},
"volumes_table_name_unique": {
"name": "volumes_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View file

@ -134,6 +134,13 @@
"when": 1764794371040, "when": 1764794371040,
"tag": "0018_breezy_invaders", "tag": "0018_breezy_invaders",
"breakpoints": true "breakpoints": true
},
{
"idx": 19,
"version": "6",
"when": 1764839917446,
"tag": "0019_secret_nomad",
"breakpoints": true
} }
] ]
} }

View file

@ -67,6 +67,7 @@ export type Repository = typeof repositoriesTable.$inferSelect;
*/ */
export const backupSchedulesTable = sqliteTable("backup_schedules_table", { export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
id: int().primaryKey({ autoIncrement: true }), id: int().primaryKey({ autoIncrement: true }),
name: text().notNull().unique(),
volumeId: int("volume_id") volumeId: int("volume_id")
.notNull() .notNull()
.references(() => volumesTable.id, { onDelete: "cascade" }), .references(() => volumesTable.id, { onDelete: "cascade" }),

View file

@ -17,6 +17,7 @@ export type RetentionPolicy = typeof retentionPolicySchema.infer;
const backupScheduleSchema = type({ const backupScheduleSchema = type({
id: "number", id: "number",
name: "string",
volumeId: "number", volumeId: "number",
repositoryId: "string", repositoryId: "string",
enabled: "boolean", enabled: "boolean",
@ -120,6 +121,7 @@ export const getBackupScheduleForVolumeDto = describeRoute({
* Create a new backup schedule * Create a new backup schedule
*/ */
export const createBackupScheduleBody = type({ export const createBackupScheduleBody = type({
name: "1 <= string <= 32",
volumeId: "number", volumeId: "number",
repositoryId: "string", repositoryId: "string",
enabled: "boolean", enabled: "boolean",
@ -156,6 +158,7 @@ export const createBackupScheduleDto = describeRoute({
* Update a backup schedule * Update a backup schedule
*/ */
export const updateBackupScheduleBody = type({ export const updateBackupScheduleBody = type({
name: "(1 <= string <= 32)?",
repositoryId: "string", repositoryId: "string",
enabled: "boolean?", enabled: "boolean?",
cronExpression: "string", cronExpression: "string",

View file

@ -1,4 +1,4 @@
import { eq } from "drizzle-orm"; import { and, eq, ne } from "drizzle-orm";
import cron from "node-cron"; import cron from "node-cron";
import { CronExpressionParser } from "cron-parser"; import { CronExpressionParser } from "cron-parser";
import { NotFoundError, BadRequestError, ConflictError } from "http-errors-enhanced"; import { NotFoundError, BadRequestError, ConflictError } from "http-errors-enhanced";
@ -63,6 +63,14 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
throw new BadRequestError("Invalid cron expression"); throw new BadRequestError("Invalid cron expression");
} }
const existingName = await db.query.backupSchedulesTable.findFirst({
where: eq(backupSchedulesTable.name, data.name),
});
if (existingName) {
throw new ConflictError("A backup schedule with this name already exists");
}
const volume = await db.query.volumesTable.findFirst({ const volume = await db.query.volumesTable.findFirst({
where: eq(volumesTable.id, data.volumeId), where: eq(volumesTable.id, data.volumeId),
}); });
@ -84,6 +92,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {
const [newSchedule] = await db const [newSchedule] = await db
.insert(backupSchedulesTable) .insert(backupSchedulesTable)
.values({ .values({
name: data.name,
volumeId: data.volumeId, volumeId: data.volumeId,
repositoryId: data.repositoryId, repositoryId: data.repositoryId,
enabled: data.enabled, enabled: data.enabled,
@ -115,6 +124,16 @@ const updateSchedule = async (scheduleId: number, data: UpdateBackupScheduleBody
throw new BadRequestError("Invalid cron expression"); throw new BadRequestError("Invalid cron expression");
} }
if (data.name) {
const existingName = await db.query.backupSchedulesTable.findFirst({
where: and(eq(backupSchedulesTable.name, data.name), ne(backupSchedulesTable.id, scheduleId)),
});
if (existingName) {
throw new ConflictError("A backup schedule with this name already exists");
}
}
const repository = await db.query.repositoriesTable.findFirst({ const repository = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.id, data.repositoryId), where: eq(repositoriesTable.id, data.repositoryId),
}); });

View file

@ -1,6 +1,7 @@
import crypto from "node:crypto"; import crypto from "node:crypto";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import os from "node:os";
import { throttle } from "es-toolkit"; import { throttle } from "es-toolkit";
import { type } from "arktype"; import { type } from "arktype";
import { $ } from "bun"; import { $ } from "bun";
@ -261,8 +262,9 @@ const backup = async (
let includeFile: string | null = null; let includeFile: string | null = null;
if (options?.include && options.include.length > 0) { if (options?.include && options.include.length > 0) {
const tmp = await fs.mkdtemp("restic-include"); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-restic-include-"));
includeFile = path.join(tmp, `include.txt`); includeFile = path.join(tmp, `include.txt`);
const includePaths = options.include.map((p) => path.join(source, p)); const includePaths = options.include.map((p) => path.join(source, p));
await fs.writeFile(includeFile, includePaths.join("\n"), "utf-8"); await fs.writeFile(includeFile, includePaths.join("\n"), "utf-8");

View file

@ -32,7 +32,7 @@
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.44.7", "drizzle-orm": "^0.44.7",
"es-toolkit": "^1.42.0", "es-toolkit": "^1.42.0",
"hono": "^4.10.7", "hono": "4.10.5",
"hono-openapi": "^1.1.1", "hono-openapi": "^1.1.1",
"http-errors-enhanced": "^4.0.2", "http-errors-enhanced": "^4.0.2",
"isbot": "^5.1.32", "isbot": "^5.1.32",
@ -41,7 +41,7 @@
"node-cron": "^4.2.1", "node-cron": "^4.2.1",
"react": "^19.2.1", "react": "^19.2.1",
"react-dom": "^19.2.1", "react-dom": "^19.2.1",
"react-hook-form": "^7.67.0", "react-hook-form": "^7.68.0",
"react-router": "^7.10.0", "react-router": "^7.10.0",
"react-router-hono-server": "^2.22.0", "react-router-hono-server": "^2.22.0",
"recharts": "3.5.1", "recharts": "3.5.1",
@ -770,7 +770,7 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="], "hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="],
"hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="], "hono-openapi": ["hono-openapi@1.1.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-AC3HNhZYPHhnZdSy2Je7GDoTTNxPos6rKRQKVDBbSilY3cWJPqsxRnN6zA4pU7tfxmQEMTqkiLXbw6sAaemB8Q=="],
@ -952,7 +952,7 @@
"react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
"react-hook-form": ["react-hook-form@7.67.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ=="], "react-hook-form": ["react-hook-form@7.68.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q=="],
"react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="], "react-is": ["react-is@19.2.0", "", {}, "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA=="],
@ -1216,8 +1216,6 @@
"react-router-hono-server/@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="], "react-router-hono-server/@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
"react-router-hono-server/hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],

View file

@ -45,7 +45,7 @@
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.44.7", "drizzle-orm": "^0.44.7",
"es-toolkit": "^1.42.0", "es-toolkit": "^1.42.0",
"hono": "^4.10.7", "hono": "4.10.5",
"hono-openapi": "^1.1.1", "hono-openapi": "^1.1.1",
"http-errors-enhanced": "^4.0.2", "http-errors-enhanced": "^4.0.2",
"isbot": "^5.1.32", "isbot": "^5.1.32",
@ -54,7 +54,7 @@
"node-cron": "^4.2.1", "node-cron": "^4.2.1",
"react": "^19.2.1", "react": "^19.2.1",
"react-dom": "^19.2.1", "react-dom": "^19.2.1",
"react-hook-form": "^7.67.0", "react-hook-form": "^7.68.0",
"react-router": "^7.10.0", "react-router": "^7.10.0",
"react-router-hono-server": "^2.22.0", "react-router-hono-server": "^2.22.0",
"recharts": "3.5.1", "recharts": "3.5.1",