feat: added camunda app connection

This commit is contained in:
Sheen Capadngan
2025-04-08 21:57:24 +08:00
parent 1da2896bb0
commit 479d6445a7
22 changed files with 528 additions and 15 deletions

View File

@@ -12,6 +12,10 @@ import {
AzureKeyVaultConnectionListItemSchema,
SanitizedAzureKeyVaultConnectionSchema
} from "@app/services/app-connection/azure-key-vault";
import {
CamundaConnectionListItemSchema,
SanitizedCamundaConnectionSchema
} from "@app/services/app-connection/camunda";
import {
DatabricksConnectionListItemSchema,
SanitizedDatabricksConnectionSchema
@@ -39,7 +43,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedDatabricksConnectionSchema.options,
...SanitizedHumanitecConnectionSchema.options,
...SanitizedPostgresConnectionSchema.options,
...SanitizedMsSqlConnectionSchema.options
...SanitizedMsSqlConnectionSchema.options,
...SanitizedCamundaConnectionSchema.options
]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -51,7 +56,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
DatabricksConnectionListItemSchema,
HumanitecConnectionListItemSchema,
PostgresConnectionListItemSchema,
MsSqlConnectionListItemSchema
MsSqlConnectionListItemSchema,
CamundaConnectionListItemSchema
]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {

View File

@@ -0,0 +1,51 @@
import { z } from "zod";
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 {
CreateCamundaConnectionSchema,
SanitizedCamundaConnectionSchema,
UpdateCamundaConnectionSchema
} from "@app/services/app-connection/camunda";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerCamundaConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Camunda,
server,
sanitizedResponseSchema: SanitizedCamundaConnectionSchema,
createSchema: CreateCamundaConnectionSchema,
updateSchema: UpdateCamundaConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/clusters`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
clusters: z.object({ uuid: z.string(), name: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const clusters = await server.services.appConnection.camunda.listClusters(connectionId, req.permission);
return { clusters };
}
});
};

View File

@@ -3,6 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
import { registerAwsConnectionRouter } from "./aws-connection-router";
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";
@@ -22,5 +23,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
[AppConnection.Postgres]: registerPostgresConnectionRouter,
[AppConnection.MsSql]: registerMsSqlConnectionRouter
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
[AppConnection.Camunda]: registerCamundaConnectionRouter
};

View File

@@ -7,7 +7,8 @@ export enum AppConnection {
AzureAppConfiguration = "azure-app-configuration",
Humanitec = "humanitec",
Postgres = "postgres",
MsSql = "mssql"
MsSql = "mssql",
Camunda = "camunda"
}
export enum AWSRegion {

View File

@@ -27,6 +27,7 @@ import {
getAzureKeyVaultConnectionListItem,
validateAzureKeyVaultConnectionCredentials
} from "./azure-key-vault";
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
import {
DatabricksConnectionMethod,
getDatabricksConnectionListItem,
@@ -52,7 +53,8 @@ export const listAppConnectionOptions = () => {
getDatabricksConnectionListItem(),
getHumanitecConnectionListItem(),
getPostgresConnectionListItem(),
getMsSqlConnectionListItem()
getMsSqlConnectionListItem(),
getCamundaConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -108,7 +110,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnect
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator
};
export const validateAppConnectionCredentials = async (
@@ -131,6 +134,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "Service Account Impersonation";
case DatabricksConnectionMethod.ServicePrincipal:
return "Service Principal";
case CamundaConnectionMethod.ClientCredentials:
return "Client Credentials";
case HumanitecConnectionMethod.ApiToken:
return "API Token";
case PostgresConnectionMethod.UsernameAndPassword:
@@ -175,5 +180,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.AzureAppConfiguration]: platformManagedCredentialsNotSupported,
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
[AppConnection.Camunda]: platformManagedCredentialsNotSupported
};

View File

@@ -31,6 +31,8 @@ import { ValidateAwsConnectionCredentialsSchema } from "./aws";
import { awsConnectionService } from "./aws/aws-connection-service";
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
@@ -59,7 +61,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema,
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
@@ -430,6 +433,7 @@ export const appConnectionServiceFactory = ({
gcp: gcpConnectionService(connectAppConnectionById),
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
aws: awsConnectionService(connectAppConnectionById),
humanitec: humanitecConnectionService(connectAppConnectionById)
humanitec: humanitecConnectionService(connectAppConnectionById),
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
};
};

View File

@@ -21,6 +21,12 @@ import {
TAzureKeyVaultConnectionInput,
TValidateAzureKeyVaultConnectionCredentialsSchema
} from "./azure-key-vault";
import {
TCamundaConnection,
TCamundaConnectionConfig,
TCamundaConnectionInput,
TValidateCamundaConnectionCredentialsSchema
} from "./camunda";
import {
TDatabricksConnection,
TDatabricksConnectionConfig,
@@ -62,6 +68,7 @@ export type TAppConnection = { id: string } & (
| THumanitecConnection
| TPostgresConnection
| TMsSqlConnection
| TCamundaConnection
);
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -78,6 +85,7 @@ export type TAppConnectionInput = { id: string } & (
| THumanitecConnectionInput
| TPostgresConnectionInput
| TMsSqlConnectionInput
| TCamundaConnectionInput
);
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
@@ -99,7 +107,8 @@ export type TAppConnectionConfig =
| TAzureAppConfigurationConnectionConfig
| TDatabricksConnectionConfig
| THumanitecConnectionConfig
| TSqlConnectionConfig;
| TSqlConnectionConfig
| TCamundaConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
@@ -110,7 +119,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateDatabricksConnectionCredentialsSchema
| TValidateHumanitecConnectionCredentialsSchema
| TValidatePostgresConnectionCredentialsSchema
| TValidateMsSqlConnectionCredentialsSchema;
| TValidateMsSqlConnectionCredentialsSchema
| TValidateCamundaConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
connectionId: string;

View File

@@ -0,0 +1,3 @@
export enum CamundaConnectionMethod {
ClientCredentials = "client-credentials"
}

View File

@@ -0,0 +1,88 @@
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "../app-connection-dal";
import { CamundaConnectionMethod } from "./camunda-connection-enums";
import { TAuthorizeCamundaConnection, TCamundaConnection, TCamundaConnectionConfig } from "./camunda-connection-types";
export const getCamundaConnectionListItem = () => {
return {
name: "Camunda" as const,
app: AppConnection.Camunda as const,
methods: Object.values(CamundaConnectionMethod) as [CamundaConnectionMethod.ClientCredentials]
};
};
const authorizeCamundaConnection = async ({
clientId,
clientSecret
}: Pick<TCamundaConnection["credentials"], "clientId" | "clientSecret">) => {
const { data } = await request.post<TAuthorizeCamundaConnection>(
IntegrationUrls.CAMUNDA_TOKEN_URL,
{
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
audience: "api.cloud.camunda.io"
},
{
headers: {
"Content-Type": "application/json"
}
}
);
return { accessToken: data.access_token, expiresAt: data.expires_in * 1000 + Date.now() };
};
export const getCamundaConnectionAccessToken = async (
{ id, orgId, credentials }: TCamundaConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const { clientSecret, clientId, accessToken, expiresAt } = credentials;
// get new token if less than 10 minutes from expiry
if (Date.now() < expiresAt - 10_000) {
return accessToken;
}
const authData = await authorizeCamundaConnection({ clientId, clientSecret });
const updatedCredentials: TCamundaConnection["credentials"] = {
...credentials,
...authData
};
const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: updatedCredentials,
orgId,
kmsService
});
await appConnectionDAL.updateById(id, { encryptedCredentials });
return authData.accessToken;
};
export const validateCamundaConnectionCredentials = async (appConnection: TCamundaConnectionConfig) => {
const { credentials } = appConnection;
try {
const { accessToken, expiresAt } = await authorizeCamundaConnection(appConnection.credentials);
return {
...credentials,
accessToken,
expiresAt
};
} catch (e: unknown) {
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
};

View File

@@ -0,0 +1,73 @@
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 { CamundaConnectionMethod } from "./camunda-connection-enums";
const BaseCamundaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Camunda) });
export const CamundaConnectionClientCredentialsInputCredentialsSchema = z.object({
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client Secret required")
});
export const CamundaConnectionClientCredentialsOutputCredentialsSchema = z
.object({
accessToken: z.string(),
expiresAt: z.number()
})
.merge(CamundaConnectionClientCredentialsInputCredentialsSchema);
export const CamundaConnectionSchema = z.intersection(
BaseCamundaConnectionSchema,
z.discriminatedUnion("method", [
z.object({
method: z.literal(CamundaConnectionMethod.ClientCredentials),
credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema
})
])
);
export const SanitizedCamundaConnectionSchema = z.discriminatedUnion("method", [
BaseCamundaConnectionSchema.extend({
method: z.literal(CamundaConnectionMethod.ClientCredentials),
credentials: CamundaConnectionClientCredentialsOutputCredentialsSchema.pick({
clientId: true
})
})
]);
export const ValidateCamundaConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(CamundaConnectionMethod.ClientCredentials)
.describe(AppConnections.CREATE(AppConnection.Camunda).method),
credentials: CamundaConnectionClientCredentialsInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Camunda).credentials
)
})
]);
export const CreateCamundaConnectionSchema = ValidateCamundaConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Camunda)
);
export const UpdateCamundaConnectionSchema = z
.object({
credentials: CamundaConnectionClientCredentialsInputCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Camunda).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Camunda));
export const CamundaConnectionListItemSchema = z.object({
name: z.literal("Camunda"),
app: z.literal(AppConnection.Camunda),
methods: z.nativeEnum(CamundaConnectionMethod).array()
});

View File

@@ -0,0 +1,50 @@
import { request } from "@app/lib/config/request";
import { OrgServiceActor } from "@app/lib/types";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { getCamundaConnectionAccessToken } from "./camunda-connection-fns";
import { TCamundaConnection, TCamundaListClustersResponse } from "./camunda-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TCamundaConnection>;
const listCamundaClusters = async (
appConnection: TCamundaConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const accessToken = await getCamundaConnectionAccessToken(appConnection, appConnectionDAL, kmsService);
const { data } = await request.get<TCamundaListClustersResponse>(`${IntegrationUrls.CAMUNDA_API_URL}/clusters`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
});
return data ?? [];
};
export const camundaConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const listClusters = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Camunda, connectionId, actor);
const clusters = await listCamundaClusters(appConnection, appConnectionDAL, kmsService);
return clusters;
};
return {
listClusters
};
};

View File

@@ -0,0 +1,31 @@
import { z } from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CamundaConnectionSchema,
CreateCamundaConnectionSchema,
ValidateCamundaConnectionCredentialsSchema
} from "./camunda-connection-schema";
export type TCamundaConnection = z.infer<typeof CamundaConnectionSchema>;
export type TCamundaConnectionInput = z.infer<typeof CreateCamundaConnectionSchema> & {
app: AppConnection.Camunda;
};
export type TValidateCamundaConnectionCredentialsSchema = typeof ValidateCamundaConnectionCredentialsSchema;
export type TCamundaConnectionConfig = DiscriminativePick<TCamundaConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TAuthorizeCamundaConnection = {
access_token: string;
scope: string;
token_type: string;
expires_in: number;
};
export type TCamundaListClustersResponse = { uuid: string; name: string }[];

View File

@@ -0,0 +1,4 @@
export * from "./camunda-connection-enums";
export * from "./camunda-connection-fns";
export * from "./camunda-connection-schema";
export * from "./camunda-connection-types";

View File

@@ -63,6 +63,7 @@ export enum IntegrationUrls {
GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token",
GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token",
BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token",
CAMUNDA_TOKEN_URL = "https://login.cloud.camunda.io/oauth/token",
// integration apps endpoints
GCP_API_URL = "https://cloudresourcemanager.googleapis.com",
@@ -94,6 +95,7 @@ export enum IntegrationUrls {
HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql",
AZURE_DEVOPS_API_URL = "https://dev.azure.com",
HUMANITEC_API_URL = "https://api.humanitec.io",
CAMUNDA_API_URL = "https://api.cloud.camunda.io",
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -6,6 +6,7 @@ import {
AwsConnectionMethod,
AzureAppConfigurationConnectionMethod,
AzureKeyVaultConnectionMethod,
CamundaConnectionMethod,
DatabricksConnectionMethod,
GcpConnectionMethod,
GitHubConnectionMethod,
@@ -30,7 +31,8 @@ export const APP_CONNECTION_MAP: Record<AppConnection, { name: string; image: st
[AppConnection.Databricks]: { name: "Databricks", image: "Databricks.png" },
[AppConnection.Humanitec]: { name: "Humanitec", image: "Humanitec.png" },
[AppConnection.Postgres]: { name: "PostgreSQL", image: "Postgres.png" },
[AppConnection.MsSql]: { name: "Microsoft SQL Server", image: "MsSql.png" }
[AppConnection.MsSql]: { name: "Microsoft SQL Server", image: "MsSql.png" },
[AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" }
};
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
@@ -49,6 +51,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
return { name: "Service Account Impersonation", icon: faUser };
case DatabricksConnectionMethod.ServicePrincipal:
return { name: "Service Principal", icon: faUser };
case CamundaConnectionMethod.ClientCredentials:
return { name: "Client Credentials", icon: faKey };
case HumanitecConnectionMethod.ApiToken:
return { name: "API Token", icon: faKey };
case PostgresConnectionMethod.UsernameAndPassword:

View File

@@ -7,5 +7,6 @@ export enum AppConnection {
Databricks = "databricks",
Humanitec = "humanitec",
Postgres = "postgres",
MsSql = "mssql"
MsSql = "mssql",
Camunda = "camunda"
}

View File

@@ -47,6 +47,10 @@ export type TMsSqlConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.MsSql;
};
export type TCamundaConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Camunda;
};
export type TAppConnectionOption =
| TAwsConnectionOption
| TGitHubConnectionOption
@@ -56,7 +60,8 @@ export type TAppConnectionOption =
| TDatabricksConnectionOption
| THumanitecConnectionOption
| TPostgresConnectionOption
| TMsSqlConnectionOption;
| TMsSqlConnectionOption
| TCamundaConnectionOption;
export type TAppConnectionOptionMap = {
[AppConnection.AWS]: TAwsConnectionOption;
@@ -68,4 +73,5 @@ export type TAppConnectionOptionMap = {
[AppConnection.Humanitec]: THumanitecConnectionOption;
[AppConnection.Postgres]: TPostgresConnectionOption;
[AppConnection.MsSql]: TMsSqlConnectionOption;
[AppConnection.Camunda]: TCamundaConnectionOption;
};

View File

@@ -0,0 +1,14 @@
import { AppConnection } from "../enums";
import { TRootAppConnection } from "./root-connection";
export enum CamundaConnectionMethod {
ClientCredentials = "client-credentials"
}
export type TCamundaConnection = TRootAppConnection & { app: AppConnection.Camunda } & {
method: CamundaConnectionMethod.ClientCredentials;
credentials: {
clientId: string;
clientSecret: string;
};
};

View File

@@ -3,6 +3,7 @@ import { TAppConnectionOption } from "./app-options";
import { TAwsConnection } from "./aws-connection";
import { TAzureAppConfigurationConnection } from "./azure-app-configuration-connection";
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
import { TCamundaConnection } from "./camunda-connection";
import { TDatabricksConnection } from "./databricks-connection";
import { TGcpConnection } from "./gcp-connection";
import { TGitHubConnection } from "./github-connection";
@@ -13,6 +14,7 @@ import { TPostgresConnection } from "./postgres-connection";
export * from "./aws-connection";
export * from "./azure-app-configuration-connection";
export * from "./azure-key-vault-connection";
export * from "./camunda-connection";
export * from "./databricks-connection";
export * from "./gcp-connection";
export * from "./github-connection";
@@ -29,7 +31,8 @@ export type TAppConnection =
| TDatabricksConnection
| THumanitecConnection
| TPostgresConnection
| TMsSqlConnection;
| TMsSqlConnection
| TCamundaConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
@@ -66,4 +69,5 @@ export type TAppConnectionMap = {
[AppConnection.Humanitec]: THumanitecConnection;
[AppConnection.Postgres]: TPostgresConnection;
[AppConnection.MsSql]: TMsSqlConnection;
[AppConnection.Camunda]: TCamundaConnection;
};

View File

@@ -12,6 +12,7 @@ import { AppConnectionHeader } from "../AppConnectionHeader";
import { AwsConnectionForm } from "./AwsConnectionForm";
import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm";
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
import { CamundaConnectionForm } from "./CamundaConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
import { GcpConnectionForm } from "./GcpConnectionForm";
import { GitHubConnectionForm } from "./GitHubConnectionForm";
@@ -74,6 +75,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
return <PostgresConnectionForm onSubmit={onSubmit} />;
case AppConnection.MsSql:
return <MsSqlConnectionForm onSubmit={onSubmit} />;
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled App ${app}`);
}
@@ -128,6 +131,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <PostgresConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.MsSql:
return <MsSqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Camunda:
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
}

View File

@@ -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 { CamundaConnectionMethod, TCamundaConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TCamundaConnection;
onSubmit: (formData: FormData) => Promise<void>;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.Camunda)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(CamundaConnectionMethod.ClientCredentials),
credentials: z.object({
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client secret required")
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const CamundaConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.Camunda,
method: CamundaConnectionMethod.ClientCredentials
}
});
const {
handleSubmit,
control,
formState: { isSubmitting, isDirty }
} = form;
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="method"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText={`The method you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.Camunda].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Method"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(CamundaConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.clientId"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Client ID"
>
<Input {...field} placeholder="05810c8f-c44d-4bd0-a327-aab6dd77719b" />
</FormControl>
)}
/>
<Controller
name="credentials.clientSecret"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Client Secret"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Credentials" : "Connect to Camunda"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};