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 (
+
+
+
+
+ );
+};
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 /)
+
+
+
+ )}
+ />
+