refactor: create volume & restore snapshot
This commit is contained in:
parent
8a25b79377
commit
e424009dd3
11 changed files with 204 additions and 126 deletions
|
|
@ -1,6 +1,5 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ChevronDown, FileIcon, FolderOpen, RotateCcw } from "lucide-react";
|
import { ChevronDown, FileIcon, FolderOpen, RotateCcw } from "lucide-react";
|
||||||
import { Button } from "~/client/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
|
|
@ -16,6 +15,7 @@ import { useFileBrowser } from "~/client/hooks/use-file-browser";
|
||||||
import { OVERWRITE_MODES, type OverwriteMode } from "~/schemas/restic";
|
import { OVERWRITE_MODES, type OverwriteMode } from "~/schemas/restic";
|
||||||
import type { Repository, Snapshot } from "~/client/lib/types";
|
import type { Repository, Snapshot } from "~/client/lib/types";
|
||||||
import { handleRepositoryError } from "~/client/lib/errors";
|
import { handleRepositoryError } from "~/client/lib/errors";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
type RestoreLocation = "original" | "custom";
|
type RestoreLocation = "original" | "custom";
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ export function RestoreForm({ snapshot, repository, snapshotId, returnPath }: Re
|
||||||
...restoreSnapshotMutation(),
|
...restoreSnapshotMutation(),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Restore completed");
|
toast.success("Restore completed");
|
||||||
void navigate(returnPath);
|
void navigate({ to: returnPath });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
handleRepositoryError("Restore failed", error, repository.id);
|
handleRepositoryError("Restore failed", error, repository.id);
|
||||||
|
|
@ -156,7 +156,7 @@ export function RestoreForm({ snapshot, repository, snapshotId, returnPath }: Re
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" onClick={() => navigate(returnPath)}>
|
<Button variant="outline" onClick={() => navigate({ to: returnPath })}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={handleRestore} disabled={isRestoring || !canRestore}>
|
<Button variant="primary" onClick={handleRestore} disabled={isRestoring || !canRestore}>
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import { redirect } from "react-router";
|
|
||||||
import { getBackupSchedule, getRepository, getSnapshotDetails } from "~/client/api-client";
|
|
||||||
import { RestoreForm } from "~/client/components/restore-form";
|
|
||||||
import type { Route } from "./+types/restore-snapshot";
|
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: (match: Route.MetaArgs) => [
|
|
||||||
{ label: "Backups", href: "/backups" },
|
|
||||||
{ label: `Schedule #${match.params.id}`, href: `/backups/${match.params.id}` },
|
|
||||||
{ label: match.params.snapshotId },
|
|
||||||
{ label: "Restore" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function meta({ params }: Route.MetaArgs) {
|
|
||||||
return [
|
|
||||||
{ title: `Zerobyte - Restore Snapshot ${params.snapshotId}` },
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content: "Restore files from a backup snapshot.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
|
|
||||||
const schedule = await getBackupSchedule({ path: { scheduleId: params.id } });
|
|
||||||
|
|
||||||
if (!schedule.data) return redirect("/backups");
|
|
||||||
|
|
||||||
const [snapshot, repository] = await Promise.all([
|
|
||||||
getSnapshotDetails({
|
|
||||||
path: {
|
|
||||||
id: schedule.data.repositoryId,
|
|
||||||
snapshotId: params.snapshotId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getRepository({ path: { id: schedule.data.repositoryId } }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!snapshot.data) return redirect(`/backups/${params.id}`);
|
|
||||||
if (!repository.data) return redirect(`/backups/${params.id}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
snapshot: snapshot.data,
|
|
||||||
repository: repository.data,
|
|
||||||
snapshotId: params.snapshotId,
|
|
||||||
backupId: params.id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RestoreSnapshotFromBackupPage({ loaderData }: Route.ComponentProps) {
|
|
||||||
const { snapshot, repository, snapshotId, backupId } = loaderData;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RestoreForm
|
|
||||||
snapshot={snapshot}
|
|
||||||
repository={repository}
|
|
||||||
snapshotId={snapshotId}
|
|
||||||
returnPath={`/backups/${backupId}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +1,24 @@
|
||||||
import { redirect } from "react-router";
|
|
||||||
import { getRepository, getSnapshotDetails } from "~/client/api-client";
|
|
||||||
import { RestoreForm } from "~/client/components/restore-form";
|
import { RestoreForm } from "~/client/components/restore-form";
|
||||||
import type { Route } from "./+types/restore-snapshot";
|
import type { Repository, Snapshot } from "~/client/lib/types";
|
||||||
|
|
||||||
export const handle = {
|
// export const handle = {
|
||||||
breadcrumb: (match: Route.MetaArgs) => [
|
// breadcrumb: (match: Route.MetaArgs) => [
|
||||||
{ label: "Repositories", href: "/repositories" },
|
// { label: "Repositories", href: "/repositories" },
|
||||||
{ label: match.loaderData?.repository.name || match.params.id, href: `/repositories/${match.params.id}` },
|
// { label: match.loaderData?.repository.name || match.params.id, href: `/repositories/${match.params.id}` },
|
||||||
{ label: match.params.snapshotId, href: `/repositories/${match.params.id}/${match.params.snapshotId}` },
|
// { label: match.params.snapshotId, href: `/repositories/${match.params.id}/${match.params.snapshotId}` },
|
||||||
{ label: "Restore" },
|
// { label: "Restore" },
|
||||||
],
|
// ],
|
||||||
|
// };
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
snapshot: Snapshot;
|
||||||
|
repository: Repository;
|
||||||
|
snapshotId: string;
|
||||||
|
returnPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function meta({ params }: Route.MetaArgs) {
|
export function RestoreSnapshotPage(props: Props) {
|
||||||
return [
|
const { snapshot, returnPath, snapshotId, repository } = props;
|
||||||
{ title: `Zerobyte - Restore Snapshot ${params.snapshotId}` },
|
|
||||||
{
|
return <RestoreForm snapshot={snapshot} repository={repository} snapshotId={snapshotId} returnPath={returnPath} />;
|
||||||
name: "description",
|
|
||||||
content: "Restore files from a backup snapshot.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
|
|
||||||
const [snapshot, repository] = await Promise.all([
|
|
||||||
getSnapshotDetails({ path: { id: params.id, snapshotId: params.snapshotId } }),
|
|
||||||
getRepository({ path: { id: params.id } }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!snapshot.data) return redirect("/repositories");
|
|
||||||
if (!repository.data) return redirect(`/repositories`);
|
|
||||||
|
|
||||||
return { snapshot: snapshot.data, id: params.id, repository: repository.data, snapshotId: params.snapshotId };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RestoreSnapshotPage({ loaderData }: Route.ComponentProps) {
|
|
||||||
const { snapshot, id, snapshotId, repository } = loaderData;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RestoreForm
|
|
||||||
snapshot={snapshot}
|
|
||||||
repository={repository}
|
|
||||||
snapshotId={snapshotId}
|
|
||||||
returnPath={`/repositories/${id}/${snapshotId}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,20 @@
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { HardDrive, Plus } from "lucide-react";
|
import { HardDrive, Plus } from "lucide-react";
|
||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import { useNavigate } from "react-router";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { createVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
import { createVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { CreateVolumeForm, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
|
import { CreateVolumeForm, type FormValues } from "~/client/modules/volumes/components/create-volume-form";
|
||||||
import { Button } from "~/client/components/ui/button";
|
import { Button } from "~/client/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
|
||||||
import { parseError } from "~/client/lib/errors";
|
import { parseError } from "~/client/lib/errors";
|
||||||
import type { Route } from "./+types/create-volume";
|
|
||||||
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
export const handle = {
|
||||||
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function CreateVolumePage() {
|
||||||
return [
|
|
||||||
{ title: "Zerobyte - Create Volume" },
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content: "Create a new storage volume with automatic mounting and health checks.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CreateVolume() {
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
||||||
|
|
@ -33,7 +22,7 @@ export default function CreateVolume() {
|
||||||
...createVolumeMutation(),
|
...createVolumeMutation(),
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.success("Volume created successfully");
|
toast.success("Volume created successfully");
|
||||||
void navigate(`/volumes/${data.shortId}`);
|
void navigate({ to: `/volumes/${data.shortId}` });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -69,7 +58,7 @@ export default function CreateVolume() {
|
||||||
)}
|
)}
|
||||||
<CreateVolumeForm mode="create" formId={formId} onSubmit={handleSubmit} loading={createVolume.isPending} />
|
<CreateVolumeForm mode="create" formId={formId} onSubmit={handleSubmit} loading={createVolume.isPending} />
|
||||||
<div className="flex justify-end gap-2 pt-4 border-t">
|
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||||
<Button type="button" variant="secondary" onClick={() => navigate("/volumes")}>
|
<Button type="button" variant="secondary" onClick={() => navigate({ to: "/volumes" })}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" form={formId} loading={createVolume.isPending}>
|
<Button type="submit" form={formId} loading={createVolume.isPending}>
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,15 @@ import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/dow
|
||||||
import { Route as dashboardVolumesIndexRouteImport } from './routes/(dashboard)/volumes/index'
|
import { Route as dashboardVolumesIndexRouteImport } from './routes/(dashboard)/volumes/index'
|
||||||
import { Route as dashboardRepositoriesIndexRouteImport } from './routes/(dashboard)/repositories/index'
|
import { Route as dashboardRepositoriesIndexRouteImport } from './routes/(dashboard)/repositories/index'
|
||||||
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
import { Route as dashboardBackupsIndexRouteImport } from './routes/(dashboard)/backups/index'
|
||||||
|
import { Route as dashboardVolumesCreateRouteImport } from './routes/(dashboard)/volumes/create'
|
||||||
import { Route as dashboardVolumesVolumeIdRouteImport } from './routes/(dashboard)/volumes/$volumeId'
|
import { Route as dashboardVolumesVolumeIdRouteImport } from './routes/(dashboard)/volumes/$volumeId'
|
||||||
import { Route as dashboardRepositoriesCreateRouteImport } from './routes/(dashboard)/repositories/create'
|
import { Route as dashboardRepositoriesCreateRouteImport } from './routes/(dashboard)/repositories/create'
|
||||||
import { Route as dashboardRepositoriesRepositoryIdRouteImport } from './routes/(dashboard)/repositories/$repositoryId'
|
import { Route as dashboardRepositoriesRepositoryIdRouteImport } from './routes/(dashboard)/repositories/$repositoryId'
|
||||||
import { Route as dashboardBackupsCreateRouteImport } from './routes/(dashboard)/backups/create'
|
import { Route as dashboardBackupsCreateRouteImport } from './routes/(dashboard)/backups/create'
|
||||||
import { Route as dashboardBackupsScheduleIdRouteImport } from './routes/(dashboard)/backups/$scheduleId'
|
import { Route as dashboardBackupsScheduleIdRouteImport } from './routes/(dashboard)/backups/$scheduleId'
|
||||||
import { Route as dashboardRepositoriesRepoIdSnapshotIdRouteImport } from './routes/(dashboard)/repositories/$repoId.$snapshotId'
|
import { Route as dashboardRepositoriesRepoIdSnapshotIdRouteImport } from './routes/(dashboard)/repositories/$repoId.$snapshotId'
|
||||||
|
import { Route as dashboardRepositoriesRepoIdSnapIdRestoreRouteImport } from './routes/(dashboard)/repositories/$repoId.$snapId.restore'
|
||||||
|
import { Route as dashboardBackupsBackupIdSnapshotIdRestoreRouteImport } from './routes/(dashboard)/backups/$backupId.$snapshotId.restore'
|
||||||
|
|
||||||
const dashboardRouteRoute = dashboardRouteRouteImport.update({
|
const dashboardRouteRoute = dashboardRouteRouteImport.update({
|
||||||
id: '/(dashboard)',
|
id: '/(dashboard)',
|
||||||
|
|
@ -64,6 +67,11 @@ const dashboardBackupsIndexRoute = dashboardBackupsIndexRouteImport.update({
|
||||||
path: '/backups/',
|
path: '/backups/',
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const dashboardVolumesCreateRoute = dashboardVolumesCreateRouteImport.update({
|
||||||
|
id: '/volumes/create',
|
||||||
|
path: '/volumes/create',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
const dashboardVolumesVolumeIdRoute =
|
const dashboardVolumesVolumeIdRoute =
|
||||||
dashboardVolumesVolumeIdRouteImport.update({
|
dashboardVolumesVolumeIdRouteImport.update({
|
||||||
id: '/volumes/$volumeId',
|
id: '/volumes/$volumeId',
|
||||||
|
|
@ -99,6 +107,18 @@ const dashboardRepositoriesRepoIdSnapshotIdRoute =
|
||||||
path: '/repositories/$repoId/$snapshotId',
|
path: '/repositories/$repoId/$snapshotId',
|
||||||
getParentRoute: () => dashboardRouteRoute,
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const dashboardRepositoriesRepoIdSnapIdRestoreRoute =
|
||||||
|
dashboardRepositoriesRepoIdSnapIdRestoreRouteImport.update({
|
||||||
|
id: '/repositories/$repoId/$snapId/restore',
|
||||||
|
path: '/repositories/$repoId/$snapId/restore',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
|
const dashboardBackupsBackupIdSnapshotIdRestoreRoute =
|
||||||
|
dashboardBackupsBackupIdSnapshotIdRestoreRouteImport.update({
|
||||||
|
id: '/backups/$backupId/$snapshotId/restore',
|
||||||
|
path: '/backups/$backupId/$snapshotId/restore',
|
||||||
|
getParentRoute: () => dashboardRouteRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
|
@ -110,10 +130,13 @@ export interface FileRoutesByFullPath {
|
||||||
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
||||||
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
||||||
|
'/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/backups/': typeof dashboardBackupsIndexRoute
|
'/backups/': typeof dashboardBackupsIndexRoute
|
||||||
'/repositories/': typeof dashboardRepositoriesIndexRoute
|
'/repositories/': typeof dashboardRepositoriesIndexRoute
|
||||||
'/volumes/': typeof dashboardVolumesIndexRoute
|
'/volumes/': typeof dashboardVolumesIndexRoute
|
||||||
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
'/repositories/$repoId/$snapId/restore': typeof dashboardRepositoriesRepoIdSnapIdRestoreRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
|
@ -125,10 +148,13 @@ export interface FileRoutesByTo {
|
||||||
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
||||||
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
||||||
|
'/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/backups': typeof dashboardBackupsIndexRoute
|
'/backups': typeof dashboardBackupsIndexRoute
|
||||||
'/repositories': typeof dashboardRepositoriesIndexRoute
|
'/repositories': typeof dashboardRepositoriesIndexRoute
|
||||||
'/volumes': typeof dashboardVolumesIndexRoute
|
'/volumes': typeof dashboardVolumesIndexRoute
|
||||||
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
|
'/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
'/repositories/$repoId/$snapId/restore': typeof dashboardRepositoriesRepoIdSnapIdRestoreRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
|
|
@ -142,10 +168,13 @@ export interface FileRoutesById {
|
||||||
'/(dashboard)/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
'/(dashboard)/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
|
||||||
'/(dashboard)/repositories/create': typeof dashboardRepositoriesCreateRoute
|
'/(dashboard)/repositories/create': typeof dashboardRepositoriesCreateRoute
|
||||||
'/(dashboard)/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
'/(dashboard)/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
|
||||||
|
'/(dashboard)/volumes/create': typeof dashboardVolumesCreateRoute
|
||||||
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
'/(dashboard)/backups/': typeof dashboardBackupsIndexRoute
|
||||||
'/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute
|
'/(dashboard)/repositories/': typeof dashboardRepositoriesIndexRoute
|
||||||
'/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute
|
'/(dashboard)/volumes/': typeof dashboardVolumesIndexRoute
|
||||||
'/(dashboard)/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
'/(dashboard)/repositories/$repoId/$snapshotId': typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
|
'/(dashboard)/backups/$backupId/$snapshotId/restore': typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
'/(dashboard)/repositories/$repoId/$snapId/restore': typeof dashboardRepositoriesRepoIdSnapIdRestoreRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
|
@ -159,10 +188,13 @@ export interface FileRouteTypes {
|
||||||
| '/repositories/$repositoryId'
|
| '/repositories/$repositoryId'
|
||||||
| '/repositories/create'
|
| '/repositories/create'
|
||||||
| '/volumes/$volumeId'
|
| '/volumes/$volumeId'
|
||||||
|
| '/volumes/create'
|
||||||
| '/backups/'
|
| '/backups/'
|
||||||
| '/repositories/'
|
| '/repositories/'
|
||||||
| '/volumes/'
|
| '/volumes/'
|
||||||
| '/repositories/$repoId/$snapshotId'
|
| '/repositories/$repoId/$snapshotId'
|
||||||
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
|
| '/repositories/$repoId/$snapId/restore'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
|
|
@ -174,10 +206,13 @@ export interface FileRouteTypes {
|
||||||
| '/repositories/$repositoryId'
|
| '/repositories/$repositoryId'
|
||||||
| '/repositories/create'
|
| '/repositories/create'
|
||||||
| '/volumes/$volumeId'
|
| '/volumes/$volumeId'
|
||||||
|
| '/volumes/create'
|
||||||
| '/backups'
|
| '/backups'
|
||||||
| '/repositories'
|
| '/repositories'
|
||||||
| '/volumes'
|
| '/volumes'
|
||||||
| '/repositories/$repoId/$snapshotId'
|
| '/repositories/$repoId/$snapshotId'
|
||||||
|
| '/backups/$backupId/$snapshotId/restore'
|
||||||
|
| '/repositories/$repoId/$snapId/restore'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
|
|
@ -190,10 +225,13 @@ export interface FileRouteTypes {
|
||||||
| '/(dashboard)/repositories/$repositoryId'
|
| '/(dashboard)/repositories/$repositoryId'
|
||||||
| '/(dashboard)/repositories/create'
|
| '/(dashboard)/repositories/create'
|
||||||
| '/(dashboard)/volumes/$volumeId'
|
| '/(dashboard)/volumes/$volumeId'
|
||||||
|
| '/(dashboard)/volumes/create'
|
||||||
| '/(dashboard)/backups/'
|
| '/(dashboard)/backups/'
|
||||||
| '/(dashboard)/repositories/'
|
| '/(dashboard)/repositories/'
|
||||||
| '/(dashboard)/volumes/'
|
| '/(dashboard)/volumes/'
|
||||||
| '/(dashboard)/repositories/$repoId/$snapshotId'
|
| '/(dashboard)/repositories/$repoId/$snapshotId'
|
||||||
|
| '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
||||||
|
| '/(dashboard)/repositories/$repoId/$snapId/restore'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
|
|
@ -262,6 +300,13 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof dashboardBackupsIndexRouteImport
|
preLoaderRoute: typeof dashboardBackupsIndexRouteImport
|
||||||
parentRoute: typeof dashboardRouteRoute
|
parentRoute: typeof dashboardRouteRoute
|
||||||
}
|
}
|
||||||
|
'/(dashboard)/volumes/create': {
|
||||||
|
id: '/(dashboard)/volumes/create'
|
||||||
|
path: '/volumes/create'
|
||||||
|
fullPath: '/volumes/create'
|
||||||
|
preLoaderRoute: typeof dashboardVolumesCreateRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
'/(dashboard)/volumes/$volumeId': {
|
'/(dashboard)/volumes/$volumeId': {
|
||||||
id: '/(dashboard)/volumes/$volumeId'
|
id: '/(dashboard)/volumes/$volumeId'
|
||||||
path: '/volumes/$volumeId'
|
path: '/volumes/$volumeId'
|
||||||
|
|
@ -304,6 +349,20 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRouteImport
|
preLoaderRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRouteImport
|
||||||
parentRoute: typeof dashboardRouteRoute
|
parentRoute: typeof dashboardRouteRoute
|
||||||
}
|
}
|
||||||
|
'/(dashboard)/repositories/$repoId/$snapId/restore': {
|
||||||
|
id: '/(dashboard)/repositories/$repoId/$snapId/restore'
|
||||||
|
path: '/repositories/$repoId/$snapId/restore'
|
||||||
|
fullPath: '/repositories/$repoId/$snapId/restore'
|
||||||
|
preLoaderRoute: typeof dashboardRepositoriesRepoIdSnapIdRestoreRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
|
'/(dashboard)/backups/$backupId/$snapshotId/restore': {
|
||||||
|
id: '/(dashboard)/backups/$backupId/$snapshotId/restore'
|
||||||
|
path: '/backups/$backupId/$snapshotId/restore'
|
||||||
|
fullPath: '/backups/$backupId/$snapshotId/restore'
|
||||||
|
preLoaderRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRouteImport
|
||||||
|
parentRoute: typeof dashboardRouteRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,10 +372,13 @@ interface dashboardRouteRouteChildren {
|
||||||
dashboardRepositoriesRepositoryIdRoute: typeof dashboardRepositoriesRepositoryIdRoute
|
dashboardRepositoriesRepositoryIdRoute: typeof dashboardRepositoriesRepositoryIdRoute
|
||||||
dashboardRepositoriesCreateRoute: typeof dashboardRepositoriesCreateRoute
|
dashboardRepositoriesCreateRoute: typeof dashboardRepositoriesCreateRoute
|
||||||
dashboardVolumesVolumeIdRoute: typeof dashboardVolumesVolumeIdRoute
|
dashboardVolumesVolumeIdRoute: typeof dashboardVolumesVolumeIdRoute
|
||||||
|
dashboardVolumesCreateRoute: typeof dashboardVolumesCreateRoute
|
||||||
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
|
||||||
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
|
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
|
||||||
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
|
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute
|
||||||
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute: typeof dashboardBackupsBackupIdSnapshotIdRestoreRoute
|
||||||
|
dashboardRepositoriesRepoIdSnapIdRestoreRoute: typeof dashboardRepositoriesRepoIdSnapIdRestoreRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
||||||
|
|
@ -326,11 +388,16 @@ const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
|
||||||
dashboardRepositoriesRepositoryIdRoute,
|
dashboardRepositoriesRepositoryIdRoute,
|
||||||
dashboardRepositoriesCreateRoute: dashboardRepositoriesCreateRoute,
|
dashboardRepositoriesCreateRoute: dashboardRepositoriesCreateRoute,
|
||||||
dashboardVolumesVolumeIdRoute: dashboardVolumesVolumeIdRoute,
|
dashboardVolumesVolumeIdRoute: dashboardVolumesVolumeIdRoute,
|
||||||
|
dashboardVolumesCreateRoute: dashboardVolumesCreateRoute,
|
||||||
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
|
||||||
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
|
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
|
||||||
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
|
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute:
|
dashboardRepositoriesRepoIdSnapshotIdRoute:
|
||||||
dashboardRepositoriesRepoIdSnapshotIdRoute,
|
dashboardRepositoriesRepoIdSnapshotIdRoute,
|
||||||
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute:
|
||||||
|
dashboardBackupsBackupIdSnapshotIdRestoreRoute,
|
||||||
|
dashboardRepositoriesRepoIdSnapIdRestoreRoute:
|
||||||
|
dashboardRepositoriesRepoIdSnapIdRestoreRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const dashboardRouteRouteWithChildren = dashboardRouteRoute._addFileChildren(
|
const dashboardRouteRouteWithChildren = dashboardRouteRoute._addFileChildren(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { getBackupSchedule } from "~/client/api-client";
|
||||||
|
import { getRepositoryOptions, getSnapshotDetailsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
import { RestoreSnapshotPage } from "~/client/modules/repositories/routes/restore-snapshot";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/(dashboard)/backups/$backupId/$snapshotId/restore")({
|
||||||
|
component: RouteComponent,
|
||||||
|
loader: async ({ params, context }) => {
|
||||||
|
const schedule = await getBackupSchedule({ path: { scheduleId: params.backupId } });
|
||||||
|
|
||||||
|
if (!schedule.data) {
|
||||||
|
throw new Response("Not Found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [snapshot, repository] = await Promise.all([
|
||||||
|
context.queryClient.ensureQueryData({
|
||||||
|
...getSnapshotDetailsOptions({ path: { id: schedule.data?.repositoryId, snapshotId: params.snapshotId } }),
|
||||||
|
}),
|
||||||
|
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: schedule.data?.repositoryId } }) }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { snapshot, repository };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { backupId, snapshotId } = Route.useParams();
|
||||||
|
const { snapshot, repository } = Route.useLoaderData();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RestoreSnapshotPage
|
||||||
|
returnPath={`/backups/${backupId}`}
|
||||||
|
snapshotId={snapshotId}
|
||||||
|
snapshot={snapshot}
|
||||||
|
repository={repository}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { getRepositoryOptions, getSnapshotDetailsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
|
import { RestoreSnapshotPage } from "~/client/modules/repositories/routes/restore-snapshot";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapId/restore")({
|
||||||
|
component: RouteComponent,
|
||||||
|
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||||
|
loader: async ({ params, context }) => {
|
||||||
|
const [snapshot, repository] = await Promise.all([
|
||||||
|
context.queryClient.ensureQueryData({
|
||||||
|
...getSnapshotDetailsOptions({ path: { id: params.repoId, snapshotId: params.snapId } }),
|
||||||
|
}),
|
||||||
|
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: params.repoId } }) }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { snapshot, repository };
|
||||||
|
},
|
||||||
|
head: ({ params }) => ({
|
||||||
|
meta: [
|
||||||
|
{ title: `Zerobyte - Restore Snapshot ${params.snapId}` },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "Restore files from a backup snapshot.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { repoId, snapId } = Route.useParams();
|
||||||
|
const { snapshot, repository } = Route.useLoaderData();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RestoreSnapshotPage
|
||||||
|
returnPath={`/repositories/${repoId}/${snapId}`}
|
||||||
|
snapshot={snapshot}
|
||||||
|
repository={repository}
|
||||||
|
snapshotId={snapId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
app/routes/(dashboard)/volumes/create.tsx
Normal file
19
app/routes/(dashboard)/volumes/create.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { CreateVolumePage } from "~/client/modules/volumes/routes/create-volume";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/(dashboard)/volumes/create")({
|
||||||
|
component: RouteComponent,
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{ title: "Zerobyte - Create Volume" },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "Create a new storage volume with automatic mounting and health checks.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <CreateVolumePage />;
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import type { QueryClient } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
import { Toaster } from "~/client/components/ui/sonner";
|
import { Toaster } from "~/client/components/ui/sonner";
|
||||||
|
import { useServerEvents } from "~/client/hooks/use-server-events";
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
||||||
server: {
|
server: {
|
||||||
|
|
@ -38,6 +39,8 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
|
||||||
});
|
});
|
||||||
|
|
||||||
function RootLayout() {
|
function RootLayout() {
|
||||||
|
useServerEvents();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{ title: "Zerobyte" },
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "Zerobyte - Manage your backups and storage volumes with ease.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
redirect({ to: "/volumes" });
|
throw redirect({ to: "/volumes" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -315,8 +315,6 @@ const getSnapshotDetails = async (id: string, snapshotId: string) => {
|
||||||
const organizationId = getOrganizationId();
|
const organizationId = getOrganizationId();
|
||||||
const repository = await findRepository(id);
|
const repository = await findRepository(id);
|
||||||
|
|
||||||
console.log("Getting snapshot details for:", id, snapshotId);
|
|
||||||
|
|
||||||
if (!repository) {
|
if (!repository) {
|
||||||
throw new NotFoundError("Repository not found");
|
throw new NotFoundError("Repository not found");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue