diff --git a/app/client/components/path-selector.tsx b/app/client/components/path-selector.tsx new file mode 100644 index 00000000..4d6e6a4d --- /dev/null +++ b/app/client/components/path-selector.tsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { DirectoryBrowser } from "./directory-browser"; +import { Button } from "./ui/button"; + +type Props = { + value: string; + onChange: (path: string) => void; + label?: string; +}; + +/** + * A reusable path selector component that shows the selected path + * with a "Change" button, and expands to a DirectoryBrowser when clicked. + * Matches the pattern used in the volume creation form. + */ +export const PathSelector = ({ value, onChange, label = "Selected path:" }: Props) => { + const [showBrowser, setShowBrowser] = useState(false); + + if (showBrowser) { + return ( +
+ { + onChange(path); + setShowBrowser(false); + }} + selectedPath={value} + /> + +
+ ); + } + + return ( +
+
+
{label}
+
{value}
+
+ +
+ ); +}; diff --git a/app/client/modules/backups/components/snapshot-file-browser.tsx b/app/client/modules/backups/components/snapshot-file-browser.tsx index 90382ae4..5ba32251 100644 --- a/app/client/modules/backups/components/snapshot-file-browser.tsx +++ b/app/client/modules/backups/components/snapshot-file-browser.tsx @@ -19,6 +19,7 @@ import { AlertDialogTitle, } from "~/client/components/ui/alert-dialog"; import { Tooltip, TooltipContent, TooltipTrigger } from "~/client/components/ui/tooltip"; +import { PathSelector } from "~/client/components/path-selector"; import type { Snapshot, Volume } from "~/client/lib/types"; import { toast } from "sonner"; import { listSnapshotFilesOptions, restoreSnapshotMutation } from "~/client/api-client/@tanstack/react-query.gen"; @@ -141,6 +142,7 @@ export const SnapshotFileBrowser = (props: Props) => { path: { name: repositoryName }, body: { snapshotId: snapshot.short_id, + target: targetPath || undefined, include: includePaths, delete: deleteExtraFiles, excludeXattr: excludeXattrArray && excludeXattrArray.length > 0 ? excludeXattrArray : undefined, diff --git a/app/client/modules/repositories/components/restore-snapshot-dialog.tsx b/app/client/modules/repositories/components/restore-snapshot-dialog.tsx index f7e64d51..d6389c70 100644 --- a/app/client/modules/repositories/components/restore-snapshot-dialog.tsx +++ b/app/client/modules/repositories/components/restore-snapshot-dialog.tsx @@ -61,6 +61,7 @@ export const RestoreSnapshotDialog = ({ name, snapshotId }: Props) => { path: { name }, body: { snapshotId, + target: values.target || undefined, include: include && include.length > 0 ? include : undefined, exclude: exclude && exclude.length > 0 ? exclude : undefined, excludeXattr: excludeXattr && excludeXattr.length > 0 ? excludeXattr : undefined, diff --git a/app/client/modules/repositories/components/restore-snapshot-form.tsx b/app/client/modules/repositories/components/restore-snapshot-form.tsx index e39fa80a..466beaf8 100644 --- a/app/client/modules/repositories/components/restore-snapshot-form.tsx +++ b/app/client/modules/repositories/components/restore-snapshot-form.tsx @@ -17,6 +17,7 @@ import { Button } from "~/client/components/ui/button"; const restoreSnapshotFormSchema = type({ path: "string?", + target: "string?", include: "string?", exclude: "string?", excludeXattr: "string?", @@ -37,6 +38,7 @@ export const RestoreSnapshotForm = ({ formId, onSubmit, className }: Props) => { resolver: arktypeResolver(restoreSnapshotFormSchema), defaultValues: { path: "", + target: "", include: "", exclude: "", excludeXattr: "", @@ -68,6 +70,23 @@ export const RestoreSnapshotForm = ({ formId, onSubmit, className }: Props) => { )} /> + ( + + Target Directory (Optional) + + + + + Restore to a custom location instead of the original path (defaults to /) + + + + )} + /> +