diff --git a/.infisicalignore b/.infisicalignore index 8cf00ac26..d705c0d66 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -45,3 +45,4 @@ cli/detect/config/gitleaks.toml:gcp-api-key:582 .github/workflows/helm-release-infisical-core.yml:generic-api-key:48 .github/workflows/helm-release-infisical-core.yml:generic-api-key:47 backend/src/services/smtp/smtp-service.ts:generic-api-key:79 +frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7 diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index c18c87c39..46fd7f2f7 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2397,6 +2397,10 @@ export const SecretSyncs = { }, FLYIO: { appId: "The ID of the Fly.io app to sync secrets to." + }, + CLOUDFLARE_PAGES: { + projectName: "The name of the Cloudflare Pages project to sync secrets to.", + environment: "The environment of the Cloudflare Pages project to sync secrets to." } } }; 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 index c5ca01952..bd3507a7d 100644 --- 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 @@ -1,11 +1,16 @@ -import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import z from "zod"; -import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { - SanitizedCloudflareConnectionSchema, CreateCloudflareConnectionSchema, + SanitizedCloudflareConnectionSchema, UpdateCloudflareConnectionSchema } from "@app/services/app-connection/cloudflare/cloudflare-connection-schema"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; export const registerCloudflareConnectionRouter = async (server: FastifyZodProvider) => { registerAppConnectionEndpoints({ @@ -15,4 +20,34 @@ export const registerCloudflareConnectionRouter = async (server: FastifyZodProvi createSchema: CreateCloudflareConnectionSchema, updateSchema: UpdateCloudflareConnectionSchema }); + + // The below endpoints are not exposed and for Infisical App use + server.route({ + method: "GET", + url: `/:connectionId/cloudflare-pages-projects`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z + .object({ + id: z.string(), + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + + const projects = await server.services.appConnection.cloudflare.listPagesProjects(connectionId, req.permission); + + return projects; + } + }); }; diff --git a/backend/src/server/routes/v1/secret-sync-routers/cloudflare-pages-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/cloudflare-pages-sync-router.ts new file mode 100644 index 000000000..6a70b5837 --- /dev/null +++ b/backend/src/server/routes/v1/secret-sync-routers/cloudflare-pages-sync-router.ts @@ -0,0 +1,16 @@ +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints"; +import { + CloudflarePagesSyncSchema, + CreateCloudflarePagesSyncSchema, + UpdateCloudflarePagesSyncSchema +} from "@app/services/secret-sync/cloudflare-pages/cloudflare-pages-schema"; + +export const registerCloudflarePagesSyncRouter = async (server: FastifyZodProvider) => + registerSyncSecretsEndpoints({ + destination: SecretSync.CloudflarePages, + server, + responseSchema: CloudflarePagesSyncSchema, + createSchema: CreateCloudflarePagesSyncSchema, + updateSchema: UpdateCloudflarePagesSyncSchema + }); diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index 675b74982..2ce05c592 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -19,6 +19,7 @@ import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router"; import { registerWindmillSyncRouter } from "./windmill-sync-router"; +import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; export * from "./secret-sync-router"; @@ -41,5 +42,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record { diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 911f70289..baaa9848e 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -48,6 +48,7 @@ import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-va import { ValidateCamundaConnectionCredentialsSchema } from "./camunda"; import { camundaConnectionService } from "./camunda/camunda-connection-service"; import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema"; +import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service"; import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks"; import { databricksConnectionService } from "./databricks/databricks-connection-service"; import { ValidateFlyioConnectionCredentialsSchema } from "./flyio"; @@ -519,6 +520,7 @@ export const appConnectionServiceFactory = ({ oci: ociConnectionService(connectAppConnectionById, licenseService), onepass: onePassConnectionService(connectAppConnectionById), render: renderConnectionService(connectAppConnectionById), + cloudflare: cloudflareConnectionService(connectAppConnectionById), flyio: flyioConnectionService(connectAppConnectionById) }; }; diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts index ae4e2cb9d..28ad44de0 100644 --- a/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-fns.ts @@ -3,10 +3,14 @@ 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 { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { CloudflareConnectionMethod } from "./cloudflare-connection-enum"; -import { TCloudflareConnectionConfig } from "./cloudflare-connection-types"; -import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { + TCloudflareConnection, + TCloudflareConnectionConfig, + TCloudflarePagesProject +} from "./cloudflare-connection-types"; export const getCloudflareConnectionListItem = () => { return { @@ -16,6 +20,29 @@ export const getCloudflareConnectionListItem = () => { }; }; +export const listCloudflarePagesProjects = async ( + appConnection: TCloudflareConnection +): Promise => { + const { + credentials: { apiToken, accountId } + } = appConnection; + + const { data } = await request.get<{ result: { name: string; id: string }[] }>( + `${IntegrationUrls.CLOUDFLARE_API_URL}/client/v4/accounts/${accountId}/pages/projects`, + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + } + ); + + return data.result.map((a) => ({ + name: a.name, + id: a.id + })); +}; + export const validateCloudflareConnectionCredentials = async (config: TCloudflareConnectionConfig) => { const { apiToken, accountId } = config.credentials; @@ -27,7 +54,7 @@ export const validateCloudflareConnectionCredentials = async (config: TCloudflar } }); - if (resp.data.data === null) { + if (resp.data === null) { throw new BadRequestError({ message: "Unable to validate connection: Invalid API token provided." }); @@ -35,6 +62,7 @@ export const validateCloudflareConnectionCredentials = async (config: TCloudflar } catch (error: unknown) { if (error instanceof AxiosError) { throw new BadRequestError({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access message: `Failed to validate credentials: ${error.response?.data?.errors?.[0]?.message || error.message || "Unknown error"}` }); } diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-service.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-service.ts new file mode 100644 index 000000000..2d1f38786 --- /dev/null +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-service.ts @@ -0,0 +1,30 @@ +import { logger } from "@app/lib/logger"; +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { listCloudflarePagesProjects } from "./cloudflare-connection-fns"; +import { TCloudflareConnection } from "./cloudflare-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +export const cloudflareConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listPagesProjects = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Cloudflare, connectionId, actor); + try { + const projects = await listCloudflarePagesProjects(appConnection); + + return projects; + } catch (error) { + logger.error(error, "Failed to list Cloudflare Pages projects for Cloudflare connection"); + return []; + } + }; + + return { + listPagesProjects + }; +}; diff --git a/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts b/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts index 52ecf2b83..6b2ee0d04 100644 --- a/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts +++ b/backend/src/services/app-connection/cloudflare/cloudflare-connection-types.ts @@ -23,3 +23,8 @@ export type TCloudflareConnectionConfig = DiscriminativePick< > & { orgId: string; }; + +export type TCloudflarePagesProject = { + id: string; + name: string; +}; diff --git a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-constants.ts b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-constants.ts new file mode 100644 index 000000000..4ec02074c --- /dev/null +++ b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-constants.ts @@ -0,0 +1,10 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types"; + +export const CLOUDFLARE_PAGES_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "Cloudflare Pages", + destination: SecretSync.CloudflarePages, + connection: AppConnection.Cloudflare, + canImportSecrets: false +}; diff --git a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-fns.ts b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-fns.ts new file mode 100644 index 000000000..7acb59107 --- /dev/null +++ b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-fns.ts @@ -0,0 +1,138 @@ +import { request } from "@app/lib/config/request"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps"; +import { TCloudflarePagesSyncWithCredentials } from "./cloudflare-pages-types"; + +const getProjectEnvironmentSecrets = async (secretSync: TCloudflarePagesSyncWithCredentials) => { + const { + destinationConfig, + connection: { + credentials: { apiToken, accountId } + } + } = secretSync; + + const secrets = ( + await request.get<{ + result: { + deployment_configs: Record< + string, + { + env_vars: Record; + } + >; + }; + }>( + `${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects/${destinationConfig.projectName}`, + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + } + ) + ).data.result.deployment_configs[destinationConfig.environment].env_vars; + + return Object.entries(secrets ?? []).map(([key, envVar]) => ({ + key, + value: envVar.value + })); +}; + +export const CloudflarePagesSyncFns = { + syncSecrets: async (secretSync: TCloudflarePagesSyncWithCredentials, secretMap: TSecretMap) => { + const { + destinationConfig, + connection: { + credentials: { apiToken, accountId } + } + } = secretSync; + + // Create/update secret entries + let secretEntries: [string, object | null][] = Object.entries(secretMap).map(([key, val]) => [ + key, + { type: "secret_text", value: val.value } + ]); + + // Handle deletions if not disabled + if (!secretSync.syncOptions.disableSecretDeletion) { + const existingSecrets = await getProjectEnvironmentSecrets(secretSync); + const toDeleteKeys = existingSecrets + .filter( + (secret) => + matchesSchema(secret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema) && + !secretMap[secret.key] + ) + .map((secret) => secret.key); + + const toDeleteEntries: [string, null][] = toDeleteKeys.map((key) => [key, null]); + secretEntries = [...secretEntries, ...toDeleteEntries]; + } + + const data = { + deployment_configs: { + [destinationConfig.environment]: { + env_vars: Object.fromEntries(secretEntries) + } + } + }; + + await request.patch( + `${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects/${destinationConfig.projectName}`, + data, + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + } + ); + }, + + getSecrets: async (secretSync: TCloudflarePagesSyncWithCredentials): Promise => { + throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`); + }, + + removeSecrets: async (secretSync: TCloudflarePagesSyncWithCredentials, secretMap: TSecretMap) => { + const { + destinationConfig, + connection: { + credentials: { apiToken, accountId } + } + } = secretSync; + + const secrets = await getProjectEnvironmentSecrets(secretSync); + const toDeleteKeys = secrets + .filter( + (secret) => + matchesSchema(secret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema) && + secret.key in secretMap + ) + .map((secret) => secret.key); + + if (toDeleteKeys.length === 0) return; + + const secretEntries: [string, null][] = toDeleteKeys.map((key) => [key, null]); + + const data = { + deployment_configs: { + [destinationConfig.environment]: { + env_vars: Object.fromEntries(secretEntries) + } + } + }; + + await request.patch( + `${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects/${destinationConfig.projectName}`, + data, + { + headers: { + Authorization: `Bearer ${apiToken}`, + Accept: "application/json" + } + } + ); + } +}; diff --git a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts new file mode 100644 index 000000000..243dbffb2 --- /dev/null +++ b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-schema.ts @@ -0,0 +1,53 @@ +import { z } from "zod"; + +import { SecretSyncs } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { + BaseSecretSyncSchema, + GenericCreateSecretSyncFieldsSchema, + GenericUpdateSecretSyncFieldsSchema +} from "@app/services/secret-sync/secret-sync-schemas"; +import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; + +const CloudflarePagesSyncDestinationConfigSchema = z.object({ + projectName: z + .string() + .min(1, "Project name is required") + .describe(SecretSyncs.DESTINATION_CONFIG.CLOUDFLARE_PAGES.projectName), + environment: z + .string() + .min(1, "Environment is required") + .describe(SecretSyncs.DESTINATION_CONFIG.CLOUDFLARE_PAGES.environment) +}); + +const CloudflarePagesSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const CloudflarePagesSyncSchema = BaseSecretSyncSchema( + SecretSync.CloudflarePages, + CloudflarePagesSyncOptionsConfig +).extend({ + destination: z.literal(SecretSync.CloudflarePages), + destinationConfig: CloudflarePagesSyncDestinationConfigSchema +}); + +export const CreateCloudflarePagesSyncSchema = GenericCreateSecretSyncFieldsSchema( + SecretSync.CloudflarePages, + CloudflarePagesSyncOptionsConfig +).extend({ + destinationConfig: CloudflarePagesSyncDestinationConfigSchema +}); + +export const UpdateCloudflarePagesSyncSchema = GenericUpdateSecretSyncFieldsSchema( + SecretSync.CloudflarePages, + CloudflarePagesSyncOptionsConfig +).extend({ + destinationConfig: CloudflarePagesSyncDestinationConfigSchema.optional() +}); + +export const CloudflarePagesSyncListItemSchema = z.object({ + name: z.literal("Cloudflare Pages"), + connection: z.literal(AppConnection.Cloudflare), + destination: z.literal(SecretSync.CloudflarePages), + canImportSecrets: z.literal(false) +}); diff --git a/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-types.ts b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-types.ts new file mode 100644 index 000000000..e87280206 --- /dev/null +++ b/backend/src/services/secret-sync/cloudflare-pages/cloudflare-pages-types.ts @@ -0,0 +1,19 @@ +import z from "zod"; + +import { TCloudflareConnection } from "@app/services/app-connection/cloudflare/cloudflare-connection-types"; + +import { + CloudflarePagesSyncListItemSchema, + CloudflarePagesSyncSchema, + CreateCloudflarePagesSyncSchema +} from "./cloudflare-pages-schema"; + +export type TCloudflarePagesSyncListItem = z.infer; + +export type TCloudflarePagesSync = z.infer; + +export type TCloudflarePagesSyncInput = z.infer; + +export type TCloudflarePagesSyncWithCredentials = TCloudflarePagesSync & { + connection: TCloudflareConnection; +}; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index 2ac235fe2..d58d6d3a7 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -17,7 +17,8 @@ export enum SecretSync { OCIVault = "oci-vault", OnePass = "1password", Render = "render", - Flyio = "flyio" + Flyio = "flyio", + CloudflarePages = "cloudflare-pages" } export enum SecretSyncInitialSyncBehavior { diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 71c413c11..be7986efb 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -29,6 +29,8 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops"; import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault"; import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda"; +import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants"; +import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns"; import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio"; import { GCP_SYNC_LIST_OPTION } from "./gcp"; import { GcpSyncFns } from "./gcp/gcp-sync-fns"; @@ -61,7 +63,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.OCIVault]: OCI_VAULT_SYNC_LIST_OPTION, [SecretSync.OnePass]: ONEPASS_SYNC_LIST_OPTION, [SecretSync.Render]: RENDER_SYNC_LIST_OPTION, - [SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION + [SecretSync.Flyio]: FLYIO_SYNC_LIST_OPTION, + [SecretSync.CloudflarePages]: CLOUDFLARE_PAGES_SYNC_LIST_OPTION }; export const listSecretSyncOptions = () => { @@ -223,6 +226,8 @@ export const SecretSyncFns = { return RenderSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.Flyio: return FlyioSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.CloudflarePages: + return CloudflarePagesSyncFns.syncSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -306,6 +311,9 @@ export const SecretSyncFns = { case SecretSync.Flyio: secretMap = await FlyioSyncFns.getSecrets(secretSync); break; + case SecretSync.CloudflarePages: + secretMap = await CloudflarePagesSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -377,6 +385,8 @@ export const SecretSyncFns = { return RenderSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.Flyio: return FlyioSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.CloudflarePages: + return CloudflarePagesSyncFns.removeSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 47cd7c164..f4fd2b238 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -20,7 +20,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.OCIVault]: "OCI Vault", [SecretSync.OnePass]: "1Password", [SecretSync.Render]: "Render", - [SecretSync.Flyio]: "Fly.io" + [SecretSync.Flyio]: "Fly.io", + [SecretSync.CloudflarePages]: "Cloudflare Pages" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -42,7 +43,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.OCIVault]: AppConnection.OCI, [SecretSync.OnePass]: AppConnection.OnePass, [SecretSync.Render]: AppConnection.Render, - [SecretSync.Flyio]: AppConnection.Flyio + [SecretSync.Flyio]: AppConnection.Flyio, + [SecretSync.CloudflarePages]: AppConnection.Cloudflare }; export const SECRET_SYNC_PLAN_MAP: Record = { @@ -64,5 +66,6 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.OCIVault]: SecretSyncPlanType.Enterprise, [SecretSync.OnePass]: SecretSyncPlanType.Regular, [SecretSync.Render]: SecretSyncPlanType.Regular, - [SecretSync.Flyio]: SecretSyncPlanType.Regular + [SecretSync.Flyio]: SecretSyncPlanType.Regular, + [SecretSync.CloudflarePages]: SecretSyncPlanType.Regular }; diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index f41d8e27b..a96cab071 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -105,6 +105,12 @@ import { TTerraformCloudSyncWithCredentials } from "./terraform-cloud"; import { TVercelSync, TVercelSyncInput, TVercelSyncListItem, TVercelSyncWithCredentials } from "./vercel"; +import { + TCloudflarePagesSync, + TCloudflarePagesSyncInput, + TCloudflarePagesSyncListItem, + TCloudflarePagesSyncWithCredentials +} from "./cloudflare-pages/cloudflare-pages-types"; export type TSecretSync = | TAwsParameterStoreSync @@ -125,7 +131,8 @@ export type TSecretSync = | TOCIVaultSync | TOnePassSync | TRenderSync - | TFlyioSync; + | TFlyioSync + | TCloudflarePagesSync; export type TSecretSyncWithCredentials = | TAwsParameterStoreSyncWithCredentials @@ -146,7 +153,8 @@ export type TSecretSyncWithCredentials = | TOCIVaultSyncWithCredentials | TOnePassSyncWithCredentials | TRenderSyncWithCredentials - | TFlyioSyncWithCredentials; + | TFlyioSyncWithCredentials + | TCloudflarePagesSyncWithCredentials; export type TSecretSyncInput = | TAwsParameterStoreSyncInput @@ -167,7 +175,8 @@ export type TSecretSyncInput = | TOCIVaultSyncInput | TOnePassSyncInput | TRenderSyncInput - | TFlyioSyncInput; + | TFlyioSyncInput + | TCloudflarePagesSyncInput; export type TSecretSyncListItem = | TAwsParameterStoreSyncListItem @@ -188,7 +197,8 @@ export type TSecretSyncListItem = | TOCIVaultSyncListItem | TOnePassSyncListItem | TRenderSyncListItem - | TFlyioSyncListItem; + | TFlyioSyncListItem + | TCloudflarePagesSyncListItem; export type TSyncOptionsConfig = { canImportSecrets: boolean; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx new file mode 100644 index 000000000..eef8de3cb --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx @@ -0,0 +1,95 @@ +import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { SingleValue } from "react-select"; + +import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; +import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2"; +import { + TCloudflareProject, + useCloudflareConnectionListPagesProjects +} from "@app/hooks/api/appConnections/cloudflare"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +import { TSecretSyncForm } from "../schemas"; + +const CLOUDFLARE_ENVIRONMENTS = [ + { + name: "Preview", + value: "preview" + }, + { + name: "Production", + value: "production" + } +]; + +export const CloudflarePagesSyncFields = () => { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.CloudflarePages } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + + const { data: projects = [], isPending: isProjectsPending } = + useCloudflareConnectionListPagesProjects(connectionId, { + enabled: Boolean(connectionId) + }); + + return ( + <> + { + setValue("destinationConfig.projectName", ""); + setValue("destinationConfig.environment", "preview"); + }} + /> + ( + + project.name === value) ?? []) : []} + onChange={(option) => { + onChange((option as SingleValue)?.name ?? null); + }} + options={projects} + placeholder="Select a project..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id.toString()} + /> + + )} + /> + ( + + + + )} + /> + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx index 5d36ac71f..4c39b06e2 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -10,6 +10,7 @@ import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFiel import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields"; import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields"; import { CamundaSyncFields } from "./CamundaSyncFields"; +import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields"; import { DatabricksSyncFields } from "./DatabricksSyncFields"; import { FlyioSyncFields } from "./FlyioSyncFields"; import { GcpSyncFields } from "./GcpSyncFields"; @@ -67,6 +68,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.Flyio: return ; + case SecretSync.CloudflarePages: + return ; default: throw new Error(`Unhandled Destination Config Field: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index 9e60e164e..762949d78 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -54,6 +54,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.OCIVault: case SecretSync.Render: case SecretSync.Flyio: + case SecretSync.CloudflarePages: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/CloudflarePagesReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/CloudflarePagesReviewFields.tsx new file mode 100644 index 000000000..ab18be5da --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/CloudflarePagesReviewFields.tsx @@ -0,0 +1,18 @@ +import { useFormContext } from "react-hook-form"; + +import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; +import { GenericFieldLabel } from "@app/components/v2"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +export const CloudflarePagesSyncReviewFields = () => { + const { watch } = useFormContext(); + const projectName = watch("destinationConfig.projectName"); + const environment = watch("destinationConfig.environment"); + + return ( + <> + {projectName} + {environment} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index a82400f5b..2c923597b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -19,6 +19,7 @@ import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSy import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields"; import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields"; import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields"; +import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields"; import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields"; import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields"; import { GcpSyncReviewFields } from "./GcpSyncReviewFields"; @@ -112,6 +113,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.Flyio: DestinationFieldsComponent = ; break; + case SecretSync.CloudflarePages: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/schemas/cloudflare-pages-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/cloudflare-pages-sync-destination-schema.ts new file mode 100644 index 000000000..c44a579db --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/cloudflare-pages-sync-destination-schema.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +export const CloudflarePagesSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.CloudflarePages), + destinationConfig: z.object({ + projectName: z.string().trim().min(1, "Project name is required"), + environment: z.string().trim().min(1, "Environment is required") + }) + }) +); diff --git a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts index 70cb4767d..08656978d 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts @@ -7,6 +7,7 @@ import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configur import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema"; import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema"; import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema"; +import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema"; import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema"; import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema"; import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema"; @@ -39,7 +40,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ OCIVaultSyncDestinationSchema, OnePassSyncDestinationSchema, RenderSyncDestinationSchema, - FlyioSyncDestinationSchema + FlyioSyncDestinationSchema, + CloudflarePagesSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index c33e45159..c34942e4d 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -69,6 +69,10 @@ export const SECRET_SYNC_MAP: Record = { [SecretSync.OCIVault]: AppConnection.OCI, [SecretSync.OnePass]: AppConnection.OnePass, [SecretSync.Render]: AppConnection.Render, - [SecretSync.Flyio]: AppConnection.Flyio + [SecretSync.Flyio]: AppConnection.Flyio, + [SecretSync.CloudflarePages]: AppConnection.Cloudflare }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< diff --git a/frontend/src/hooks/api/appConnections/cloudflare/index.ts b/frontend/src/hooks/api/appConnections/cloudflare/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/cloudflare/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/cloudflare/queries.tsx b/frontend/src/hooks/api/appConnections/cloudflare/queries.tsx new file mode 100644 index 000000000..ae2efea99 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/cloudflare/queries.tsx @@ -0,0 +1,37 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { appConnectionKeys } from "../queries"; +import { TCloudflareProject } from "./types"; + +const cloudflareConnectionKeys = { + all: [...appConnectionKeys.all, "cloudflare"] as const, + listPagesProjects: (connectionId: string) => + [...cloudflareConnectionKeys.all, "pages-projects", connectionId] as const +}; + +export const useCloudflareConnectionListPagesProjects = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TCloudflareProject[], + unknown, + TCloudflareProject[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: cloudflareConnectionKeys.listPagesProjects(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/cloudflare/${connectionId}/cloudflare-pages-projects` + ); + + return data; + }, + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/cloudflare/types.ts b/frontend/src/hooks/api/appConnections/cloudflare/types.ts new file mode 100644 index 000000000..ed7bda478 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/cloudflare/types.ts @@ -0,0 +1,4 @@ +export type TCloudflareProject = { + id: string; + name: string; +}; diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index 323266caa..cf3fb4e85 100644 --- a/frontend/src/hooks/api/secretSyncs/enums.ts +++ b/frontend/src/hooks/api/secretSyncs/enums.ts @@ -17,7 +17,8 @@ export enum SecretSync { OCIVault = "oci-vault", OnePass = "1password", Render = "render", - Flyio = "flyio" + Flyio = "flyio", + CloudflarePages = "cloudflare-pages" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/types/cloudflare-pages-sync.ts b/frontend/src/hooks/api/secretSyncs/types/cloudflare-pages-sync.ts new file mode 100644 index 000000000..14fc268b7 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/types/cloudflare-pages-sync.ts @@ -0,0 +1,16 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; +import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync"; + +export type TCloudflarePagesSync = TRootSecretSync & { + destination: SecretSync.CloudflarePages; + destinationConfig: { + projectName: string; + environment: string; + }; + connection: { + app: AppConnection.Cloudflare; + name: string; + id: string; + }; +}; diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index fc8a0da46..9f9200223 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -9,6 +9,7 @@ import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync"; import { TAzureDevOpsSync } from "./azure-devops-sync"; import { TAzureKeyVaultSync } from "./azure-key-vault-sync"; import { TCamundaSync } from "./camunda-sync"; +import { TCloudflarePagesSync } from "./cloudflare-pages-sync"; import { TDatabricksSync } from "./databricks-sync"; import { TFlyioSync } from "./flyio-sync"; import { TGcpSync } from "./gcp-sync"; @@ -47,7 +48,8 @@ export type TSecretSync = | TOCIVaultSync | TOnePassSync | TRenderSync - | TFlyioSync; + | TFlyioSync + | TCloudflarePagesSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/CloudflarePagesSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/CloudflarePagesSyncDestinationCol.tsx new file mode 100644 index 000000000..5bde23796 --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/CloudflarePagesSyncDestinationCol.tsx @@ -0,0 +1,14 @@ +import { TCloudflarePagesSync } from "@app/hooks/api/secretSyncs/types/cloudflare-pages-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TCloudflarePagesSync; +}; + +export const CloudflarePagesSyncDestinationCol = ({ secretSync }: Props) => { + const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync); + + return ; +}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx index ca7b37682..47e5bea47 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx @@ -7,6 +7,7 @@ import { AzureAppConfigurationDestinationSyncCol } from "./AzureAppConfiguration import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol"; import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol"; import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol"; +import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol"; import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol"; import { FlyioSyncDestinationCol } from "./FlyioSyncDestinationCol"; import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol"; @@ -64,6 +65,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.Flyio: return ; + case SecretSync.CloudflarePages: + return ; default: throw new Error( `Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}` diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts index f8ab727a2..71f097628 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts @@ -124,6 +124,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.appId; secondaryText = "App ID"; break; + case SecretSync.CloudflarePages: + primaryText = destinationConfig.projectName; + secondaryText = destinationConfig.environment; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/CloudflarePagesSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/CloudflarePagesSyncDestinationSection.tsx new file mode 100644 index 000000000..f34d8e461 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/CloudflarePagesSyncDestinationSection.tsx @@ -0,0 +1,19 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TCloudflarePagesSync } from "@app/hooks/api/secretSyncs/types/cloudflare-pages-sync"; + +type Props = { + secretSync: TCloudflarePagesSync; +}; + +export const CloudflarePagesSyncDestinationSection = ({ secretSync }: Props) => { + const { + destinationConfig: { projectName, environment } + } = secretSync; + + return ( + <> + {projectName} + {environment} + + ); +}; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx index bd245cc19..8e5de9405 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -18,6 +18,7 @@ import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigura import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationSection"; import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection"; import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection"; +import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection"; import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection"; import { FlyioSyncDestinationSection } from "./FlyioSyncDestinationSection"; import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection"; @@ -102,6 +103,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.Flyio: DestinationComponents = ; break; + case SecretSync.CloudflarePages: + DestinationComponents = ; + break; default: throw new Error(`Unhandled Destination Section components: ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx index 213a08bdc..fc059cac6 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -57,6 +57,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.OnePass: case SecretSync.Render: case SecretSync.Flyio: + case SecretSync.CloudflarePages: AdditionalSyncOptionsComponent = null; break; default: