"use client"; import React from "react"; import Link from "next/link"; import { ActionButton } from "@/components/ui/action-button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { toast } from "@/components/ui/use-toast"; import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { useUserSettings } from "@/lib/userSettings"; import { zodResolver } from "@hookform/resolvers/zod"; import { CheckCircle, Download, Play, Save, Trash2, XCircle, } from "lucide-react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useUpdateUserSettings } from "@karakeep/shared-react/hooks/users"; import { zBackupSchema } from "@karakeep/shared/types/backups"; import { zUpdateBackupSettingsSchema } from "@karakeep/shared/types/users"; import { getAssetUrl } from "@karakeep/shared/utils/assetUtils"; import ActionConfirmingDialog from "../ui/action-confirming-dialog"; import { Button } from "../ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; function BackupConfigurationForm() { const { t } = useTranslation(); const settings = useUserSettings(); const { mutate: updateSettings, isPending: isUpdating } = useUpdateUserSettings({ onSuccess: () => { toast({ description: t("settings.info.user_settings.user_settings_updated"), }); }, onError: () => { toast({ description: t("common.something_went_wrong"), variant: "destructive", }); }, }); const form = useForm>({ resolver: zodResolver(zUpdateBackupSettingsSchema), values: settings ? { backupsEnabled: settings.backupsEnabled, backupsFrequency: settings.backupsFrequency, backupsRetentionDays: settings.backupsRetentionDays, } : undefined, }); return (

{t("settings.backups.configuration.title")}

{ updateSettings(value); })} > (
{t( "settings.backups.configuration.enable_automatic_backups", )} {t( "settings.backups.configuration.enable_automatic_backups_description", )}
)} /> ( {t("settings.backups.configuration.backup_frequency")} {t( "settings.backups.configuration.backup_frequency_description", )} )} /> ( {t("settings.backups.configuration.retention_period")} field.onChange(parseInt(e.target.value))} /> {t( "settings.backups.configuration.retention_period_description", )} )} /> {t("settings.backups.configuration.save_settings")}
); } function BackupRow({ backup }: { backup: z.infer }) { const { t } = useTranslation(); const apiUtils = api.useUtils(); const { mutate: deleteBackup, isPending: isDeleting } = api.backups.delete.useMutation({ onSuccess: () => { toast({ description: t("settings.backups.toasts.backup_deleted"), }); apiUtils.backups.list.invalidate(); }, onError: (error) => { toast({ description: `Error: ${error.message}`, variant: "destructive", }); }, }); const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; }; return ( {backup.createdAt.toLocaleString()} {backup.status === "pending" ? "-" : backup.bookmarkCount.toLocaleString()} {backup.status === "pending" ? "-" : formatSize(backup.size)} {backup.status === "success" ? ( {t("settings.backups.list.status.success")} ) : backup.status === "failure" ? ( {t("settings.backups.list.status.failed")} {backup.errorMessage} ) : (
{t("settings.backups.list.status.pending")} )} {backup.assetId && ( {t("settings.backups.list.actions.download_backup")} )} ( deleteBackup({ backupId: backup.id })} className="items-center" type="button" > {t("settings.backups.list.actions.delete_backup")} )} > ); } function BackupsList() { const { t } = useTranslation(); const apiUtils = api.useUtils(); const { data: backups, isLoading } = api.backups.list.useQuery(undefined, { refetchInterval: (query) => { const data = query.state.data; // Poll every 3 seconds if there's a pending backup, otherwise don't poll return data?.backups.some((backup) => backup.status === "pending") ? 3000 : false; }, }); const { mutate: triggerBackup, isPending: isTriggering } = api.backups.triggerBackup.useMutation({ onSuccess: () => { toast({ description: t("settings.backups.toasts.backup_queued"), }); apiUtils.backups.list.invalidate(); }, onError: (error) => { toast({ description: `Error: ${error.message}`, variant: "destructive", }); }, }); return (
{t("settings.backups.list.title")} triggerBackup()} loading={isTriggering} variant="default" className="items-center" > {t("settings.backups.list.create_backup_now")}
{isLoading && } {backups && backups.backups.length === 0 && (

{t("settings.backups.list.no_backups")}

)} {backups && backups.backups.length > 0 && ( {t("settings.backups.list.table.created_at")} {t("settings.backups.list.table.bookmarks")} {t("settings.backups.list.table.size")} {t("settings.backups.list.table.status")} {t("settings.backups.list.table.actions")} {backups.backups.map((backup) => ( ))}
)}
); } export default function BackupSettings() { return (
); }