diff --git a/Dockerfile b/Dockerfile index b9c01db1..206d294b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG BUN_VERSION="1.3.3" FROM oven/bun:${BUN_VERSION}-alpine AS base -RUN apk add --no-cache davfs2=1.6.1-r2 openssh-client +RUN apk add --no-cache davfs2=1.6.1-r2 openssh-client fuse3 # ------------------------------ diff --git a/app/client/components/create-volume-form.tsx b/app/client/components/create-volume-form.tsx index a497526f..7f2af9a1 100644 --- a/app/client/components/create-volume-form.tsx +++ b/app/client/components/create-volume-form.tsx @@ -1,7 +1,7 @@ import { arktypeResolver } from "@hookform/resolvers/arktype"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { type } from "arktype"; -import { CheckCircle, Loader2, Pencil, Plug, Save, XCircle } from "lucide-react"; +import { CheckCircle, ExternalLink, Loader2, Pencil, Plug, Save, XCircle } from "lucide-react"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { cn, slugify } from "~/client/lib/utils"; @@ -12,7 +12,10 @@ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For import { Input } from "./ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { volumeConfigSchema } from "~/schemas/volumes"; -import { testConnectionMutation } from "../api-client/@tanstack/react-query.gen"; +import { listRcloneRemotesOptions, testConnectionMutation } from "../api-client/@tanstack/react-query.gen"; +import { Alert, AlertDescription } from "./ui/alert"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; +import { useSystemInfo } from "~/client/hooks/use-system-info"; export const formSchema = type({ name: "2<=string<=32", @@ -35,6 +38,7 @@ const defaultValuesForType = { nfs: { backend: "nfs" as const, port: 2049, version: "4.1" as const }, smb: { backend: "smb" as const, port: 445, vers: "3.0" as const }, webdav: { backend: "webdav" as const, port: 80, ssl: false }, + rclone: { backend: "rclone" as const, remote: "", path: "" }, }; export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading, className }: Props) => { @@ -49,6 +53,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for const { watch, getValues } = form; + const { capabilities } = useSystemInfo(); + + const { data: rcloneRemotes, isLoading: isLoadingRemotes } = useQuery({ + ...listRcloneRemotesOptions(), + enabled: capabilities.rclone, + }); + const watchedBackend = watch("backend"); useEffect(() => { @@ -122,14 +133,24 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for - - - Directory - NFS - SMB - WebDAV - - + + + Directory + NFS + SMB + WebDAV + + + + rclone (40+ cloud providers) + + + +

Setup rclone to use this backend

+
+
+
+ Choose the storage backend for this volume. @@ -545,7 +566,99 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} - {watchedBackend && watchedBackend !== "directory" && ( + {watchedBackend === "rclone" && + (!rcloneRemotes || rcloneRemotes.length === 0 ? ( + + +

No rclone remotes configured

+

+ To use rclone, you need to configure remotes on your host system +

+ + View rclone documentation + + +
+
+ ) : ( + <> + ( + + Remote + + Select the rclone remote configured on your host system. + + + )} + /> + ( + + Path + + + + Path on the remote to mount. Use "/" for the root. + + + )} + /> + ( + + Read-only Mode + +
+ field.onChange(e.target.checked)} + className="rounded border-gray-300" + /> + Mount volume as read-only +
+
+ + Prevent any modifications to the volume. Recommended for backup sources and sensitive data. + + +
+ )} + /> + + ))} {watchedBackend && watchedBackend !== "directory" && watchedBackend !== "rclone" && (