From d2c7ed62d01a8d630e0b87e5085561e7364e9ce0 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 20 Jun 2025 01:16:56 +0800 Subject: [PATCH] feat: added cloudflare app connection --- .../app-connection-router.ts | 10 +- .../cloudflare-connection-router.ts | 18 +++ .../routes/v1/app-connection-routers/index.ts | 4 +- .../app-connection/app-connection-enums.ts | 3 +- .../app-connection/app-connection-fns.ts | 15 +- .../app-connection/app-connection-maps.ts | 6 +- .../app-connection/app-connection-service.ts | 4 +- .../app-connection/app-connection-types.ts | 14 +- .../cloudflare/cloudflare-connection-enum.ts | 3 + .../cloudflare/cloudflare-connection-fns.ts | 47 ++++++ .../cloudflare-connection-schema.ts | 74 +++++++++ .../cloudflare/cloudflare-connection-types.ts | 25 +++ .../integration-auth/integration-list.ts | 1 + frontend/src/helpers/appConnections.ts | 5 +- .../src/hooks/api/appConnections/enums.ts | 3 +- .../api/appConnections/types/app-options.ts | 8 +- .../types/cloudflare-connection.ts | 14 ++ .../hooks/api/appConnections/types/index.ts | 6 +- .../AppConnectionForm/AppConnectionForm.tsx | 5 + .../CloudflareConnectionForm.tsx | 148 ++++++++++++++++++ 20 files changed, 397 insertions(+), 16 deletions(-) create mode 100644 backend/src/server/routes/v1/app-connection-routers/cloudflare-connection-router.ts create mode 100644 backend/src/services/app-connection/cloudflare/cloudflare-connection-enum.ts create mode 100644 backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts create mode 100644 backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts create mode 100644 backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts create mode 100644 frontend/src/hooks/api/appConnections/types/cloudflare-connection.ts create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CloudflareConnectionForm.tsx diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index ab84c014d..699a01d4c 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -79,6 +79,10 @@ import { WindmillConnectionListItemSchema } from "@app/services/app-connection/windmill"; import { AuthMode } from "@app/services/auth/auth-type"; +import { + CloudflareConnectionListItemSchema, + SanitizedCloudflareConnectionSchema +} from "@app/services/app-connection/cloudflare/cloudflare-connection-schema"; // can't use discriminated due to multiple schemas for certain apps const SanitizedAppConnectionSchema = z.union([ @@ -107,7 +111,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedOracleDBConnectionSchema.options, ...SanitizedOnePassConnectionSchema.options, ...SanitizedRenderConnectionSchema.options, - ...SanitizedFlyioConnectionSchema.options + ...SanitizedFlyioConnectionSchema.options, + ...SanitizedCloudflareConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -136,7 +141,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ OracleDBConnectionListItemSchema, OnePassConnectionListItemSchema, RenderConnectionListItemSchema, - FlyioConnectionListItemSchema + FlyioConnectionListItemSchema, + CloudflareConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/cloudflare-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/cloudflare-connection-router.ts new file mode 100644 index 000000000..c5ca01952 --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/cloudflare-connection-router.ts @@ -0,0 +1,18 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; +import { + SanitizedCloudflareConnectionSchema, + CreateCloudflareConnectionSchema, + UpdateCloudflareConnectionSchema +} from "@app/services/app-connection/cloudflare/cloudflare-connection-schema"; + +export const registerCloudflareConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Cloudflare, + server, + sanitizedResponseSchema: SanitizedCloudflareConnectionSchema, + createSchema: CreateCloudflareConnectionSchema, + updateSchema: UpdateCloudflareConnectionSchema + }); +}; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 45af934a7..2153dc685 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -26,6 +26,7 @@ import { registerTeamCityConnectionRouter } from "./teamcity-connection-router"; import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router"; import { registerVercelConnectionRouter } from "./vercel-connection-router"; import { registerWindmillConnectionRouter } from "./windmill-connection-router"; +import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router"; export * from "./app-connection-router"; @@ -56,5 +57,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { return [ @@ -126,7 +131,8 @@ export const listAppConnectionOptions = () => { getOracleDBConnectionListItem(), getOnePassConnectionListItem(), getRenderConnectionListItem(), - getFlyioConnectionListItem() + getFlyioConnectionListItem(), + getCloudflareConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -203,7 +209,8 @@ export const validateAppConnectionCredentials = async ( [AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator + [AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator }; return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); @@ -235,6 +242,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case TerraformCloudConnectionMethod.ApiToken: case VercelConnectionMethod.ApiToken: case OnePassConnectionMethod.ApiToken: + case CloudflareConnectionMethod.APIToken: return "API Token"; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: @@ -311,7 +319,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.OnePass]: platformManagedCredentialsNotSupported, [AppConnection.Render]: platformManagedCredentialsNotSupported, - [AppConnection.Flyio]: platformManagedCredentialsNotSupported + [AppConnection.Flyio]: platformManagedCredentialsNotSupported, + [AppConnection.Cloudflare]: platformManagedCredentialsNotSupported }; export const enterpriseAppCheck = async ( diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 24dc31f99..15bdf9886 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -26,7 +26,8 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.OracleDB]: "OracleDB", [AppConnection.OnePass]: "1Password", [AppConnection.Render]: "Render", - [AppConnection.Flyio]: "Fly.io" + [AppConnection.Flyio]: "Fly.io", + [AppConnection.Cloudflare]: "Cloudflare" }; export const APP_CONNECTION_PLAN_MAP: Record = { @@ -55,5 +56,6 @@ export const APP_CONNECTION_PLAN_MAP: Record>>; @@ -208,6 +215,7 @@ export type TAppConnectionInput = { id: string } & ( | TOnePassConnectionInput | TRenderConnectionInput | TFlyioConnectionInput + | TCloudflareConnectionInput ); export type TSqlConnectionInput = @@ -248,7 +256,8 @@ export type TAppConnectionConfig = | TOCIConnectionConfig | TOnePassConnectionConfig | TRenderConnectionConfig - | TFlyioConnectionConfig; + | TFlyioConnectionConfig + | TCloudflareConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -276,7 +285,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidateOracleDBConnectionCredentialsSchema | TValidateOnePassConnectionCredentialsSchema | TValidateRenderConnectionCredentialsSchema - | TValidateFlyioConnectionCredentialsSchema; + | TValidateFlyioConnectionCredentialsSchema + | TValidateCloudflareConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-enum.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-enum.ts new file mode 100644 index 000000000..2381524b9 --- /dev/null +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-enum.ts @@ -0,0 +1,3 @@ +export enum CloudflareConnectionMethod { + APIToken = "api-token" +} diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts new file mode 100644 index 000000000..ae4e2cb9d --- /dev/null +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts @@ -0,0 +1,47 @@ +import { AxiosError } from "axios"; + +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { CloudflareConnectionMethod } from "./cloudflare-connection-enum"; +import { TCloudflareConnectionConfig } from "./cloudflare-connection-types"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; + +export const getCloudflareConnectionListItem = () => { + return { + name: "Cloudflare" as const, + app: AppConnection.Cloudflare as const, + methods: Object.values(CloudflareConnectionMethod) as [CloudflareConnectionMethod.APIToken] + }; +}; + +export const validateCloudflareConnectionCredentials = async (config: TCloudflareConnectionConfig) => { + const { apiToken, accountId } = config.credentials; + + try { + const resp = await request.get(`${IntegrationUrls.CLOUDFLARE_API_URL}/client/v4/accounts/${accountId}`, { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + }); + + if (resp.data.data === null) { + throw new BadRequestError({ + message: "Unable to validate connection: Invalid API token provided." + }); + } + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to validate credentials: ${error.response?.data?.errors?.[0]?.message || error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); + } + + return config.credentials; +}; diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts new file mode 100644 index 000000000..64dee9dd5 --- /dev/null +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-schema.ts @@ -0,0 +1,74 @@ +import z from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { CloudflareConnectionMethod } from "./cloudflare-connection-enum"; +import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; + +const accountIdCharacterValidator = characterValidator([ + CharacterType.AlphaNumeric, + CharacterType.Underscore, + CharacterType.Hyphen +]); + +export const CloudflareConnectionApiTokenCredentialsSchema = z.object({ + accountId: z + .string() + .trim() + .min(1, "Account ID required") + .max(256, "Account ID cannot exceed 256 characters") + .refine( + (val) => accountIdCharacterValidator(val), + "Account ID can only contain alphanumeric characters, underscores, and hyphens" + ), + apiToken: z.string().trim().min(1, "API token required").max(256, "API token cannot exceed 256 characters") +}); + +const BaseCloudflareConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Cloudflare) }); + +export const CloudflareConnectionSchema = BaseCloudflareConnectionSchema.extend({ + method: z.literal(CloudflareConnectionMethod.APIToken), + credentials: CloudflareConnectionApiTokenCredentialsSchema +}); + +export const SanitizedCloudflareConnectionSchema = z.discriminatedUnion("method", [ + BaseCloudflareConnectionSchema.extend({ + method: z.literal(CloudflareConnectionMethod.APIToken), + credentials: CloudflareConnectionApiTokenCredentialsSchema.pick({ accountId: true }) + }) +]); + +export const ValidateCloudflareConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z + .literal(CloudflareConnectionMethod.APIToken) + .describe(AppConnections.CREATE(AppConnection.Cloudflare).method), + credentials: CloudflareConnectionApiTokenCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.Cloudflare).credentials + ) + }) +]); + +export const CreateCloudflareConnectionSchema = ValidateCloudflareConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Cloudflare) +); + +export const UpdateCloudflareConnectionSchema = z + .object({ + credentials: CloudflareConnectionApiTokenCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Cloudflare).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Cloudflare)); + +export const CloudflareConnectionListItemSchema = z.object({ + name: z.literal("Cloudflare"), + app: z.literal(AppConnection.Cloudflare), + methods: z.nativeEnum(CloudflareConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts new file mode 100644 index 000000000..52ecf2b83 --- /dev/null +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts @@ -0,0 +1,25 @@ +import z from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { + CloudflareConnectionSchema, + CreateCloudflareConnectionSchema, + ValidateCloudflareConnectionCredentialsSchema +} from "./cloudflare-connection-schema"; + +export type TCloudflareConnection = z.infer; + +export type TCloudflareConnectionInput = z.infer & { + app: AppConnection.Cloudflare; +}; + +export type TValidateCloudflareConnectionCredentialsSchema = typeof ValidateCloudflareConnectionCredentialsSchema; + +export type TCloudflareConnectionConfig = DiscriminativePick< + TCloudflareConnectionInput, + "method" | "app" | "credentials" +> & { + orgId: string; +}; diff --git a/backend/src/services/integration-auth/integration-list.ts b/backend/src/services/integration-auth/integration-list.ts index e8ef72f7a..d83dfec10 100644 --- a/backend/src/services/integration-auth/integration-list.ts +++ b/backend/src/services/integration-auth/integration-list.ts @@ -84,6 +84,7 @@ export enum IntegrationUrls { QOVERY_API_URL = "https://api.qovery.com", TERRAFORM_CLOUD_API_URL = "https://app.terraform.io", CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com", + CLOUDFLARE_API_URL = "https://api.cloudflare.com", // eslint-disable-next-line CLOUDFLARE_WORKERS_API_URL = "https://api.cloudflare.com", BITBUCKET_API_URL = "https://api.bitbucket.org", diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index c27e1e646..561d18107 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -18,6 +18,7 @@ import { AzureDevOpsConnectionMethod, AzureKeyVaultConnectionMethod, CamundaConnectionMethod, + CloudflareConnectionMethod, DatabricksConnectionMethod, FlyioConnectionMethod, GcpConnectionMethod, @@ -82,7 +83,8 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true }, [AppConnection.OnePass]: { name: "1Password", image: "1Password.png" }, [AppConnection.Render]: { name: "Render", image: "Render.png" }, - [AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" } + [AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" }, + [AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { @@ -111,6 +113,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case TerraformCloudConnectionMethod.ApiToken: case VercelConnectionMethod.ApiToken: case OnePassConnectionMethod.ApiToken: + case CloudflareConnectionMethod.ApiToken: return { name: "API Token", icon: faKey }; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index 1f5cb4a3a..425e5e174 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -24,5 +24,6 @@ export enum AppConnection { OCI = "oci", OnePass = "1password", Render = "render", - Flyio = "flyio" + Flyio = "flyio", + Cloudflare = "cloudflare" } diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index e6e545daf..684864ab7 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -118,6 +118,10 @@ export type TFlyioConnectionOption = TAppConnectionOptionBase & { app: AppConnection.Flyio; }; +export type TCloudflareConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.Cloudflare; +}; + export type TAppConnectionOption = | TAwsConnectionOption | TGitHubConnectionOption @@ -142,7 +146,8 @@ export type TAppConnectionOption = | TOCIConnectionOption | TOnePassConnectionOption | TRenderConnectionOption - | TFlyioConnectionOption; + | TFlyioConnectionOption + | TCloudflareConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -171,4 +176,5 @@ export type TAppConnectionOptionMap = { [AppConnection.OnePass]: TOnePassConnectionOption; [AppConnection.Render]: TRenderConnectionOption; [AppConnection.Flyio]: TFlyioConnectionOption; + [AppConnection.Cloudflare]: TCloudflareConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/cloudflare-connection.ts b/frontend/src/hooks/api/appConnections/types/cloudflare-connection.ts new file mode 100644 index 000000000..6ef7dda95 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/cloudflare-connection.ts @@ -0,0 +1,14 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum CloudflareConnectionMethod { + ApiToken = "api-token" +} + +export type TCloudflareConnection = TRootAppConnection & { app: AppConnection.Cloudflare } & { + method: CloudflareConnectionMethod.ApiToken; + credentials: { + apiToken: string; + accountId: string; + }; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index abaac4fad..70b5a04e8 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -8,6 +8,7 @@ import { TAzureClientSecretsConnection } from "./azure-client-secrets-connection import { TAzureDevOpsConnection } from "./azure-devops-connection"; import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; import { TCamundaConnection } from "./camunda-connection"; +import { TCloudflareConnection } from "./cloudflare-connection"; import { TDatabricksConnection } from "./databricks-connection"; import { TFlyioConnection } from "./flyio-connection"; import { TGcpConnection } from "./gcp-connection"; @@ -53,6 +54,7 @@ export * from "./teamcity-connection"; export * from "./terraform-cloud-connection"; export * from "./vercel-connection"; export * from "./windmill-connection"; +export * from "./cloudflare-connection"; export type TAppConnection = | TAwsConnection @@ -80,7 +82,8 @@ export type TAppConnection = | TOCIConnection | TOnePassConnection | TRenderConnection - | TFlyioConnection; + | TFlyioConnection + | TCloudflareConnection; export type TAvailableAppConnection = Pick; @@ -134,4 +137,5 @@ export type TAppConnectionMap = { [AppConnection.OnePass]: TOnePassConnection; [AppConnection.Render]: TRenderConnection; [AppConnection.Flyio]: TFlyioConnection; + [AppConnection.Cloudflare]: TCloudflareConnection; }; 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 99c81a745..0026fc39c 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -35,6 +35,7 @@ import { TeamCityConnectionForm } from "./TeamCityConnectionForm"; import { TerraformCloudConnectionForm } from "./TerraformCloudConnectionForm"; import { VercelConnectionForm } from "./VercelConnectionForm"; import { WindmillConnectionForm } from "./WindmillConnectionForm"; +import { CloudflareConnectionForm } from "./CloudflareConnectionForm"; type FormProps = { onComplete: (appConnection: TAppConnection) => void; @@ -125,6 +126,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { return ; case AppConnection.Flyio: return ; + case AppConnection.Cloudflare: + return ; default: throw new Error(`Unhandled App ${app}`); } @@ -213,6 +216,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Flyio: return ; + case AppConnection.Cloudflare: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); } diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CloudflareConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CloudflareConnectionForm.tsx new file mode 100644 index 000000000..4d277ab04 --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/CloudflareConnectionForm.tsx @@ -0,0 +1,148 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { CloudflareConnectionMethod, TCloudflareConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TCloudflareConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.Cloudflare) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(CloudflareConnectionMethod.ApiToken), + credentials: z.object({ + apiToken: z.string().trim().min(1, "API Token required"), + accountId: z.string().trim().min(1, "Account ID required") + }) + }) +]); + +type FormData = z.infer; + +export const CloudflareConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.Cloudflare, + method: CloudflareConnectionMethod.ApiToken + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + + + )} + /> + ( + + onChange(e.target.value)} /> + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};