refactor: backups, create repo, volumes

This commit is contained in:
Nicolas Meienberger 2026-02-08 15:53:55 +01:00
parent 551618d192
commit 8a25b79377
17 changed files with 310 additions and 175 deletions

View file

@ -1,5 +1,6 @@
import { useSortable } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import { ClientOnly } from "@tanstack/react-router";
import { GripVertical } from "lucide-react"; import { GripVertical } from "lucide-react";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
@ -21,13 +22,15 @@ export function SortableCard({ isDragging, uniqueId, children }: PropsWithChildr
return ( return (
<div ref={setNodeRef} style={style} className="relative group"> <div ref={setNodeRef} style={style} className="relative group">
<div <ClientOnly>
{...attributes} <div
{...listeners} {...attributes}
className="absolute left-1/2 -translate-x-1/2 top-1 z-10 cursor-grab active:cursor-grabbing opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-muted/50 bg-background/80 backdrop-blur-sm" {...listeners}
> className="absolute left-1/2 -translate-x-1/2 top-1 z-10 cursor-grab active:cursor-grabbing opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-muted/50 bg-background/80 backdrop-blur-sm"
<GripVertical className="h-4 w-4 text-muted-foreground rotate-90" /> >
</div> <GripVertical className="h-4 w-4 text-muted-foreground rotate-90" />
</div>
</ClientOnly>
{children} {children}
</div> </div>
); );

View file

@ -1,6 +1,8 @@
import type { import type {
GetBackupScheduleResponse, GetBackupScheduleResponse,
GetRepositoryResponse, GetRepositoryResponse,
GetScheduleMirrorsResponse,
GetScheduleNotificationsResponse,
GetVolumeResponse, GetVolumeResponse,
ListNotificationDestinationsResponse, ListNotificationDestinationsResponse,
ListSnapshotsResponse, ListSnapshotsResponse,
@ -17,3 +19,6 @@ export type BackupSchedule = GetBackupScheduleResponse;
export type Snapshot = ListSnapshotsResponse[number]; export type Snapshot = ListSnapshotsResponse[number];
export type NotificationDestination = ListNotificationDestinationsResponse[number]; export type NotificationDestination = ListNotificationDestinationsResponse[number];
export type ScheduleNotification = GetScheduleNotificationsResponse[number];
export type ScheduleMirror = GetScheduleMirrorsResponse[number];

View file

@ -1,13 +1,13 @@
import { CalendarClock, Database, HardDrive } from "lucide-react"; import { CalendarClock, Database, HardDrive } from "lucide-react";
import { Link } from "react-router";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
import type { BackupSchedule } from "~/client/lib/types"; import type { BackupSchedule } from "~/client/lib/types";
import { BackupStatusDot } from "./backup-status-dot"; import { BackupStatusDot } from "./backup-status-dot";
import { formatShortDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { formatShortDateTime, formatTimeAgo } from "~/client/lib/datetime";
import { Link } from "@tanstack/react-router";
export const BackupCard = ({ schedule }: { schedule: BackupSchedule }) => { export const BackupCard = ({ schedule }: { schedule: BackupSchedule }) => {
return ( return (
<Link key={schedule.id} to={`/backups/${schedule.id}`}> <Link key={schedule.id} to="/backups/$scheduleId" params={{ scheduleId: schedule.id.toString() }}>
<Card key={schedule.id} className="flex flex-col h-full"> <Card key={schedule.id} className="flex flex-col h-full">
<CardHeader className="pb-3 overflow-hidden"> <CardHeader className="pb-3 overflow-hidden">
<div className="flex items-center justify-between gap-2 w-full"> <div className="flex items-center justify-between gap-2 w-full">

View file

@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { Copy, Plus, Trash2 } from "lucide-react"; import { Copy, Plus, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
@ -19,9 +19,9 @@ import type { Repository } from "~/client/lib/types";
import { RepositoryIcon } from "~/client/components/repository-icon"; import { RepositoryIcon } from "~/client/components/repository-icon";
import { StatusDot } from "~/client/components/status-dot"; import { StatusDot } from "~/client/components/status-dot";
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { Link } from "react-router";
import { cn } from "~/client/lib/utils"; import { cn } from "~/client/lib/utils";
import type { GetScheduleMirrorsResponse } from "~/client/api-client"; import type { GetScheduleMirrorsResponse } from "~/client/api-client";
import { Link } from "@tanstack/react-router";
type Props = { type Props = {
scheduleId: number; scheduleId: number;
@ -39,13 +39,23 @@ type MirrorAssignment = {
}; };
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => { export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(new Map()); const map = new Map<string, MirrorAssignment>();
for (const mirror of initialData) {
map.set(mirror.repositoryId, {
repositoryId: mirror.repositoryId,
enabled: mirror.enabled,
lastCopyAt: mirror.lastCopyAt,
lastCopyStatus: mirror.lastCopyStatus,
lastCopyError: mirror.lastCopyError,
});
}
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(map);
const [hasChanges, setHasChanges] = useState(false); const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false); const [isAddingNew, setIsAddingNew] = useState(false);
const { data: currentMirrors } = useQuery({ const { data: currentMirrors } = useSuspenseQuery({
...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }), ...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }),
initialData,
}); });
const { data: compatibility } = useQuery({ const { data: compatibility } = useQuery({
@ -75,23 +85,6 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
return map; return map;
}, [compatibility]); }, [compatibility]);
useEffect(() => {
if (currentMirrors && !hasChanges) {
const map = new Map<string, MirrorAssignment>();
for (const mirror of currentMirrors) {
map.set(mirror.repositoryId, {
repositoryId: mirror.repositoryId,
enabled: mirror.enabled,
lastCopyAt: mirror.lastCopyAt,
lastCopyStatus: mirror.lastCopyStatus,
lastCopyError: mirror.lastCopyError,
});
}
setAssignments(map);
}
}, [currentMirrors, hasChanges]);
const addRepository = (repositoryId: string) => { const addRepository = (repositoryId: string) => {
const newAssignments = new Map(assignments); const newAssignments = new Map(assignments);
newAssignments.set(repositoryId, { newAssignments.set(repositoryId, {
@ -288,7 +281,8 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
<TableCell> <TableCell>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link <Link
to={`/repositories/${repository.shortId}`} to="/repositories/$repositoryId"
params={{ repositoryId: repository.id }}
className="hover:underline flex items-center gap-2" className="hover:underline flex items-center gap-2"
> >
<RepositoryIcon backend={repository.type} className="h-4 w-4" /> <RepositoryIcon backend={repository.type} className="h-4 w-4" />

View file

@ -31,7 +31,18 @@ type NotificationAssignment = {
}; };
export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialData }: Props) => { export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<number, NotificationAssignment>>(new Map()); const map = new Map<number, NotificationAssignment>();
for (const assignment of initialData) {
map.set(assignment.destinationId, {
destinationId: assignment.destinationId,
notifyOnStart: assignment.notifyOnStart,
notifyOnSuccess: assignment.notifyOnSuccess,
notifyOnWarning: assignment.notifyOnWarning,
notifyOnFailure: assignment.notifyOnFailure,
});
}
const [assignments, setAssignments] = useState<Map<number, NotificationAssignment>>(map);
const [hasChanges, setHasChanges] = useState(false); const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false); const [isAddingNew, setIsAddingNew] = useState(false);
@ -53,23 +64,6 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialD
}, },
}); });
useEffect(() => {
if (currentAssignments) {
const map = new Map<number, NotificationAssignment>();
for (const assignment of currentAssignments) {
map.set(assignment.destinationId, {
destinationId: assignment.destinationId,
notifyOnStart: assignment.notifyOnStart,
notifyOnSuccess: assignment.notifyOnSuccess,
notifyOnWarning: assignment.notifyOnWarning,
notifyOnFailure: assignment.notifyOnFailure,
});
}
setAssignments(map);
}
}, [currentAssignments]);
const addDestination = (destinationId: string) => { const addDestination = (destinationId: string) => {
const id = Number.parseInt(destinationId, 10); const id = Number.parseInt(destinationId, 10);
const newAssignments = new Map(assignments); const newAssignments = new Map(assignments);

View file

@ -19,7 +19,9 @@ import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner"; import { toast } from "sonner";
import { handleRepositoryError } from "~/client/lib/errors"; import { handleRepositoryError } from "~/client/lib/errors";
import { Link } from "react-router"; import { Link } from "react-router";
import { parseError } from "~/client/lib/errors";
import { formatShortDateTime, formatTimeAgo } from "~/client/lib/datetime"; import { formatShortDateTime, formatTimeAgo } from "~/client/lib/datetime";
import { Link } from "@tanstack/react-router";
type Props = { type Props = {
schedule: BackupSchedule; schedule: BackupSchedule;
@ -93,12 +95,20 @@ export const ScheduleSummary = (props: Props) => {
<div> <div>
<CardTitle>{schedule.name}</CardTitle> <CardTitle>{schedule.name}</CardTitle>
<CardDescription className="mt-1"> <CardDescription className="mt-1">
<Link to={`/volumes/${schedule.volume.shortId}`} className="hover:underline"> <Link
to="/volumes/$volumeId"
className="hover:underline"
params={{ volumeId: schedule.volume.shortId }}
>
<HardDrive className="inline h-4 w-4 mr-2" /> <HardDrive className="inline h-4 w-4 mr-2" />
<span>{schedule.volume.name}</span> <span>{schedule.volume.name}</span>
</Link> </Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<Link to={`/repositories/${schedule.repository.shortId}`} className="hover:underline"> <Link
to="/repositories/$repositoryId"
className="hover:underline"
params={{ repositoryId: schedule.repository.shortId }}
>
<Database className="inline h-4 w-4 mr-2 text-strong-accent" /> <Database className="inline h-4 w-4 mr-2 text-strong-accent" />
<span className="text-strong-accent">{schedule.repository.name}</span> <span className="text-strong-accent">{schedule.repository.name}</span>
</Link> </Link>

View file

@ -1,6 +1,5 @@
import { useId, useState } from "react"; import { useId, useState } from "react";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { redirect, useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { Save, X } from "lucide-react"; import { Save, X } from "lucide-react";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
@ -27,58 +26,40 @@ import { parseError, handleRepositoryError } from "~/client/lib/errors";
import { getCronExpression } from "~/utils/utils"; import { getCronExpression } from "~/utils/utils";
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form"; import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
import { ScheduleSummary } from "../components/schedule-summary"; import { ScheduleSummary } from "../components/schedule-summary";
import type { Route } from "./+types/backup-details";
import { SnapshotFileBrowser } from "../components/snapshot-file-browser"; import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
import { SnapshotTimeline } from "../components/snapshot-timeline"; import { SnapshotTimeline } from "../components/snapshot-timeline";
import {
getBackupSchedule,
getScheduleMirrors,
getScheduleNotifications,
listNotificationDestinations,
listRepositories,
} from "~/client/api-client";
import { ScheduleNotificationsConfig } from "../components/schedule-notifications-config"; import { ScheduleNotificationsConfig } from "../components/schedule-notifications-config";
import { ScheduleMirrorsConfig } from "../components/schedule-mirrors-config"; import { ScheduleMirrorsConfig } from "../components/schedule-mirrors-config";
import { cn } from "~/client/lib/utils"; import { cn } from "~/client/lib/utils";
import type {
BackupSchedule,
NotificationDestination,
Repository,
ScheduleMirror,
ScheduleNotification,
} from "~/client/lib/types";
import { useNavigate } from "@tanstack/react-router";
export const handle = { // export const handle = {
breadcrumb: (match: Route.MetaArgs) => { // breadcrumb: (match: Route.MetaArgs) => {
const data = match.loaderData; // const data = match.loaderData;
return [{ label: "Backups", href: "/backups" }, { label: data.schedule.name }]; // return [{ label: "Backups", href: "/backups" }, { label: data.schedule.name }];
}, // },
}; // };
type Props = {
export function meta(_: Route.MetaArgs) { loaderData: {
return [ schedule: BackupSchedule;
{ title: "Zerobyte - Backup Job Details" }, notifs: NotificationDestination[];
{ repos: Repository[];
name: "description", scheduleNotifs: ScheduleNotification[];
content: "View and manage backup job configuration, schedule, and snapshots.", mirrors: ScheduleMirror[];
},
];
}
export const clientLoader = async ({ params }: Route.LoaderArgs) => {
const [schedule, notifs, repos, scheduleNotifs, mirrors] = await Promise.all([
getBackupSchedule({ path: { scheduleId: params.id } }),
listNotificationDestinations(),
listRepositories(),
getScheduleNotifications({ path: { scheduleId: params.id } }),
getScheduleMirrors({ path: { scheduleId: params.id } }),
]);
if (!schedule.data) return redirect("/backups");
return {
schedule: schedule.data,
notifs: notifs.data,
repos: repos.data,
scheduleNotifs: scheduleNotifs.data,
scheduleMirrors: mirrors.data,
}; };
scheduleId: string;
}; };
export default function ScheduleDetailsPage({ params, loaderData }: Route.ComponentProps) { export function ScheduleDetailsPage(props: Props) {
const { loaderData, scheduleId } = props;
const navigate = useNavigate(); const navigate = useNavigate();
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const formId = useId(); const formId = useId();
@ -86,9 +67,8 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [snapshotToDelete, setSnapshotToDelete] = useState<string | null>(null); const [snapshotToDelete, setSnapshotToDelete] = useState<string | null>(null);
const { data: schedule } = useQuery({ const { data: schedule } = useSuspenseQuery({
...getBackupScheduleOptions({ path: { scheduleId: params.id } }), ...getBackupScheduleOptions({ path: { scheduleId } }),
initialData: loaderData.schedule,
}); });
const { const {
@ -136,7 +116,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
...deleteBackupScheduleMutation(), ...deleteBackupScheduleMutation(),
onSuccess: () => { onSuccess: () => {
toast.success("Backup schedule deleted successfully"); toast.success("Backup schedule deleted successfully");
void navigate("/backups"); void navigate({ to: "/backups" });
}, },
onError: (error) => { onError: (error) => {
toast.error("Failed to delete backup schedule", { description: parseError(error)?.message }); toast.error("Failed to delete backup schedule", { description: parseError(error)?.message });
@ -267,7 +247,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
scheduleId={schedule.id} scheduleId={schedule.id}
primaryRepositoryId={schedule.repositoryId} primaryRepositoryId={schedule.repositoryId}
repositories={loaderData.repos ?? []} repositories={loaderData.repos ?? []}
initialData={loaderData.scheduleMirrors ?? []} initialData={loaderData.mirrors ?? []}
/> />
</div> </div>
<SnapshotTimeline <SnapshotTimeline

View file

@ -1,4 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
@ -10,12 +10,10 @@ import {
} from "@dnd-kit/core"; } from "@dnd-kit/core";
import { arrayMove, SortableContext, sortableKeyboardCoordinates, rectSortingStrategy } from "@dnd-kit/sortable"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, rectSortingStrategy } from "@dnd-kit/sortable";
import { CalendarClock, Plus } from "lucide-react"; import { CalendarClock, Plus } from "lucide-react";
import { Link } from "react-router";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { EmptyState } from "~/client/components/empty-state"; import { EmptyState } from "~/client/components/empty-state";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { Card, CardContent } from "~/client/components/ui/card"; import { Card, CardContent } from "~/client/components/ui/card";
import type { Route } from "./+types/backups";
import { listBackupSchedules } from "~/client/api-client"; import { listBackupSchedules } from "~/client/api-client";
import { import {
listBackupSchedulesOptions, listBackupSchedulesOptions,
@ -23,31 +21,21 @@ import {
} from "~/client/api-client/@tanstack/react-query.gen"; } from "~/client/api-client/@tanstack/react-query.gen";
import { SortableCard } from "~/client/components/sortable-card"; import { SortableCard } from "~/client/components/sortable-card";
import { BackupCard } from "../components/backup-card"; import { BackupCard } from "../components/backup-card";
import { Link } from "@tanstack/react-router";
export const handle = { export const handle = {
breadcrumb: () => [{ label: "Backups" }], breadcrumb: () => [{ label: "Backups" }],
}; };
export function meta(_: Route.MetaArgs) {
return [
{ title: "Zerobyte - Backup Jobs" },
{
name: "description",
content: "Automate volume backups with scheduled jobs and retention policies.",
},
];
}
export const clientLoader = async () => { export const clientLoader = async () => {
const jobs = await listBackupSchedules(); const jobs = await listBackupSchedules();
if (jobs.data) return jobs.data; if (jobs.data) return jobs.data;
return []; return [];
}; };
export default function Backups({ loaderData }: Route.ComponentProps) { export function BackupsPage() {
const { data: schedules, isLoading } = useQuery({ const { data: schedules, isLoading } = useSuspenseQuery({
...listBackupSchedulesOptions(), ...listBackupSchedulesOptions(),
initialData: loaderData,
}); });
const [items, setItems] = useState(schedules?.map((s) => s.id) ?? []); const [items, setItems] = useState(schedules?.map((s) => s.id) ?? []);

View file

@ -1,7 +1,6 @@
import { useId, useState } from "react"; import { useId, useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { Database, HardDrive, Plus } from "lucide-react"; import { Database, HardDrive, Plus } from "lucide-react";
import { Link, useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
createBackupScheduleMutation, createBackupScheduleMutation,
@ -15,50 +14,30 @@ import { parseError } from "~/client/lib/errors";
import { EmptyState } from "~/client/components/empty-state"; import { EmptyState } from "~/client/components/empty-state";
import { getCronExpression } from "~/utils/utils"; import { getCronExpression } from "~/utils/utils";
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form"; import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
import type { Route } from "./+types/create-backup"; import { Link, useNavigate } from "@tanstack/react-router";
import { listRepositories, listVolumes } from "~/client/api-client";
export const handle = { // export const handle = {
breadcrumb: () => [{ label: "Backups", href: "/backups" }, { label: "Create" }], // breadcrumb: () => [{ label: "Backups", href: "/backups" }, { label: "Create" }],
}; // };
export function meta(_: Route.MetaArgs) { export function CreateBackupPage() {
return [
{ title: "Zerobyte - Create Backup Job" },
{
name: "description",
content: "Create a new automated backup job for your volumes.",
},
];
}
export const clientLoader = async () => {
const [volumes, repositories] = await Promise.all([listVolumes(), listRepositories()]);
if (volumes.data && repositories.data) return { volumes: volumes.data, repositories: repositories.data };
return { volumes: [], repositories: [] };
};
export default function CreateBackup({ loaderData }: Route.ComponentProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const formId = useId(); const formId = useId();
const [selectedVolumeId, setSelectedVolumeId] = useState<number | undefined>(); const [selectedVolumeId, setSelectedVolumeId] = useState<number | undefined>();
const { data: volumesData, isLoading: loadingVolumes } = useQuery({ const { data: volumesData, isLoading: loadingVolumes } = useSuspenseQuery({
...listVolumesOptions(), ...listVolumesOptions(),
initialData: loaderData.volumes,
}); });
const { data: repositoriesData } = useQuery({ const { data: repositoriesData } = useSuspenseQuery({
...listRepositoriesOptions(), ...listRepositoriesOptions(),
initialData: loaderData.repositories,
}); });
const createSchedule = useMutation({ const createSchedule = useMutation({
...createBackupScheduleMutation(), ...createBackupScheduleMutation(),
onSuccess: (data) => { onSuccess: (data) => {
toast.success("Backup job created successfully"); toast.success("Backup job created successfully");
void navigate(`/backups/${data.id}`); void navigate({ to: `/backups/${data.id}` });
}, },
onError: (error) => { onError: (error) => {
toast.error("Failed to create backup job", { toast.error("Failed to create backup job", {

View file

@ -1,7 +1,6 @@
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { Database, Plus } from "lucide-react"; import { Database, 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 { createRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { createRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen";
import { import {
@ -11,24 +10,14 @@ import {
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-repository";
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: "Repositories", href: "/repositories" }, { label: "Create" }], breadcrumb: () => [{ label: "Repositories", href: "/repositories" }, { label: "Create" }],
}; };
export function meta(_: Route.MetaArgs) { export function CreateRepositoryPage() {
return [
{ title: "Zerobyte - Create Repository" },
{
name: "description",
content: "Create a new backup repository with encryption and compression.",
},
];
}
export default function CreateRepository() {
const navigate = useNavigate(); const navigate = useNavigate();
const formId = useId(); const formId = useId();
@ -36,7 +25,7 @@ export default function CreateRepository() {
...createRepositoryMutation(), ...createRepositoryMutation(),
onSuccess: (data) => { onSuccess: (data) => {
toast.success("Repository created successfully"); toast.success("Repository created successfully");
void navigate(`/repositories/${data.repository.shortId}`); void navigate({ to: `/repositories/${data.repository.shortId}` });
}, },
}); });
@ -78,7 +67,7 @@ export default function CreateRepository() {
loading={createRepository.isPending} loading={createRepository.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("/repositories")}> <Button type="button" variant="secondary" onClick={() => navigate({ to: "/repositories" })}>
Cancel Cancel
</Button> </Button>
<Button type="submit" form={formId} loading={createRepository.isPending}> <Button type="submit" form={formId} loading={createRepository.isPending}>

View file

@ -76,13 +76,13 @@ export function VolumesPage() {
<div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4"> <div className="flex flex-col lg:flex-row items-stretch lg:items-center gap-2 md:justify-between p-4 bg-card-header py-4">
<span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap "> <span className="flex flex-col sm:flex-row items-stretch md:items-center gap-0 flex-wrap ">
<Input <Input
className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px" className="w-full lg:w-45 min-w-45 -mr-px -mt-px"
placeholder="Search volumes…" placeholder="Search volumes…"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
/> />
<Select value={statusFilter} onValueChange={setStatusFilter}> <Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mr-px -mt-px"> <SelectTrigger className="w-full lg:w-45 min-w-45 -mr-px -mt-px">
<SelectValue placeholder="All status" /> <SelectValue placeholder="All status" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -92,7 +92,7 @@ export function VolumesPage() {
</SelectContent> </SelectContent>
</Select> </Select>
<Select value={backendFilter} onValueChange={setBackendFilter}> <Select value={backendFilter} onValueChange={setBackendFilter}>
<SelectTrigger className="w-full lg:w-[180px] min-w-[180px] -mt-px"> <SelectTrigger className="w-full lg:w-45 min-w-45 -mt-px">
<SelectValue placeholder="All backends" /> <SelectValue placeholder="All backends" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>

View file

@ -16,8 +16,12 @@ import { Route as authLoginRouteImport } from './routes/(auth)/login'
import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/download-recovery-key' import { Route as authDownloadRecoveryKeyRouteImport } from './routes/(auth)/download-recovery-key'
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 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 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 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'
const dashboardRouteRoute = dashboardRouteRouteImport.update({ const dashboardRouteRoute = dashboardRouteRouteImport.update({
@ -55,18 +59,40 @@ const dashboardRepositoriesIndexRoute =
path: '/repositories/', path: '/repositories/',
getParentRoute: () => dashboardRouteRoute, getParentRoute: () => dashboardRouteRoute,
} as any) } as any)
const dashboardBackupsIndexRoute = dashboardBackupsIndexRouteImport.update({
id: '/backups/',
path: '/backups/',
getParentRoute: () => dashboardRouteRoute,
} as any)
const dashboardVolumesVolumeIdRoute = const dashboardVolumesVolumeIdRoute =
dashboardVolumesVolumeIdRouteImport.update({ dashboardVolumesVolumeIdRouteImport.update({
id: '/volumes/$volumeId', id: '/volumes/$volumeId',
path: '/volumes/$volumeId', path: '/volumes/$volumeId',
getParentRoute: () => dashboardRouteRoute, getParentRoute: () => dashboardRouteRoute,
} as any) } as any)
const dashboardRepositoriesCreateRoute =
dashboardRepositoriesCreateRouteImport.update({
id: '/repositories/create',
path: '/repositories/create',
getParentRoute: () => dashboardRouteRoute,
} as any)
const dashboardRepositoriesRepositoryIdRoute = const dashboardRepositoriesRepositoryIdRoute =
dashboardRepositoriesRepositoryIdRouteImport.update({ dashboardRepositoriesRepositoryIdRouteImport.update({
id: '/repositories/$repositoryId', id: '/repositories/$repositoryId',
path: '/repositories/$repositoryId', path: '/repositories/$repositoryId',
getParentRoute: () => dashboardRouteRoute, getParentRoute: () => dashboardRouteRoute,
} as any) } as any)
const dashboardBackupsCreateRoute = dashboardBackupsCreateRouteImport.update({
id: '/backups/create',
path: '/backups/create',
getParentRoute: () => dashboardRouteRoute,
} as any)
const dashboardBackupsScheduleIdRoute =
dashboardBackupsScheduleIdRouteImport.update({
id: '/backups/$scheduleId',
path: '/backups/$scheduleId',
getParentRoute: () => dashboardRouteRoute,
} as any)
const dashboardRepositoriesRepoIdSnapshotIdRoute = const dashboardRepositoriesRepoIdSnapshotIdRoute =
dashboardRepositoriesRepoIdSnapshotIdRouteImport.update({ dashboardRepositoriesRepoIdSnapshotIdRouteImport.update({
id: '/repositories/$repoId/$snapshotId', id: '/repositories/$repoId/$snapshotId',
@ -79,8 +105,12 @@ export interface FileRoutesByFullPath {
'/download-recovery-key': typeof authDownloadRecoveryKeyRoute '/download-recovery-key': typeof authDownloadRecoveryKeyRoute
'/login': typeof authLoginRoute '/login': typeof authLoginRoute
'/onboarding': typeof authOnboardingRoute '/onboarding': typeof authOnboardingRoute
'/backups/$scheduleId': typeof dashboardBackupsScheduleIdRoute
'/backups/create': typeof dashboardBackupsCreateRoute
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute '/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
'/repositories/create': typeof dashboardRepositoriesCreateRoute
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute '/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
'/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
@ -90,8 +120,12 @@ export interface FileRoutesByTo {
'/download-recovery-key': typeof authDownloadRecoveryKeyRoute '/download-recovery-key': typeof authDownloadRecoveryKeyRoute
'/login': typeof authLoginRoute '/login': typeof authLoginRoute
'/onboarding': typeof authOnboardingRoute '/onboarding': typeof authOnboardingRoute
'/backups/$scheduleId': typeof dashboardBackupsScheduleIdRoute
'/backups/create': typeof dashboardBackupsCreateRoute
'/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute '/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
'/repositories/create': typeof dashboardRepositoriesCreateRoute
'/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute '/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
'/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
@ -103,8 +137,12 @@ export interface FileRoutesById {
'/(auth)/download-recovery-key': typeof authDownloadRecoveryKeyRoute '/(auth)/download-recovery-key': typeof authDownloadRecoveryKeyRoute
'/(auth)/login': typeof authLoginRoute '/(auth)/login': typeof authLoginRoute
'/(auth)/onboarding': typeof authOnboardingRoute '/(auth)/onboarding': typeof authOnboardingRoute
'/(dashboard)/backups/$scheduleId': typeof dashboardBackupsScheduleIdRoute
'/(dashboard)/backups/create': typeof dashboardBackupsCreateRoute
'/(dashboard)/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute '/(dashboard)/repositories/$repositoryId': typeof dashboardRepositoriesRepositoryIdRoute
'/(dashboard)/repositories/create': typeof dashboardRepositoriesCreateRoute
'/(dashboard)/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute '/(dashboard)/volumes/$volumeId': typeof dashboardVolumesVolumeIdRoute
'/(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
@ -116,8 +154,12 @@ export interface FileRouteTypes {
| '/download-recovery-key' | '/download-recovery-key'
| '/login' | '/login'
| '/onboarding' | '/onboarding'
| '/backups/$scheduleId'
| '/backups/create'
| '/repositories/$repositoryId' | '/repositories/$repositoryId'
| '/repositories/create'
| '/volumes/$volumeId' | '/volumes/$volumeId'
| '/backups/'
| '/repositories/' | '/repositories/'
| '/volumes/' | '/volumes/'
| '/repositories/$repoId/$snapshotId' | '/repositories/$repoId/$snapshotId'
@ -127,8 +169,12 @@ export interface FileRouteTypes {
| '/download-recovery-key' | '/download-recovery-key'
| '/login' | '/login'
| '/onboarding' | '/onboarding'
| '/backups/$scheduleId'
| '/backups/create'
| '/repositories/$repositoryId' | '/repositories/$repositoryId'
| '/repositories/create'
| '/volumes/$volumeId' | '/volumes/$volumeId'
| '/backups'
| '/repositories' | '/repositories'
| '/volumes' | '/volumes'
| '/repositories/$repoId/$snapshotId' | '/repositories/$repoId/$snapshotId'
@ -139,8 +185,12 @@ export interface FileRouteTypes {
| '/(auth)/download-recovery-key' | '/(auth)/download-recovery-key'
| '/(auth)/login' | '/(auth)/login'
| '/(auth)/onboarding' | '/(auth)/onboarding'
| '/(dashboard)/backups/$scheduleId'
| '/(dashboard)/backups/create'
| '/(dashboard)/repositories/$repositoryId' | '/(dashboard)/repositories/$repositoryId'
| '/(dashboard)/repositories/create'
| '/(dashboard)/volumes/$volumeId' | '/(dashboard)/volumes/$volumeId'
| '/(dashboard)/backups/'
| '/(dashboard)/repositories/' | '/(dashboard)/repositories/'
| '/(dashboard)/volumes/' | '/(dashboard)/volumes/'
| '/(dashboard)/repositories/$repoId/$snapshotId' | '/(dashboard)/repositories/$repoId/$snapshotId'
@ -205,6 +255,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardRepositoriesIndexRouteImport preLoaderRoute: typeof dashboardRepositoriesIndexRouteImport
parentRoute: typeof dashboardRouteRoute parentRoute: typeof dashboardRouteRoute
} }
'/(dashboard)/backups/': {
id: '/(dashboard)/backups/'
path: '/backups'
fullPath: '/backups/'
preLoaderRoute: typeof dashboardBackupsIndexRouteImport
parentRoute: typeof dashboardRouteRoute
}
'/(dashboard)/volumes/$volumeId': { '/(dashboard)/volumes/$volumeId': {
id: '/(dashboard)/volumes/$volumeId' id: '/(dashboard)/volumes/$volumeId'
path: '/volumes/$volumeId' path: '/volumes/$volumeId'
@ -212,6 +269,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardVolumesVolumeIdRouteImport preLoaderRoute: typeof dashboardVolumesVolumeIdRouteImport
parentRoute: typeof dashboardRouteRoute parentRoute: typeof dashboardRouteRoute
} }
'/(dashboard)/repositories/create': {
id: '/(dashboard)/repositories/create'
path: '/repositories/create'
fullPath: '/repositories/create'
preLoaderRoute: typeof dashboardRepositoriesCreateRouteImport
parentRoute: typeof dashboardRouteRoute
}
'/(dashboard)/repositories/$repositoryId': { '/(dashboard)/repositories/$repositoryId': {
id: '/(dashboard)/repositories/$repositoryId' id: '/(dashboard)/repositories/$repositoryId'
path: '/repositories/$repositoryId' path: '/repositories/$repositoryId'
@ -219,6 +283,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardRepositoriesRepositoryIdRouteImport preLoaderRoute: typeof dashboardRepositoriesRepositoryIdRouteImport
parentRoute: typeof dashboardRouteRoute parentRoute: typeof dashboardRouteRoute
} }
'/(dashboard)/backups/create': {
id: '/(dashboard)/backups/create'
path: '/backups/create'
fullPath: '/backups/create'
preLoaderRoute: typeof dashboardBackupsCreateRouteImport
parentRoute: typeof dashboardRouteRoute
}
'/(dashboard)/backups/$scheduleId': {
id: '/(dashboard)/backups/$scheduleId'
path: '/backups/$scheduleId'
fullPath: '/backups/$scheduleId'
preLoaderRoute: typeof dashboardBackupsScheduleIdRouteImport
parentRoute: typeof dashboardRouteRoute
}
'/(dashboard)/repositories/$repoId/$snapshotId': { '/(dashboard)/repositories/$repoId/$snapshotId': {
id: '/(dashboard)/repositories/$repoId/$snapshotId' id: '/(dashboard)/repositories/$repoId/$snapshotId'
path: '/repositories/$repoId/$snapshotId' path: '/repositories/$repoId/$snapshotId'
@ -230,17 +308,25 @@ declare module '@tanstack/react-router' {
} }
interface dashboardRouteRouteChildren { interface dashboardRouteRouteChildren {
dashboardBackupsScheduleIdRoute: typeof dashboardBackupsScheduleIdRoute
dashboardBackupsCreateRoute: typeof dashboardBackupsCreateRoute
dashboardRepositoriesRepositoryIdRoute: typeof dashboardRepositoriesRepositoryIdRoute dashboardRepositoriesRepositoryIdRoute: typeof dashboardRepositoriesRepositoryIdRoute
dashboardRepositoriesCreateRoute: typeof dashboardRepositoriesCreateRoute
dashboardVolumesVolumeIdRoute: typeof dashboardVolumesVolumeIdRoute dashboardVolumesVolumeIdRoute: typeof dashboardVolumesVolumeIdRoute
dashboardBackupsIndexRoute: typeof dashboardBackupsIndexRoute
dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute dashboardRepositoriesIndexRoute: typeof dashboardRepositoriesIndexRoute
dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute dashboardVolumesIndexRoute: typeof dashboardVolumesIndexRoute
dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute dashboardRepositoriesRepoIdSnapshotIdRoute: typeof dashboardRepositoriesRepoIdSnapshotIdRoute
} }
const dashboardRouteRouteChildren: dashboardRouteRouteChildren = { const dashboardRouteRouteChildren: dashboardRouteRouteChildren = {
dashboardBackupsScheduleIdRoute: dashboardBackupsScheduleIdRoute,
dashboardBackupsCreateRoute: dashboardBackupsCreateRoute,
dashboardRepositoriesRepositoryIdRoute: dashboardRepositoriesRepositoryIdRoute:
dashboardRepositoriesRepositoryIdRoute, dashboardRepositoriesRepositoryIdRoute,
dashboardRepositoriesCreateRoute: dashboardRepositoriesCreateRoute,
dashboardVolumesVolumeIdRoute: dashboardVolumesVolumeIdRoute, dashboardVolumesVolumeIdRoute: dashboardVolumesVolumeIdRoute,
dashboardBackupsIndexRoute: dashboardBackupsIndexRoute,
dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute, dashboardRepositoriesIndexRoute: dashboardRepositoriesIndexRoute,
dashboardVolumesIndexRoute: dashboardVolumesIndexRoute, dashboardVolumesIndexRoute: dashboardVolumesIndexRoute,
dashboardRepositoriesRepoIdSnapshotIdRoute: dashboardRepositoriesRepoIdSnapshotIdRoute:

View file

@ -0,0 +1,47 @@
import { createFileRoute } from "@tanstack/react-router";
import {
getBackupScheduleOptions,
getScheduleMirrorsOptions,
getScheduleNotificationsOptions,
listNotificationDestinationsOptions,
listRepositoriesOptions,
listSnapshotsOptions,
} from "~/client/api-client/@tanstack/react-query.gen";
import { ScheduleDetailsPage } from "~/client/modules/backups/routes/backup-details";
export const Route = createFileRoute("/(dashboard)/backups/$scheduleId")({
component: RouteComponent,
loader: async ({ params, context }) => {
const { scheduleId } = params;
const [schedule, notifs, repos, scheduleNotifs, mirrors] = await Promise.all([
context.queryClient.ensureQueryData({ ...getBackupScheduleOptions({ path: { scheduleId } }) }),
context.queryClient.ensureQueryData({ ...listNotificationDestinationsOptions() }),
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { scheduleId } }) }),
context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { scheduleId } }) }),
]);
context.queryClient.prefetchQuery({
...listSnapshotsOptions({ path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } }),
});
return { schedule, notifs, repos, scheduleNotifs, mirrors };
},
head: ({ loaderData }) => ({
meta: [
{ title: `Zerobyte - ${loaderData?.schedule.name || "Backup Job Details"}` },
{
name: "description",
content: "View and manage backup job configuration, schedule, and snapshots.",
},
],
}),
});
function RouteComponent() {
const loaderData = Route.useLoaderData();
const { scheduleId } = Route.useParams();
return <ScheduleDetailsPage loaderData={loaderData} scheduleId={scheduleId} />;
}

View file

@ -0,0 +1,17 @@
import { createFileRoute } from "@tanstack/react-router";
import { listRepositoriesOptions, listVolumesOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { CreateBackupPage } from "~/client/modules/backups/routes/create-backup";
export const Route = createFileRoute("/(dashboard)/backups/create")({
loader: async ({ context }) => {
await Promise.all([
context.queryClient.ensureQueryData({ ...listVolumesOptions() }),
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
]);
},
component: RouteComponent,
});
function RouteComponent() {
return <CreateBackupPage />;
}

View file

@ -0,0 +1,25 @@
import { createFileRoute } from "@tanstack/react-router";
import { listBackupSchedulesOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { BackupsPage } from "~/client/modules/backups/routes/backups";
export const Route = createFileRoute("/(dashboard)/backups/")({
component: RouteComponent,
loader: async ({ context }) => {
await context.queryClient.ensureQueryData({
...listBackupSchedulesOptions(),
});
},
head: () => ({
meta: [
{ title: "Zerobyte - Backup Jobs" },
{
name: "description",
content: "Automate volume backups with scheduled jobs and retention policies.",
},
],
}),
});
function RouteComponent() {
return <BackupsPage />;
}

View file

@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { CreateRepositoryPage } from "~/client/modules/repositories/routes/create-repository";
export const Route = createFileRoute("/(dashboard)/repositories/create")({
component: RouteComponent,
head: () => ({
meta: [
{ title: "Zerobyte - Create Repository" },
{
name: "description",
content: "Create a new backup repository with encryption and compression.",
},
],
}),
});
function RouteComponent() {
return <CreateRepositoryPage />;
}

View file

@ -1,4 +1,3 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite";
@ -10,13 +9,13 @@ export default defineConfig({
plugins: [ plugins: [
tailwindcss(), tailwindcss(),
tsconfigPaths(), tsconfigPaths(),
// babel({ babel({
// filter: /\.[jt]sx?$/, filter: /\.[jt]sx?$/,
// babelConfig: { babelConfig: {
// presets: ["@babel/preset-typescript"], presets: ["@babel/preset-typescript"],
// plugins: [["babel-plugin-react-compiler"]], plugins: [["babel-plugin-react-compiler"]],
// }, },
// }), }),
tanstackStart({ tanstackStart({
srcDirectory: "app", srcDirectory: "app",
router: { router: {