diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts index 661a2399a..470a63849 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts @@ -8,7 +8,8 @@ export enum SecretRotation { AwsIamUserSecret = "aws-iam-user-secret", LdapPassword = "ldap-password", OktaClientSecret = "okta-client-secret", - RedisCredentials = "redis-credentials" + RedisCredentials = "redis-credentials", + MongoDBCredentials = "mongodb-credentials" } export enum SecretRotationStatus { diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts index 2087fa195..c2b0714ab 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts @@ -11,7 +11,8 @@ export const SECRET_ROTATION_NAME_MAP: Record = { [SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret", [SecretRotation.LdapPassword]: "LDAP Password", [SecretRotation.OktaClientSecret]: "Okta Client Secret", - [SecretRotation.RedisCredentials]: "Redis Credentials" + [SecretRotation.RedisCredentials]: "Redis Credentials", + [SecretRotation.MongoDBCredentials]: "MongoDB Credentials" }; export const SECRET_ROTATION_CONNECTION_MAP: Record = { @@ -24,5 +25,6 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record; diff --git a/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts b/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts new file mode 100644 index 000000000..151c676a5 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/mongodb-connection.ts @@ -0,0 +1,22 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum MongoDBConnectionMethod { + UsernameAndPassword = "username-and-password" +} + +export type TMongoDBConnectionCredentials = { + host: string; + port: number; + username: string; + password: string; + database: string; + sslEnabled: boolean; + sslRejectUnauthorized: boolean; + sslCertificate?: string; +}; + +export type TMongoDBConnection = TRootAppConnection & { app: AppConnection.MongoDB } & { + method: MongoDBConnectionMethod.UsernameAndPassword; + credentials: TMongoDBConnectionCredentials; +}; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index aca33ffa2..3b1710fdb 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -34,6 +34,7 @@ import { HerokuConnectionForm } from "./HerokuAppConnectionForm"; import { HumanitecConnectionForm } from "./HumanitecConnectionForm"; import { LaravelForgeConnectionForm } from "./LaravelForgeConnectionForm"; import { LdapConnectionForm } from "./LdapConnectionForm"; +import { MongoDBConnectionForm } from "./MongoDBConnectionForm"; import { MsSqlConnectionForm } from "./MsSqlConnectionForm"; import { MySqlConnectionForm } from "./MySqlConnectionForm"; import { NetlifyConnectionForm } from "./NetlifyConnectionForm"; @@ -170,6 +171,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { return ; case AppConnection.Redis: return ; + case AppConnection.MongoDB: + return ; default: throw new Error(`Unhandled App ${app}`); } @@ -326,6 +329,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Redis: return ; + case AppConnection.MongoDB: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); } diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx new file mode 100644 index 000000000..c9b3421ce --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/MongoDBConnectionForm.tsx @@ -0,0 +1,334 @@ +import { useEffect, useState } from "react"; +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tab } from "@headlessui/react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem, + Switch, + TextArea, + Tooltip +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { MongoDBConnectionMethod, TMongoDBConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TMongoDBConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.MongoDB) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(MongoDBConnectionMethod.UsernameAndPassword), + credentials: z.object({ + host: z.string().trim().min(1, "Host required"), + port: z.coerce.number().default(27017), + username: z.string().trim().min(1, "Username required"), + password: z.string().trim().min(1, "Password required"), + database: z.string().trim().min(1, "Database required"), + sslEnabled: z.boolean().default(false), + sslRejectUnauthorized: z.boolean().default(true), + sslCertificate: z + .string() + .trim() + .transform((value) => value || undefined) + .optional() + }) + }) +]); + +type FormData = z.infer; + +export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.MongoDB, + method: MongoDBConnectionMethod.UsernameAndPassword, + credentials: { + host: "", + port: 27017, + username: "", + password: "", + database: "", + sslEnabled: false, + sslRejectUnauthorized: true, + sslCertificate: undefined + } + } + }); + + const { + handleSubmit, + watch, + control, + setValue, + formState: { isSubmitting, isDirty } + } = form; + + const host = watch("credentials.host"); + const sslEnabled = watch("credentials.sslEnabled"); + + useEffect(() => { + if (host && host.includes("+srv") && !sslEnabled) { + setValue("credentials.sslEnabled", true, { shouldDirty: true }); + } + }, [host, sslEnabled, setValue]); + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + + + + + `-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${ + selected + ? "border-b-2 border-mineshaft-300 text-mineshaft-200" + : "text-bunker-300" + }` + } + > + Configuration + + + `-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${ + selected + ? "border-b-2 border-mineshaft-300 text-mineshaft-200" + : "text-bunker-300" + }` + } + > + SSL ({sslEnabled ? "Enabled" : "Disabled"}) + + + + +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+
+ ( + + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+
+ + ( + + + Enable SSL + + + )} + /> + ( + +