diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 5a3de70..a36e74b 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -741,12 +741,21 @@ export type ListRepositoriesResponses = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; createdAt: number; id: string; @@ -754,7 +763,7 @@ export type ListRepositoriesResponses = { lastError: string | null; name: string; status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3'; + type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3'; updatedAt: number; }>; }; @@ -799,12 +808,21 @@ export type CreateRepositoryData = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; name: string; compressionMode?: 'auto' | 'better' | 'fastest' | 'max' | 'off'; @@ -919,12 +937,21 @@ export type GetRepositoryResponses = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; createdAt: number; id: string; @@ -932,7 +959,7 @@ export type GetRepositoryResponses = { lastError: string | null; name: string; status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3'; + type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3'; updatedAt: number; }; }; @@ -1166,12 +1193,21 @@ export type ListBackupSchedulesResponses = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; createdAt: number; id: string; @@ -1179,7 +1215,7 @@ export type ListBackupSchedulesResponses = { lastError: string | null; name: string; status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3'; + type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3'; updatedAt: number; }; repositoryId: string; @@ -1379,12 +1415,21 @@ export type GetBackupScheduleResponses = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; createdAt: number; id: string; @@ -1392,7 +1437,7 @@ export type GetBackupScheduleResponses = { lastError: string | null; name: string; status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3'; + type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3'; updatedAt: number; }; repositoryId: string; @@ -1573,12 +1618,21 @@ export type GetBackupScheduleForVolumeResponses = { name: string; customPassword?: string; isExistingRepository?: boolean; + path?: string; } | { backend: 'rclone'; path: string; remote: string; customPassword?: string; isExistingRepository?: boolean; + } | { + backend: 'rest'; + url: string; + customPassword?: string; + isExistingRepository?: boolean; + password?: string; + path?: string; + username?: string; }; createdAt: number; id: string; @@ -1586,7 +1640,7 @@ export type GetBackupScheduleForVolumeResponses = { lastError: string | null; name: string; status: 'error' | 'healthy' | 'unknown' | null; - type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 's3'; + type: 'azure' | 'gcs' | 'local' | 'r2' | 'rclone' | 'rest' | 's3'; updatedAt: number; }; repositoryId: string; diff --git a/app/client/components/create-repository-form.tsx b/app/client/components/create-repository-form.tsx index 724eda8..51ba64a 100644 --- a/app/client/components/create-repository-form.tsx +++ b/app/client/components/create-repository-form.tsx @@ -10,12 +10,23 @@ import { Input } from "./ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { useQuery } from "@tanstack/react-query"; import { Alert, AlertDescription } from "./ui/alert"; -import { ExternalLink } from "lucide-react"; +import { ExternalLink, AlertTriangle } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { useSystemInfo } from "~/client/hooks/use-system-info"; import { COMPRESSION_MODES, repositoryConfigSchema } from "~/schemas/restic"; import { listRcloneRemotesOptions } from "../api-client/@tanstack/react-query.gen"; import { Checkbox } from "./ui/checkbox"; +import { DirectoryBrowser } from "./directory-browser"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "./ui/alert-dialog"; export const formSchema = type({ name: "2<=string<=32", @@ -67,6 +78,8 @@ export const CreateRepositoryForm = ({ const watchedIsExistingRepository = watch("isExistingRepository"); const [passwordMode, setPasswordMode] = useState<"default" | "custom">("default"); + const [showPathBrowser, setShowPathBrowser] = useState(false); + const [showPathWarning, setShowPathWarning] = useState(false); const { capabilities } = useSystemInfo(); @@ -247,6 +260,87 @@ export const CreateRepositoryForm = ({ )} + {watchedBackend === "local" && ( + <> + + Repository Directory +
+
+ {form.watch("path") || "/var/lib/ironmount/repositories"} +
+ +
+ + The directory where the repository will be stored. + +
+ + + + + + + Important: Host Mount Required + + +

+ When selecting a custom path, ensure it is mounted from the host machine into the + container. +

+

+ If the path is not a host mount, you will lose your repository data when the container + restarts. +

+

+ The default path /var/lib/ironmount/repositories is + already mounted from the host and is safe to use. +

+
+
+ + Cancel + { + setShowPathBrowser(true); + setShowPathWarning(false); + }} + > + I Understand, Continue + + +
+
+ + + + + Select Repository Directory + + Choose a directory from the filesystem to store the repository. + + +
+ form.setValue("path", path)} + selectedPath={form.watch("path") || "/var/lib/ironmount/repositories"} + /> +
+ + Cancel + setShowPathBrowser(false)}>Done + +
+
+ + )} + {watchedBackend === "s3" && ( <> { const buildRepoUrl = (config: RepositoryConfig): string => { switch (config.backend) { case "local": - return `${REPOSITORY_BASE}/${config.name}`; + return `${config.path}/${config.name}` || `${REPOSITORY_BASE}/${config.name}`; case "s3": return `s3:${config.endpoint}/${config.bucket}`; case "r2": {