style: responsive fixes (#282)
* style(backups): use container queries to avoid overflow when menu is open * style: fix breadcrumbs breaking the layout on smaller devices * style: improve wording and error displays * chore: wording
This commit is contained in:
parent
b8fe2a985b
commit
1b88fa6bd6
14 changed files with 78 additions and 69 deletions
|
|
@ -38,7 +38,7 @@ export function AppBreadcrumb() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb className="min-w-0">
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
{breadcrumbs.map((breadcrumb, index) => {
|
{breadcrumbs.map((breadcrumb, index) => {
|
||||||
const isLast = index === breadcrumbs.length - 1;
|
const isLast = index === breadcrumbs.length - 1;
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ export default function Layout({ loaderData }: Route.ComponentProps) {
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<div className="w-full relative flex flex-col h-screen overflow-hidden">
|
<div className="w-full relative flex flex-col h-screen overflow-hidden">
|
||||||
<header className="z-50 bg-card-header border-b border-border/50 shrink-0">
|
<header className="z-50 bg-card-header border-b border-border/50 shrink-0">
|
||||||
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container">
|
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container gap-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4 min-w-0">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
<AppBreadcrumb />
|
<AppBreadcrumb />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,7 +76,7 @@ export default function Layout({ loaderData }: Route.ComponentProps) {
|
||||||
</header>
|
</header>
|
||||||
<div className="main-content flex-1 overflow-y-auto">
|
<div className="main-content flex-1 overflow-y-auto">
|
||||||
<GridBackground>
|
<GridBackground>
|
||||||
<main className="flex flex-col p-2 pb-6 pt-2 sm:p-8 sm:pt-6 mx-auto">
|
<main className="flex flex-col p-2 pb-6 pt-2 sm:p-8 sm:pt-6 mx-auto @container">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</GridBackground>
|
</GridBackground>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "~/client/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
|
|
||||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" className={cn("min-w-0", className)} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
|
|
@ -13,7 +13,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
<ol
|
<ol
|
||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm wrap-break-words sm:gap-2.5",
|
"text-muted-foreground flex items-center gap-1.5 text-sm sm:gap-2.5 min-w-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -22,7 +22,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />;
|
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5 min-w-0", className)} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbLink({
|
function BreadcrumbLink({
|
||||||
|
|
@ -35,7 +35,11 @@ function BreadcrumbLink({
|
||||||
const Comp = asChild ? Slot : "a";
|
const Comp = asChild ? Slot : "a";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} />
|
<Comp
|
||||||
|
data-slot="breadcrumb-link"
|
||||||
|
className={cn("hover:text-foreground transition-colors truncate", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +50,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
role="link"
|
role="link"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
className={cn("text-foreground font-normal", className)}
|
className={cn("text-foreground font-normal truncate", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@ function Card({ className, children, ...props }: React.ComponentProps<"div">) {
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" className="pointer-events-none absolute inset-0 z-10 select-none">
|
<span aria-hidden="true" className="pointer-events-none absolute inset-0 z-10 select-none">
|
||||||
<span className="absolute left-[-2px] top-[-2px] h-0.5 w-4 bg-white/80" />
|
<span className="absolute -left-0.5 -top-0.5 h-0.5 w-4 bg-white/80" />
|
||||||
<span className="absolute left-[-2px] top-[-2px] h-4 w-0.5 bg-white/80" />
|
<span className="absolute -left-0.5 -top-0.5 h-4 w-0.5 bg-white/80" />
|
||||||
<span className="absolute right-[-2px] top-[-2px] h-0.5 w-4 bg-white/80" />
|
<span className="absolute -right-0.5 -top-0.5 h-0.5 w-4 bg-white/80" />
|
||||||
<span className="absolute right-[-2px] top-[-2px] h-4 w-0.5 bg-white/80" />
|
<span className="absolute -right-0.5 -top-0.5 h-4 w-0.5 bg-white/80" />
|
||||||
<span className="absolute left-[-2px] bottom-[-2px] h-0.5 w-4 bg-white/80" />
|
<span className="absolute -left-0.5 -bottom-0.5 h-0.5 w-4 bg-white/80" />
|
||||||
<span className="absolute left-[-2px] bottom-[-2px] h-4 w-0.5 bg-white/80" />
|
<span className="absolute -left-0.5 -bottom-0.5 h-4 w-0.5 bg-white/80" />
|
||||||
<span className="absolute right-[-2px] bottom-[-2px] h-0.5 w-4 bg-white/80" />
|
<span className="absolute -right-0.5 -bottom-0.5 h-0.5 w-4 bg-white/80" />
|
||||||
<span className="absolute right-[-2px] bottom-[-2px] h-4 w-0.5 bg-white/80" />
|
<span className="absolute -right-0.5 -bottom-0.5 h-4 w-0.5 bg-white/80" />
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,12 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
Schedule automated backups of <strong>{volume.name}</strong> to a secure repository.
|
Schedule automated backups of <strong>{volume.name}</strong> to a secure repository.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-6 md:grid-cols-2">
|
<CardContent className="grid gap-6 @md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="@md:col-span-2">
|
||||||
<FormLabel>Backup name</FormLabel>
|
<FormLabel>Backup name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="My backup" {...field} />
|
<Input placeholder="My backup" {...field} />
|
||||||
|
|
@ -215,7 +215,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="repositoryId"
|
name="repositoryId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="@md:col-span-2">
|
||||||
<FormLabel>Backup repository</FormLabel>
|
<FormLabel>Backup repository</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select {...field} onValueChange={field.onChange}>
|
<Select {...field} onValueChange={field.onChange}>
|
||||||
|
|
@ -300,7 +300,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="weeklyDay"
|
name="weeklyDay"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="@md:col-span-2">
|
||||||
<FormLabel>Execution day</FormLabel>
|
<FormLabel>Execution day</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select {...field} onValueChange={field.onChange}>
|
<Select {...field} onValueChange={field.onChange}>
|
||||||
|
|
@ -327,7 +327,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="monthlyDays"
|
name="monthlyDays"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="@md:col-span-2">
|
||||||
<FormLabel>Days of the month</FormLabel>
|
<FormLabel>Days of the month</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="grid grid-cols-7 gap-4 w-max">
|
<div className="grid grid-cols-7 gap-4 w-max">
|
||||||
|
|
@ -512,7 +512,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||||
<CardTitle>Retention policy</CardTitle>
|
<CardTitle>Retention policy</CardTitle>
|
||||||
<CardDescription>Define how many snapshots to keep. Leave empty to keep all.</CardDescription>
|
<CardDescription>Define how many snapshots to keep. Leave empty to keep all.</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
<CardContent className="grid gap-4 @md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="keepLast"
|
name="keepLast"
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
|
||||||
<Copy className="h-5 w-5" />
|
<Copy className="h-5 w-5" />
|
||||||
Mirror Repositories
|
Mirror Repositories
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription className="hidden @md:block mt-1">
|
||||||
Configure secondary repositories where snapshots will be automatically copied after each backup
|
Configure secondary repositories where snapshots will be automatically copied after each backup
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -270,9 +270,9 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Repository</TableHead>
|
<TableHead>Repository</TableHead>
|
||||||
<TableHead className="text-center w-[100px]">Enabled</TableHead>
|
<TableHead className="text-center w-25">Enabled</TableHead>
|
||||||
<TableHead className="w-[180px]">Last Copy</TableHead>
|
<TableHead className="w-45">Last Copy</TableHead>
|
||||||
<TableHead className="w-[50px]"></TableHead>
|
<TableHead className="w-12.5"></TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,9 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations }: Props)
|
||||||
<Bell className="h-5 w-5" />
|
<Bell className="h-5 w-5" />
|
||||||
Notifications
|
Notifications
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Configure which notifications to send for this backup schedule</CardDescription>
|
<CardDescription className="hidden @md:block mt-1">
|
||||||
|
Configure which notifications to send for this backup schedule
|
||||||
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
{!isAddingNew && availableDestinations.length > 0 && (
|
{!isAddingNew && availableDestinations.length > 0 && (
|
||||||
<Button variant="outline" size="sm" onClick={() => setIsAddingNew(true)}>
|
<Button variant="outline" size="sm" onClick={() => setIsAddingNew(true)}>
|
||||||
|
|
@ -198,11 +200,11 @@ export const ScheduleNotificationsConfig = ({ scheduleId, destinations }: Props)
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Destination</TableHead>
|
<TableHead>Destination</TableHead>
|
||||||
<TableHead className="text-center w-[100px]">Start</TableHead>
|
<TableHead className="text-center w-25">Start</TableHead>
|
||||||
<TableHead className="text-center w-[100px]">Success</TableHead>
|
<TableHead className="text-center w-25">Success</TableHead>
|
||||||
<TableHead className="text-center w-[100px]">Warnings</TableHead>
|
<TableHead className="text-center w-25">Warnings</TableHead>
|
||||||
<TableHead className="text-center w-[100px]">Failures</TableHead>
|
<TableHead className="text-center w-25">Failures</TableHead>
|
||||||
<TableHead className="w-[50px]"></TableHead>
|
<TableHead className="w-12.5"></TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Card>
|
<Card>
|
||||||
<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>{schedule.name}</CardTitle>
|
<CardTitle>{schedule.name}</CardTitle>
|
||||||
<CardDescription className="mt-1">
|
<CardDescription className="mt-1">
|
||||||
|
|
@ -96,7 +96,7 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
</Link>
|
</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">
|
||||||
<OnOff
|
<OnOff
|
||||||
isOn={schedule.enabled}
|
isOn={schedule.enabled}
|
||||||
toggle={handleToggleEnabled}
|
toggle={handleToggleEnabled}
|
||||||
|
|
@ -105,16 +105,16 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row gap-2">
|
<div className="flex flex-col @lg:flex-row gap-2">
|
||||||
{schedule.lastBackupStatus === "in_progress" ? (
|
{schedule.lastBackupStatus === "in_progress" ? (
|
||||||
<Button variant="destructive" size="sm" onClick={handleStopBackup} className="w-full sm:w-auto">
|
<Button variant="destructive" size="sm" onClick={handleStopBackup} className="w-full @md:w-auto">
|
||||||
<Square className="h-4 w-4 mr-2" />
|
<Square className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Stop backup</span>
|
<span>Stop backup</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="default" size="sm" onClick={handleRunBackupNow} className="w-full sm:w-auto">
|
<Button variant="default" size="sm" onClick={handleRunBackupNow} className="w-full @md:w-auto">
|
||||||
<Play className="h-4 w-4 mr-2" />
|
<Play className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Backup now</span>
|
<span>Backup now</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{schedule.retentionPolicy && (
|
{schedule.retentionPolicy && (
|
||||||
|
|
@ -123,28 +123,28 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
size="sm"
|
size="sm"
|
||||||
loading={runForget.isPending}
|
loading={runForget.isPending}
|
||||||
onClick={() => setShowForgetConfirm(true)}
|
onClick={() => setShowForgetConfirm(true)}
|
||||||
className="w-full sm:w-auto"
|
className="w-full @md:w-auto"
|
||||||
>
|
>
|
||||||
<Eraser className="h-4 w-4 mr-2" />
|
<Eraser className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Run cleanup</span>
|
<span>Run cleanup</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="outline" size="sm" onClick={() => setIsEditMode(true)} className="w-full sm:w-auto">
|
<Button variant="outline" size="sm" onClick={() => setIsEditMode(true)} className="w-full @md:w-auto">
|
||||||
<Pencil className="h-4 w-4 mr-2" />
|
<Pencil className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Edit schedule</span>
|
<span>Edit schedule</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowDeleteConfirm(true)}
|
onClick={() => setShowDeleteConfirm(true)}
|
||||||
className="text-destructive hover:text-destructive w-full sm:w-auto"
|
className="text-destructive hover:text-destructive w-full @md:w-auto"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 mr-2" />
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
<span className="sm:inline">Delete</span>
|
<span>Delete</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
<CardContent className="grid gap-4 grid-cols-1 @md:grid-cols-2 @lg:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs uppercase text-muted-foreground">Schedule</p>
|
<p className="text-xs uppercase text-muted-foreground">Schedule</p>
|
||||||
<p className="font-medium">{summary.scheduleLabel}</p>
|
<p className="font-medium">{summary.scheduleLabel}</p>
|
||||||
|
|
@ -178,19 +178,21 @@ export const ScheduleSummary = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{schedule.lastBackupStatus === "warning" && (
|
{schedule.lastBackupStatus === "warning" && (
|
||||||
<div className="md:col-span-2 lg:col-span-4">
|
<div className="@md:col-span-2 @lg:col-span-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Warning Details</p>
|
<p className="text-xs uppercase text-muted-foreground">Warning Details</p>
|
||||||
<p className="font-mono text-sm text-yellow-600 whitespace-pre-wrap break-all">
|
<p className="font-mono text-sm text-yellow-600 whitespace-pre-wrap wrap-break-word">
|
||||||
{schedule.lastBackupError ??
|
{schedule.lastBackupError ??
|
||||||
"Last backup completed with warnings. Check your container logs for more details."}
|
"Last backup completed with warnings. Check your container logs for more details."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{schedule.lastBackupError && (
|
{schedule.lastBackupError && schedule.lastBackupStatus === "error" && (
|
||||||
<div className="md:col-span-2 lg:col-span-4">
|
<div className="@md:col-span-2 @lg:col-span-4">
|
||||||
<p className="text-xs uppercase text-muted-foreground">Error Details</p>
|
<p className="text-xs uppercase text-muted-foreground">Error details</p>
|
||||||
<p className="font-mono text-sm text-red-600 whitespace-pre-wrap break-all">{schedule.lastBackupError}</p>
|
<p className="font-mono text-sm text-red-600 whitespace-pre-wrap wrap-break-word">
|
||||||
|
{schedule.lastBackupError}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { cn } from "~/client/lib/utils";
|
import { cn } from "~/client/lib/utils";
|
||||||
import { Card } from "~/client/components/ui/card";
|
import { Card, CardContent } from "~/client/components/ui/card";
|
||||||
import { ByteSize } from "~/client/components/bytes-size";
|
import { ByteSize } from "~/client/components/bytes-size";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import type { ListSnapshotsResponse } from "~/client/api-client";
|
import type { ListSnapshotsResponse } from "~/client/api-client";
|
||||||
|
|
@ -24,9 +24,9 @@ export const SnapshotTimeline = (props: Props) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="flex items-center justify-center h-24 p-4 text-center">
|
<CardContent className="flex items-center justify-center text-center">
|
||||||
<p className="text-destructive">Error loading snapshots: {error}</p>
|
<p className="text-destructive text-sm">{error}</p>
|
||||||
</div>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
|
||||||
<div className="container mx-auto space-y-6">
|
<div className="container mx-auto space-y-6">
|
||||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||||
<SortableContext items={items} strategy={rectSortingStrategy}>
|
<SortableContext items={items} strategy={rectSortingStrategy}>
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 auto-rows-fr">
|
<div className="grid gap-4 @md:grid-cols-1 @lg:grid-cols-2 @2xl:grid-cols-3 auto-rows-fr">
|
||||||
{items.map((id) => {
|
{items.map((id) => {
|
||||||
const schedule = scheduleMap.get(id);
|
const schedule = scheduleMap.get(id);
|
||||||
if (!schedule) return null;
|
if (!schedule) return null;
|
||||||
|
|
|
||||||
|
|
@ -226,12 +226,13 @@ export const CreateRepositoryForm = ({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="default">Use Zerobyte's password</SelectItem>
|
<SelectItem value="default">Use the existing recovery key</SelectItem>
|
||||||
<SelectItem value="custom">Enter password manually</SelectItem>
|
<SelectItem value="custom">Enter password manually</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Choose whether to use Zerobyte's master password or enter a custom password for the existing repository.
|
Choose whether to use Zerobyte's recovery key (which you downloaded when creating your account) or enter
|
||||||
|
a custom password for the existing repository.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ export const RepositoryInfoTabContent = ({ repository }: Props) => {
|
||||||
<h3 className="text-lg font-semibold text-red-500">Last Error</h3>
|
<h3 className="text-lg font-semibold text-red-500">Last Error</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-red-500/10 border border-red-500/20 rounded-md p-4">
|
<div className="bg-red-500/10 border border-red-500/20 rounded-md p-4">
|
||||||
<p className="text-sm text-red-500">{repository.lastError}</p>
|
<p className="text-sm text-red-500 wrap-break-word">{repository.lastError}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,13 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="flex flex-col items-center justify-center text-center py-12">
|
<CardContent className="flex flex-col items-center justify-center text-center py-12">
|
||||||
<Database className="mb-4 h-12 w-12 text-destructive" />
|
<Database className="mb-4 h-12 w-12 text-destructive" />
|
||||||
<p className="text-destructive font-semibold">Repository Error</p>
|
<p className="text-destructive font-semibold">Repository error</p>
|
||||||
<p className="text-sm text-muted-foreground mt-2">
|
<p className="text-sm text-muted-foreground mt-2">
|
||||||
This repository is in an error state and cannot be accessed.
|
This repository is in an error state and cannot be accessed.
|
||||||
</p>
|
</p>
|
||||||
{repository.lastError && (
|
{repository.lastError && (
|
||||||
<div className="mt-4 max-w-md bg-destructive/10 border border-destructive/20 rounded-md p-3">
|
<div className="mt-4 w-full max-w-md bg-destructive/10 border border-destructive/20 rounded-md p-3">
|
||||||
<p className="text-sm text-destructive">{repository.lastError}</p>
|
<p className="text-sm text-destructive wrap-break-word">{repository.lastError}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
<Download className="size-5" />
|
<Download className="size-5" />
|
||||||
Backup Recovery Key
|
Backup Recovery Key
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="mt-1.5">Download your Restic password file for disaster recovery</CardDescription>
|
<CardDescription className="mt-1.5">Download your recovery key for Restic backups</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<CardContent className="p-6 space-y-4">
|
<CardContent className="p-6 space-y-4">
|
||||||
<p className="text-sm text-muted-foreground max-w-2xl">
|
<p className="text-sm text-muted-foreground max-w-2xl">
|
||||||
|
|
@ -219,15 +219,15 @@ export default function Settings({ loaderData }: Route.ComponentProps) {
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
<Download size={16} className="mr-2" />
|
<Download size={16} className="mr-2" />
|
||||||
Download Restic Password
|
Download recovery key
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<form onSubmit={handleDownloadResticPassword}>
|
<form onSubmit={handleDownloadResticPassword}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Download Restic Password</DialogTitle>
|
<DialogTitle>Download Recovery Key</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
For security reasons, please enter your account password to download the Restic password file.
|
For security reasons, please enter your account password to download the recovery key file.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue