From 0477329db7e45789caa53eef7501f4d3d74bfcbe Mon Sep 17 00:00:00 2001 From: chuyun Date: Wed, 27 Nov 2024 02:14:45 +0800 Subject: [PATCH 01/21] feat: add optional Provider attribute to S3 Destinations --- .../settings/destination/add-destination.tsx | 40 + .../settings/destination/constants.ts | 133 + .../destination/update-destination.tsx | 39 + .../settings/web-server/update-server.tsx | 2 +- apps/dokploy/drizzle/0045_smiling_blur.sql | 1 + apps/dokploy/drizzle/meta/0045_snapshot.json | 3981 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + .../dokploy/server/api/routers/destination.ts | 8 +- apps/dokploy/templates/templates.ts | 16 +- packages/server/src/db/schema/destination.ts | 3 + packages/server/src/utils/backups/utils.ts | 8 +- 11 files changed, 4224 insertions(+), 14 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/destination/constants.ts create mode 100644 apps/dokploy/drizzle/0045_smiling_blur.sql create mode 100644 apps/dokploy/drizzle/meta/0045_snapshot.json diff --git a/apps/dokploy/components/dashboard/settings/destination/add-destination.tsx b/apps/dokploy/components/dashboard/settings/destination/add-destination.tsx index b8718d3bf..a44860986 100644 --- a/apps/dokploy/components/dashboard/settings/destination/add-destination.tsx +++ b/apps/dokploy/components/dashboard/settings/destination/add-destination.tsx @@ -34,9 +34,11 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { S3_PROVIDERS } from "./constants"; const addDestination = z.object({ name: z.string().min(1, "Name is required"), + provider: z.string().optional(), accessKeyId: z.string(), secretAccessKey: z.string(), bucket: z.string(), @@ -58,6 +60,7 @@ export const AddDestination = () => { api.destination.testConnection.useMutation(); const form = useForm({ defaultValues: { + provider: "", accessKeyId: "", bucket: "", name: "", @@ -73,6 +76,7 @@ export const AddDestination = () => { const onSubmit = async (data: AddDestination) => { await mutateAsync({ + provider: data.provider, accessKey: data.accessKeyId, bucket: data.bucket, endpoint: data.endpoint, @@ -123,6 +127,40 @@ export const AddDestination = () => { ); }} /> + { + return ( + + Provider + + + + + + ); + }} + /> { isLoading={isLoading} onClick={async () => { await testConnection({ + provider: form.getValues("provider"), accessKey: form.getValues("accessKeyId"), bucket: form.getValues("bucket"), endpoint: form.getValues("endpoint"), @@ -283,6 +322,7 @@ export const AddDestination = () => { variant="secondary" onClick={async () => { await testConnection({ + provider: form.getValues("provider"), accessKey: form.getValues("accessKeyId"), bucket: form.getValues("bucket"), endpoint: form.getValues("endpoint"), diff --git a/apps/dokploy/components/dashboard/settings/destination/constants.ts b/apps/dokploy/components/dashboard/settings/destination/constants.ts new file mode 100644 index 000000000..f43e47d1a --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/destination/constants.ts @@ -0,0 +1,133 @@ +export const S3_PROVIDERS: Array<{ + key: string; + name: string; +}> = [ + { + key: "AWS", + name: "Amazon Web Services (AWS) S3", + }, + { + key: "Alibaba", + name: "Alibaba Cloud Object Storage System (OSS) formerly Aliyun", + }, + { + key: "ArvanCloud", + name: "Arvan Cloud Object Storage (AOS)", + }, + { + key: "Ceph", + name: "Ceph Object Storage", + }, + { + key: "ChinaMobile", + name: "China Mobile Ecloud Elastic Object Storage (EOS)", + }, + { + key: "Cloudflare", + name: "Cloudflare R2 Storage", + }, + { + key: "DigitalOcean", + name: "DigitalOcean Spaces", + }, + { + key: "Dreamhost", + name: "Dreamhost DreamObjects", + }, + { + key: "GCS", + name: "Google Cloud Storage", + }, + { + key: "HuaweiOBS", + name: "Huawei Object Storage Service", + }, + { + key: "IBMCOS", + name: "IBM COS S3", + }, + { + key: "IDrive", + name: "IDrive e2", + }, + { + key: "IONOS", + name: "IONOS Cloud", + }, + { + key: "LyveCloud", + name: "Seagate Lyve Cloud", + }, + { + key: "Leviia", + name: "Leviia Object Storage", + }, + { + key: "Liara", + name: "Liara Object Storage", + }, + { + key: "Linode", + name: "Linode Object Storage", + }, + { + key: "Magalu", + name: "Magalu Object Storage", + }, + { + key: "Minio", + name: "Minio Object Storage", + }, + { + key: "Netease", + name: "Netease Object Storage (NOS)", + }, + { + key: "Petabox", + name: "Petabox Object Storage", + }, + { + key: "RackCorp", + name: "RackCorp Object Storage", + }, + { + key: "Rclone", + name: "Rclone S3 Server", + }, + { + key: "Scaleway", + name: "Scaleway Object Storage", + }, + { + key: "SeaweedFS", + name: "SeaweedFS S3", + }, + { + key: "StackPath", + name: "StackPath Object Storage", + }, + { + key: "Storj", + name: "Storj (S3 Compatible Gateway)", + }, + { + key: "Synology", + name: "Synology C2 Object Storage", + }, + { + key: "TencentCOS", + name: "Tencent Cloud Object Storage (COS)", + }, + { + key: "Wasabi", + name: "Wasabi Object Storage", + }, + { + key: "Qiniu", + name: "Qiniu Object Storage (Kodo)", + }, + { + key: "Other", + name: "Any other S3 compatible provider", + }, +]; diff --git a/apps/dokploy/components/dashboard/settings/destination/update-destination.tsx b/apps/dokploy/components/dashboard/settings/destination/update-destination.tsx index fbf08c976..474354baf 100644 --- a/apps/dokploy/components/dashboard/settings/destination/update-destination.tsx +++ b/apps/dokploy/components/dashboard/settings/destination/update-destination.tsx @@ -35,9 +35,11 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { S3_PROVIDERS } from "./constants"; const updateDestination = z.object({ name: z.string().min(1, "Name is required"), + provider: z.string().optional(), accessKeyId: z.string(), secretAccessKey: z.string(), bucket: z.string(), @@ -70,6 +72,7 @@ export const UpdateDestination = ({ destinationId }: Props) => { api.destination.testConnection.useMutation(); const form = useForm({ defaultValues: { + provider: "", accessKeyId: "", bucket: "", name: "", @@ -152,6 +155,40 @@ export const UpdateDestination = ({ destinationId }: Props) => { ); }} /> + { + return ( + + Provider + + + + + + ); + }} + /> { variant={"secondary"} onClick={async () => { await testConnection({ + provider: form.getValues("provider"), accessKey: form.getValues("accessKeyId"), bucket: form.getValues("bucket"), endpoint: form.getValues("endpoint"), @@ -311,6 +349,7 @@ export const UpdateDestination = ({ destinationId }: Props) => { variant="secondary" onClick={async () => { await testConnection({ + provider: form.getValues("provider"), accessKey: form.getValues("accessKeyId"), bucket: form.getValues("bucket"), endpoint: form.getValues("endpoint"), diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 7c300e46d..48a61c7a4 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -48,7 +48,7 @@ export const UpdateServer = () => {
  • Some bug that is blocking to use some features
  • - We recommend checking the latest version for any breaking changes + We recommend checking the latest version for any breaking changes before updating. Go to{" "} { - const { secretAccessKey, bucket, region, endpoint, accessKey } = input; - + const { secretAccessKey, bucket, region, endpoint, accessKey, provider } = + input; try { const rcloneFlags = [ - // `--s3-provider=Cloudflare`, `--s3-access-key-id=${accessKey}`, `--s3-secret-access-key=${secretAccessKey}`, `--s3-region=${region}`, @@ -52,6 +51,9 @@ export const destinationRouter = createTRPCRouter({ "--s3-no-check-bucket", "--s3-force-path-style", ]; + if (provider) { + rcloneFlags.unshift(`--s3-provider=${provider}`); + } const rcloneDestination = `:s3:${bucket}`; const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`; diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index f6ed672c3..e2591124c 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -881,8 +881,8 @@ export const templates: TemplateData[] = [ }, tags: ["forum", "community", "discussion"], load: () => import("./discourse/index").then((m) => m.generate), - }, - { + }, + { id: "immich", name: "Immich", version: "v1.121.0", @@ -896,8 +896,8 @@ export const templates: TemplateData[] = [ }, tags: ["photos", "videos", "backup", "media"], load: () => import("./immich/index").then((m) => m.generate), - }, - { + }, + { id: "twenty", name: "Twenty CRM", version: "latest", @@ -911,8 +911,8 @@ export const templates: TemplateData[] = [ }, tags: ["crm", "sales", "business"], load: () => import("./twenty/index").then((m) => m.generate), - }, - { + }, + { id: "yourls", name: "YOURLS", version: "1.9.2", @@ -926,8 +926,8 @@ export const templates: TemplateData[] = [ }, tags: ["url-shortener", "php"], load: () => import("./yourls/index").then((m) => m.generate), - }, - { + }, + { id: "ryot", name: "Ryot", version: "v7.10", diff --git a/packages/server/src/db/schema/destination.ts b/packages/server/src/db/schema/destination.ts index bd9c77620..320a3d7c0 100644 --- a/packages/server/src/db/schema/destination.ts +++ b/packages/server/src/db/schema/destination.ts @@ -12,6 +12,7 @@ export const destinations = pgTable("destination", { .primaryKey() .$defaultFn(() => nanoid()), name: text("name").notNull(), + provider: text("provider"), accessKey: text("accessKey").notNull(), secretAccessKey: text("secretAccessKey").notNull(), bucket: text("bucket").notNull(), @@ -37,6 +38,7 @@ export const destinationsRelations = relations( const createSchema = createInsertSchema(destinations, { destinationId: z.string(), name: z.string().min(1), + provider: z.string(), accessKey: z.string(), bucket: z.string(), endpoint: z.string(), @@ -47,6 +49,7 @@ const createSchema = createInsertSchema(destinations, { export const apiCreateDestination = createSchema .pick({ name: true, + provider: true, accessKey: true, bucket: true, region: true, diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index b9656ff28..0d78ff961 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -28,9 +28,9 @@ export const removeScheduleBackup = (backupId: string) => { }; export const getS3Credentials = (destination: Destination) => { - const { accessKey, secretAccessKey, bucket, region, endpoint } = destination; + const { accessKey, secretAccessKey, bucket, region, endpoint, provider } = + destination; const rcloneFlags = [ - // `--s3-provider=Cloudflare`, `--s3-access-key-id=${accessKey}`, `--s3-secret-access-key=${secretAccessKey}`, `--s3-region=${region}`, @@ -39,5 +39,9 @@ export const getS3Credentials = (destination: Destination) => { "--s3-force-path-style", ]; + if (provider) { + rcloneFlags.unshift(`--s3-provider=${provider}`); + } + return rcloneFlags; }; From c8e9d9d169448e2ab98ca8926572a82986c906c5 Mon Sep 17 00:00:00 2001 From: F43Z <50200066+faeztgh@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:40:42 +0330 Subject: [PATCH 02/21] feat: add fa locale --- .../dashboard/settings/appearance-form.tsx | 391 ++++++++++-------- apps/dokploy/next-i18next.config.cjs | 14 +- apps/dokploy/pages/_app.tsx | 106 ++--- apps/dokploy/public/locales/fa/common.json | 1 + apps/dokploy/public/locales/fa/settings.json | 45 ++ apps/dokploy/utils/hooks/use-locale.ts | 28 +- 6 files changed, 350 insertions(+), 235 deletions(-) create mode 100644 apps/dokploy/public/locales/fa/common.json create mode 100644 apps/dokploy/public/locales/fa/settings.json diff --git a/apps/dokploy/components/dashboard/settings/appearance-form.tsx b/apps/dokploy/components/dashboard/settings/appearance-form.tsx index b7fe20e86..f0b2e680c 100644 --- a/apps/dokploy/components/dashboard/settings/appearance-form.tsx +++ b/apps/dokploy/components/dashboard/settings/appearance-form.tsx @@ -4,28 +4,28 @@ import * as z from "zod"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@/components/ui/form"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; import useLocale from "@/utils/hooks/use-locale"; import { useTranslation } from "next-i18next"; @@ -34,167 +34,226 @@ import { useEffect } from "react"; import { toast } from "sonner"; const appearanceFormSchema = z.object({ - theme: z.enum(["light", "dark", "system"], { - required_error: "Please select a theme.", - }), - language: z.enum(["en", "pl", "ru", "de", "zh-Hant", "zh-Hans"], { - required_error: "Please select a language.", - }), + theme: z.enum(["light", "dark", "system"], { + required_error: "Please select a theme.", + }), + language: z.enum(["en", "pl", "ru", "de", "zh-Hant", "zh-Hans", "fa"], { + required_error: "Please select a language.", + }), }); type AppearanceFormValues = z.infer; // This can come from your database or API. const defaultValues: Partial = { - theme: "system", - language: "en", + theme: "system", + language: "en", }; export function AppearanceForm() { - const { setTheme, theme } = useTheme(); - const { locale, setLocale } = useLocale(); - const { t } = useTranslation("settings"); + const { setTheme, theme } = useTheme(); + const { locale, setLocale } = useLocale(); + const { t } = useTranslation("settings"); - const form = useForm({ - resolver: zodResolver(appearanceFormSchema), - defaultValues, - }); + const form = useForm({ + resolver: zodResolver(appearanceFormSchema), + defaultValues, + }); - useEffect(() => { - form.reset({ - theme: (theme ?? "system") as AppearanceFormValues["theme"], - language: locale, - }); - }, [form, theme, locale]); - function onSubmit(data: AppearanceFormValues) { - setTheme(data.theme); - setLocale(data.language); - toast.success("Preferences Updated"); - } + useEffect(() => { + form.reset({ + theme: (theme ?? "system") as AppearanceFormValues["theme"], + language: locale, + }); + }, [form, theme, locale]); + function onSubmit(data: AppearanceFormValues) { + setTheme(data.theme); + setLocale(data.language); + toast.success("Preferences Updated"); + } - return ( - - - - {t("settings.appearance.title")} - - - {t("settings.appearance.description")} - - - -
    - - { - return ( - - {t("settings.appearance.theme")} - - {t("settings.appearance.themeDescription")} - - - - - - - - -
    - light -
    - - {t("settings.appearance.themes.light")} - -
    -
    - - - - - -
    - dark -
    - - {t("settings.appearance.themes.dark")} - -
    -
    - - - - - -
    - system -
    - - {t("settings.appearance.themes.system")} - -
    -
    -
    -
    - ); - }} - /> + return ( + + + + {t("settings.appearance.title")} + + + {t("settings.appearance.description")} + + + + + + { + return ( + + + {t("settings.appearance.theme")} + + + {t( + "settings.appearance.themeDescription" + )} + + + + + + + + +
    + light +
    + + {t( + "settings.appearance.themes.light" + )} + +
    +
    + + + + + +
    + dark +
    + + {t( + "settings.appearance.themes.dark" + )} + +
    +
    + + + + + +
    + system +
    + + {t( + "settings.appearance.themes.system" + )} + +
    +
    +
    +
    + ); + }} + /> - { - return ( - - {t("settings.appearance.language")} - - {t("settings.appearance.languageDescription")} - - - - - ); - }} - /> + { + return ( + + + {t("settings.appearance.language")} + + + {t( + "settings.appearance.languageDescription" + )} + + + + + ); + }} + /> - - - -
    -
    - ); + + + +
    +
    + ); } diff --git a/apps/dokploy/next-i18next.config.cjs b/apps/dokploy/next-i18next.config.cjs index 30f82e64c..f8be0f2d7 100644 --- a/apps/dokploy/next-i18next.config.cjs +++ b/apps/dokploy/next-i18next.config.cjs @@ -1,10 +1,10 @@ /** @type {import('next-i18next').UserConfig} */ module.exports = { - i18n: { - defaultLocale: "en", - locales: ["en", "pl", "ru", "de", "zh-Hant", "zh-Hans"], - localeDetection: false, - }, - fallbackLng: "en", - keySeparator: false, + i18n: { + defaultLocale: "en", + locales: ["en", "pl", "ru", "de", "zh-Hant", "zh-Hans", "fa"], + localeDetection: false, + }, + fallbackLng: "en", + keySeparator: false, }; diff --git a/apps/dokploy/pages/_app.tsx b/apps/dokploy/pages/_app.tsx index d1d951231..517dcd89f 100644 --- a/apps/dokploy/pages/_app.tsx +++ b/apps/dokploy/pages/_app.tsx @@ -14,68 +14,70 @@ import type { ReactElement, ReactNode } from "react"; const inter = Inter({ subsets: ["latin"] }); export type NextPageWithLayout

    = NextPage & { - getLayout?: (page: ReactElement) => ReactNode; - // session: Session | null; - theme?: string; + getLayout?: (page: ReactElement) => ReactNode; + // session: Session | null; + theme?: string; }; type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout; + Component: NextPageWithLayout; }; const MyApp = ({ - Component, - pageProps: { ...pageProps }, + Component, + pageProps: { ...pageProps }, }: AppPropsWithLayout) => { - const getLayout = Component.getLayout ?? ((page) => page); + const getLayout = Component.getLayout ?? ((page) => page); - return ( - <> - - - Dokploy - - {process.env.NEXT_PUBLIC_UMAMI_HOST && - process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && ( -