Compare commits
13 Commits
navbar-org
...
fix/postgr
Author | SHA1 | Date | |
---|---|---|---|
b4ef55db4e | |||
307b5d1f87 | |||
54087038c2 | |||
f835bf0ba8 | |||
c79ea0631e | |||
948799822f | |||
c14a431177 | |||
7ef077228e | |||
023079be16 | |||
f95bcabef7 | |||
d5043fdba4 | |||
c4e08b9811 | |||
7784b8a81c |
@ -2282,6 +2282,9 @@ export const AppConnections = {
|
|||||||
},
|
},
|
||||||
RAILWAY: {
|
RAILWAY: {
|
||||||
apiToken: "The API token used to authenticate with Railway."
|
apiToken: "The API token used to authenticate with Railway."
|
||||||
|
},
|
||||||
|
CHECKLY: {
|
||||||
|
apiKey: "The API key used to authenticate with Checkly."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -2488,6 +2491,9 @@ export const SecretSyncs = {
|
|||||||
environmentName: "The Railway environment to sync secrets to.",
|
environmentName: "The Railway environment to sync secrets to.",
|
||||||
serviceId: "The Railway service that secrets should be synced to.",
|
serviceId: "The Railway service that secrets should be synced to.",
|
||||||
serviceName: "The Railway service that secrets should be synced to."
|
serviceName: "The Railway service that secrets should be synced to."
|
||||||
|
},
|
||||||
|
CHECKLY: {
|
||||||
|
accountId: "The ID of the Checkly account to sync secrets to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,10 @@ import {
|
|||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
SanitizedCamundaConnectionSchema
|
SanitizedCamundaConnectionSchema
|
||||||
} from "@app/services/app-connection/camunda";
|
} from "@app/services/app-connection/camunda";
|
||||||
|
import {
|
||||||
|
ChecklyConnectionListItemSchema,
|
||||||
|
SanitizedChecklyConnectionSchema
|
||||||
|
} from "@app/services/app-connection/checkly";
|
||||||
import {
|
import {
|
||||||
CloudflareConnectionListItemSchema,
|
CloudflareConnectionListItemSchema,
|
||||||
SanitizedCloudflareConnectionSchema
|
SanitizedCloudflareConnectionSchema
|
||||||
@ -128,7 +132,8 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedCloudflareConnectionSchema.options,
|
...SanitizedCloudflareConnectionSchema.options,
|
||||||
...SanitizedBitbucketConnectionSchema.options,
|
...SanitizedBitbucketConnectionSchema.options,
|
||||||
...SanitizedZabbixConnectionSchema.options,
|
...SanitizedZabbixConnectionSchema.options,
|
||||||
...SanitizedRailwayConnectionSchema.options
|
...SanitizedRailwayConnectionSchema.options,
|
||||||
|
...SanitizedChecklyConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@ -163,7 +168,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
CloudflareConnectionListItemSchema,
|
CloudflareConnectionListItemSchema,
|
||||||
BitbucketConnectionListItemSchema,
|
BitbucketConnectionListItemSchema,
|
||||||
ZabbixConnectionListItemSchema,
|
ZabbixConnectionListItemSchema,
|
||||||
RailwayConnectionListItemSchema
|
RailwayConnectionListItemSchema,
|
||||||
|
ChecklyConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
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 {
|
||||||
|
CreateChecklyConnectionSchema,
|
||||||
|
SanitizedChecklyConnectionSchema,
|
||||||
|
UpdateChecklyConnectionSchema
|
||||||
|
} from "@app/services/app-connection/checkly";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerChecklyConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.Checkly,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedChecklyConnectionSchema,
|
||||||
|
createSchema: CreateChecklyConnectionSchema,
|
||||||
|
updateSchema: UpdateChecklyConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/accounts`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
accounts: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
runtimeId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const accounts = await server.services.appConnection.checkly.listAccounts(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { accounts };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -11,6 +11,7 @@ import { registerAzureDevOpsConnectionRouter } from "./azure-devops-connection-r
|
|||||||
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
import { registerBitbucketConnectionRouter } from "./bitbucket-connection-router";
|
import { registerBitbucketConnectionRouter } from "./bitbucket-connection-router";
|
||||||
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
||||||
|
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
|
||||||
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
import { registerFlyioConnectionRouter } from "./flyio-connection-router";
|
import { registerFlyioConnectionRouter } from "./flyio-connection-router";
|
||||||
@ -68,5 +69,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Cloudflare]: registerCloudflareConnectionRouter,
|
[AppConnection.Cloudflare]: registerCloudflareConnectionRouter,
|
||||||
[AppConnection.Bitbucket]: registerBitbucketConnectionRouter,
|
[AppConnection.Bitbucket]: registerBitbucketConnectionRouter,
|
||||||
[AppConnection.Zabbix]: registerZabbixConnectionRouter,
|
[AppConnection.Zabbix]: registerZabbixConnectionRouter,
|
||||||
[AppConnection.Railway]: registerRailwayConnectionRouter
|
[AppConnection.Railway]: registerRailwayConnectionRouter,
|
||||||
|
[AppConnection.Checkly]: registerChecklyConnectionRouter
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
ChecklySyncSchema,
|
||||||
|
CreateChecklySyncSchema,
|
||||||
|
UpdateChecklySyncSchema
|
||||||
|
} from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerChecklySyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.Checkly,
|
||||||
|
server,
|
||||||
|
responseSchema: ChecklySyncSchema,
|
||||||
|
createSchema: CreateChecklySyncSchema,
|
||||||
|
updateSchema: UpdateChecklySyncSchema
|
||||||
|
});
|
@ -8,6 +8,7 @@ import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configurati
|
|||||||
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
|
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
|
||||||
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
||||||
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
||||||
|
import { registerChecklySyncRouter } from "./checkly-sync-router";
|
||||||
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
||||||
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
|
||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
@ -54,5 +55,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.CloudflareWorkers]: registerCloudflareWorkersSyncRouter,
|
[SecretSync.CloudflareWorkers]: registerCloudflareWorkersSyncRouter,
|
||||||
|
|
||||||
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
||||||
[SecretSync.Railway]: registerRailwaySyncRouter
|
[SecretSync.Railway]: registerRailwaySyncRouter,
|
||||||
|
[SecretSync.Checkly]: registerChecklySyncRouter
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
|
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
|
||||||
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
||||||
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
||||||
|
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
||||||
import {
|
import {
|
||||||
CloudflarePagesSyncListItemSchema,
|
CloudflarePagesSyncListItemSchema,
|
||||||
CloudflarePagesSyncSchema
|
CloudflarePagesSyncSchema
|
||||||
@ -72,7 +73,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
CloudflareWorkersSyncSchema,
|
CloudflareWorkersSyncSchema,
|
||||||
|
|
||||||
ZabbixSyncSchema,
|
ZabbixSyncSchema,
|
||||||
RailwaySyncSchema
|
RailwaySyncSchema,
|
||||||
|
ChecklySyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
@ -101,7 +103,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
CloudflareWorkersSyncListItemSchema,
|
CloudflareWorkersSyncListItemSchema,
|
||||||
|
|
||||||
ZabbixSyncListItemSchema,
|
ZabbixSyncListItemSchema,
|
||||||
RailwaySyncListItemSchema
|
RailwaySyncListItemSchema,
|
||||||
|
ChecklySyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@ -30,7 +30,8 @@ export enum AppConnection {
|
|||||||
Cloudflare = "cloudflare",
|
Cloudflare = "cloudflare",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Bitbucket = "bitbucket"
|
Bitbucket = "bitbucket",
|
||||||
|
Checkly = "checkly"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
@ -56,6 +56,7 @@ import {
|
|||||||
validateBitbucketConnectionCredentials
|
validateBitbucketConnectionCredentials
|
||||||
} from "./bitbucket";
|
} from "./bitbucket";
|
||||||
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda";
|
||||||
|
import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly";
|
||||||
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum";
|
||||||
import {
|
import {
|
||||||
getCloudflareConnectionListItem,
|
getCloudflareConnectionListItem,
|
||||||
@ -146,7 +147,8 @@ export const listAppConnectionOptions = () => {
|
|||||||
getCloudflareConnectionListItem(),
|
getCloudflareConnectionListItem(),
|
||||||
getZabbixConnectionListItem(),
|
getZabbixConnectionListItem(),
|
||||||
getRailwayConnectionListItem(),
|
getRailwayConnectionListItem(),
|
||||||
getBitbucketConnectionListItem()
|
getBitbucketConnectionListItem(),
|
||||||
|
getChecklyConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -229,7 +231,8 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||||
@ -287,6 +290,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case LdapConnectionMethod.SimpleBind:
|
case LdapConnectionMethod.SimpleBind:
|
||||||
return "Simple Bind";
|
return "Simple Bind";
|
||||||
case RenderConnectionMethod.ApiKey:
|
case RenderConnectionMethod.ApiKey:
|
||||||
|
case ChecklyConnectionMethod.ApiKey:
|
||||||
return "API Key";
|
return "API Key";
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
@ -350,7 +354,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
|
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
|
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
|
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported
|
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.Checkly]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enterpriseAppCheck = async (
|
export const enterpriseAppCheck = async (
|
||||||
|
@ -32,7 +32,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Cloudflare]: "Cloudflare",
|
[AppConnection.Cloudflare]: "Cloudflare",
|
||||||
[AppConnection.Zabbix]: "Zabbix",
|
[AppConnection.Zabbix]: "Zabbix",
|
||||||
[AppConnection.Railway]: "Railway",
|
[AppConnection.Railway]: "Railway",
|
||||||
[AppConnection.Bitbucket]: "Bitbucket"
|
[AppConnection.Bitbucket]: "Bitbucket",
|
||||||
|
[AppConnection.Checkly]: "Checkly"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
|
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
|
||||||
@ -67,5 +68,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
|
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
|
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Railway]: AppConnectionPlanType.Regular,
|
[AppConnection.Railway]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular
|
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
|
||||||
|
[AppConnection.Checkly]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
@ -49,6 +49,8 @@ import { ValidateBitbucketConnectionCredentialsSchema } from "./bitbucket";
|
|||||||
import { bitbucketConnectionService } from "./bitbucket/bitbucket-connection-service";
|
import { bitbucketConnectionService } from "./bitbucket/bitbucket-connection-service";
|
||||||
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
||||||
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||||
|
import { ValidateChecklyConnectionCredentialsSchema } from "./checkly";
|
||||||
|
import { checklyConnectionService } from "./checkly/checkly-connection-service";
|
||||||
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
|
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
|
||||||
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
|
||||||
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
|
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
|
||||||
@ -128,7 +130,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
|
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
|
||||||
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
|
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
|
||||||
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
|
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
|
||||||
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema
|
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
|
||||||
|
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@ -541,6 +544,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
cloudflare: cloudflareConnectionService(connectAppConnectionById),
|
cloudflare: cloudflareConnectionService(connectAppConnectionById),
|
||||||
zabbix: zabbixConnectionService(connectAppConnectionById),
|
zabbix: zabbixConnectionService(connectAppConnectionById),
|
||||||
railway: railwayConnectionService(connectAppConnectionById),
|
railway: railwayConnectionService(connectAppConnectionById),
|
||||||
bitbucket: bitbucketConnectionService(connectAppConnectionById)
|
bitbucket: bitbucketConnectionService(connectAppConnectionById),
|
||||||
|
checkly: checklyConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -68,6 +68,12 @@ import {
|
|||||||
TCamundaConnectionInput,
|
TCamundaConnectionInput,
|
||||||
TValidateCamundaConnectionCredentialsSchema
|
TValidateCamundaConnectionCredentialsSchema
|
||||||
} from "./camunda";
|
} from "./camunda";
|
||||||
|
import {
|
||||||
|
TChecklyConnection,
|
||||||
|
TChecklyConnectionConfig,
|
||||||
|
TChecklyConnectionInput,
|
||||||
|
TValidateChecklyConnectionCredentialsSchema
|
||||||
|
} from "./checkly";
|
||||||
import {
|
import {
|
||||||
TCloudflareConnection,
|
TCloudflareConnection,
|
||||||
TCloudflareConnectionConfig,
|
TCloudflareConnectionConfig,
|
||||||
@ -217,6 +223,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TBitbucketConnection
|
| TBitbucketConnection
|
||||||
| TZabbixConnection
|
| TZabbixConnection
|
||||||
| TRailwayConnection
|
| TRailwayConnection
|
||||||
|
| TChecklyConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
@ -256,6 +263,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TBitbucketConnectionInput
|
| TBitbucketConnectionInput
|
||||||
| TZabbixConnectionInput
|
| TZabbixConnectionInput
|
||||||
| TRailwayConnectionInput
|
| TRailwayConnectionInput
|
||||||
|
| TChecklyConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput =
|
export type TSqlConnectionInput =
|
||||||
@ -302,7 +310,8 @@ export type TAppConnectionConfig =
|
|||||||
| TCloudflareConnectionConfig
|
| TCloudflareConnectionConfig
|
||||||
| TBitbucketConnectionConfig
|
| TBitbucketConnectionConfig
|
||||||
| TZabbixConnectionConfig
|
| TZabbixConnectionConfig
|
||||||
| TRailwayConnectionConfig;
|
| TRailwayConnectionConfig
|
||||||
|
| TChecklyConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
| TValidateAwsConnectionCredentialsSchema
|
| TValidateAwsConnectionCredentialsSchema
|
||||||
@ -336,7 +345,8 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateCloudflareConnectionCredentialsSchema
|
| TValidateCloudflareConnectionCredentialsSchema
|
||||||
| TValidateBitbucketConnectionCredentialsSchema
|
| TValidateBitbucketConnectionCredentialsSchema
|
||||||
| TValidateZabbixConnectionCredentialsSchema
|
| TValidateZabbixConnectionCredentialsSchema
|
||||||
| TValidateRailwayConnectionCredentialsSchema;
|
| TValidateRailwayConnectionCredentialsSchema
|
||||||
|
| TValidateChecklyConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export enum ChecklyConnectionMethod {
|
||||||
|
ApiKey = "api-key"
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
||||||
|
import { ChecklyPublicAPI } from "./checkly-connection-public-client";
|
||||||
|
import { TChecklyConnectionConfig } from "./checkly-connection-types";
|
||||||
|
|
||||||
|
export const getChecklyConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "Checkly" as const,
|
||||||
|
app: AppConnection.Checkly as const,
|
||||||
|
methods: Object.values(ChecklyConnectionMethod)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateChecklyConnectionCredentials = async (config: TChecklyConnectionConfig) => {
|
||||||
|
try {
|
||||||
|
await ChecklyPublicAPI.healthcheck(config);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection - verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.credentials;
|
||||||
|
};
|
@ -0,0 +1,186 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse, HttpStatusCode, isAxiosError } from "axios";
|
||||||
|
|
||||||
|
import { createRequestClient } from "@app/lib/config/request";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
|
import { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
||||||
|
import { TChecklyAccount, TChecklyConnectionConfig, TChecklyVariable } from "./checkly-connection-types";
|
||||||
|
|
||||||
|
export function getChecklyAuthHeaders(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
accountId?: string
|
||||||
|
): Record<string, string> {
|
||||||
|
switch (connection.method) {
|
||||||
|
case ChecklyConnectionMethod.ApiKey:
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${connection.credentials.apiKey}`,
|
||||||
|
...(accountId && { "X-Checkly-Account": accountId })
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported Checkly connection method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChecklyRatelimiter(response: AxiosResponse): {
|
||||||
|
maxAttempts: number;
|
||||||
|
isRatelimited: boolean;
|
||||||
|
wait: () => Promise<void>;
|
||||||
|
} {
|
||||||
|
const wait = () => {
|
||||||
|
return new Promise<void>((res) => {
|
||||||
|
setTimeout(res, 60 * 1000); // Wait for 60 seconds
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRatelimited: response.status === HttpStatusCode.TooManyRequests,
|
||||||
|
wait,
|
||||||
|
maxAttempts: 3
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChecklyPublicClient {
|
||||||
|
private client: AxiosInstance;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = createRequestClient({
|
||||||
|
baseURL: IntegrationUrls.CHECKLY_API_URL,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send<T>(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
config: AxiosRequestConfig & { accountId?: string },
|
||||||
|
retryAttempt = 0
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const response = await this.client.request<T>({
|
||||||
|
...config,
|
||||||
|
timeout: 1000 * 60, // 60 seconds timeout
|
||||||
|
validateStatus: (status) => (status >= 200 && status < 300) || status === HttpStatusCode.TooManyRequests,
|
||||||
|
headers: getChecklyAuthHeaders(connection, config.accountId)
|
||||||
|
});
|
||||||
|
const limiter = getChecklyRatelimiter(response);
|
||||||
|
|
||||||
|
if (limiter.isRatelimited && retryAttempt <= limiter.maxAttempts) {
|
||||||
|
await limiter.wait();
|
||||||
|
return this.send(connection, config, retryAttempt + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
healthcheck(connection: TChecklyConnectionConfig) {
|
||||||
|
switch (connection.method) {
|
||||||
|
case ChecklyConnectionMethod.ApiKey:
|
||||||
|
return this.getChecklyAccounts(connection);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported Checkly connection method`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariables(connection: TChecklyConnectionConfig, accountId: string, limit: number = 50, page: number = 1) {
|
||||||
|
const res = await this.send<TChecklyVariable[]>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/variables`,
|
||||||
|
params: {
|
||||||
|
limit,
|
||||||
|
page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
|
||||||
|
const res = await this.send<TChecklyVariable>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "POST",
|
||||||
|
url: `/v1/variables`,
|
||||||
|
data: variable
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
|
||||||
|
const res = await this.send<TChecklyVariable>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "PUT",
|
||||||
|
url: `/v1/variables/${variable.key}`,
|
||||||
|
data: variable
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariable(connection: TChecklyConnectionConfig, accountId: string, variable: Pick<TChecklyVariable, "key">) {
|
||||||
|
try {
|
||||||
|
const res = await this.send<TChecklyVariable>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/variables/${variable.key}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertVariable(connection: TChecklyConnectionConfig, accountId: string, variable: TChecklyVariable) {
|
||||||
|
const res = await this.getVariable(connection, accountId, variable);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return this.createVariable(connection, accountId, variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateVariable(connection, accountId, variable);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteVariable(
|
||||||
|
connection: TChecklyConnectionConfig,
|
||||||
|
accountId: string,
|
||||||
|
variable: Pick<TChecklyVariable, "key">
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await this.send<TChecklyVariable>(connection, {
|
||||||
|
accountId,
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/v1/variables/${variable.key}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
if (isAxiosError(error) && error.response?.status === HttpStatusCode.NotFound) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChecklyAccounts(connection: TChecklyConnectionConfig) {
|
||||||
|
// This endpoint is in beta and might be subject to changes
|
||||||
|
// Refer: https://developers.checklyhq.com/reference/getv1accounts
|
||||||
|
const res = await this.send<TChecklyAccount[]>(connection, {
|
||||||
|
method: "GET",
|
||||||
|
url: `/v1/accounts`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChecklyPublicAPI = new ChecklyPublicClient();
|
@ -0,0 +1,62 @@
|
|||||||
|
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 { ChecklyConnectionMethod } from "./checkly-connection-constants";
|
||||||
|
|
||||||
|
export const ChecklyConnectionMethodSchema = z
|
||||||
|
.nativeEnum(ChecklyConnectionMethod)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.Checkly).method);
|
||||||
|
|
||||||
|
export const ChecklyConnectionAccessTokenCredentialsSchema = z.object({
|
||||||
|
apiKey: z.string().trim().min(1, "API Key required").max(255).describe(AppConnections.CREDENTIALS.CHECKLY.apiKey)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BaseChecklyConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.Checkly)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChecklyConnectionSchema = BaseChecklyConnectionSchema.extend({
|
||||||
|
method: ChecklyConnectionMethodSchema,
|
||||||
|
credentials: ChecklyConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedChecklyConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseChecklyConnectionSchema.extend({
|
||||||
|
method: ChecklyConnectionMethodSchema,
|
||||||
|
credentials: ChecklyConnectionAccessTokenCredentialsSchema.pick({})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateChecklyConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: ChecklyConnectionMethodSchema,
|
||||||
|
credentials: ChecklyConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.Checkly).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateChecklyConnectionSchema = ValidateChecklyConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.Checkly)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateChecklyConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: ChecklyConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.Checkly).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Checkly));
|
||||||
|
|
||||||
|
export const ChecklyConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Checkly"),
|
||||||
|
app: z.literal(AppConnection.Checkly),
|
||||||
|
methods: z.nativeEnum(ChecklyConnectionMethod).array()
|
||||||
|
});
|
@ -0,0 +1,30 @@
|
|||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { ChecklyPublicAPI } from "./checkly-connection-public-client";
|
||||||
|
import { TChecklyConnection } from "./checkly-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TChecklyConnection>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export const checklyConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listAccounts = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Checkly, connectionId, actor);
|
||||||
|
try {
|
||||||
|
const accounts = await ChecklyPublicAPI.getChecklyAccounts(appConnection);
|
||||||
|
return accounts!;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to list accounts on Checkly");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listAccounts
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,35 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
ChecklyConnectionSchema,
|
||||||
|
CreateChecklyConnectionSchema,
|
||||||
|
ValidateChecklyConnectionCredentialsSchema
|
||||||
|
} from "./checkly-connection-schemas";
|
||||||
|
|
||||||
|
export type TChecklyConnection = z.infer<typeof ChecklyConnectionSchema>;
|
||||||
|
|
||||||
|
export type TChecklyConnectionInput = z.infer<typeof CreateChecklyConnectionSchema> & {
|
||||||
|
app: AppConnection.Checkly;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateChecklyConnectionCredentialsSchema = typeof ValidateChecklyConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TChecklyConnectionConfig = DiscriminativePick<TChecklyConnection, "method" | "app" | "credentials"> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TChecklyVariable = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
locked: boolean;
|
||||||
|
secret: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TChecklyAccount = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
runtimeId: string;
|
||||||
|
};
|
4
backend/src/services/app-connection/checkly/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./checkly-connection-constants";
|
||||||
|
export * from "./checkly-connection-fns";
|
||||||
|
export * from "./checkly-connection-schemas";
|
||||||
|
export * from "./checkly-connection-types";
|
@ -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 CHECKLY_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Checkly",
|
||||||
|
destination: SecretSync.Checkly,
|
||||||
|
connection: AppConnection.Checkly,
|
||||||
|
canImportSecrets: false
|
||||||
|
};
|
102
backend/src/services/secret-sync/checkly/checkly-sync-fns.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/* eslint-disable no-continue */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
|
import { ChecklyPublicAPI } from "@app/services/app-connection/checkly/checkly-connection-public-client";
|
||||||
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
|
|
||||||
|
import { SecretSyncError } from "../secret-sync-errors";
|
||||||
|
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||||
|
import { TSecretMap } from "../secret-sync-types";
|
||||||
|
import { TChecklySyncWithCredentials } from "./checkly-sync-types";
|
||||||
|
|
||||||
|
export const ChecklySyncFns = {
|
||||||
|
async getSecrets(secretSync: TChecklySyncWithCredentials) {
|
||||||
|
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async syncSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
syncOptions: { disableSecretDeletion, keySchema }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
||||||
|
|
||||||
|
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
|
for await (const key of Object.keys(secretMap)) {
|
||||||
|
try {
|
||||||
|
const entry = secretMap[key];
|
||||||
|
|
||||||
|
// If value is empty, we skip the upsert - checkly does not allow empty values
|
||||||
|
if (entry.value.trim() === "") {
|
||||||
|
// Delete the secret from Checkly if its empty
|
||||||
|
if (!disableSecretDeletion) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue; // Skip empty values
|
||||||
|
}
|
||||||
|
|
||||||
|
await ChecklyPublicAPI.upsertVariable(secretSync.connection, config.accountId, {
|
||||||
|
key,
|
||||||
|
value: entry.value,
|
||||||
|
secret: true,
|
||||||
|
locked: true
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableSecretDeletion) return;
|
||||||
|
|
||||||
|
for await (const key of Object.keys(checklySecrets)) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
||||||
|
|
||||||
|
if (!secretMap[key]) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeSecrets(secretSync: TChecklySyncWithCredentials, secretMap: TSecretMap) {
|
||||||
|
const config = secretSync.destinationConfig;
|
||||||
|
|
||||||
|
const variables = await ChecklyPublicAPI.getVariables(secretSync.connection, config.accountId);
|
||||||
|
|
||||||
|
const checklySecrets = Object.fromEntries(variables!.map((variable) => [variable.key, variable]));
|
||||||
|
|
||||||
|
for await (const secret of Object.keys(checklySecrets)) {
|
||||||
|
try {
|
||||||
|
if (secret in secretMap) {
|
||||||
|
await ChecklyPublicAPI.deleteVariable(secretSync.connection, config.accountId, {
|
||||||
|
key: secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
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 ChecklySyncDestinationConfigSchema = z.object({
|
||||||
|
accountId: z.string().min(1, "Account ID is required").max(255, "Account ID must be less than 255 characters"),
|
||||||
|
accountName: z.string().min(1, "Account Name is required").max(255, "Account ID must be less than 255 characters")
|
||||||
|
});
|
||||||
|
|
||||||
|
const ChecklySyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|
||||||
|
export const ChecklySyncSchema = BaseSecretSyncSchema(SecretSync.Checkly, ChecklySyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.Checkly),
|
||||||
|
destinationConfig: ChecklySyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateChecklySyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Checkly,
|
||||||
|
ChecklySyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: ChecklySyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateChecklySyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Checkly,
|
||||||
|
ChecklySyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: ChecklySyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChecklySyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Checkly"),
|
||||||
|
connection: z.literal(AppConnection.Checkly),
|
||||||
|
destination: z.literal(SecretSync.Checkly),
|
||||||
|
canImportSecrets: z.literal(false)
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { TChecklyConnection, TChecklyVariable } from "@app/services/app-connection/checkly";
|
||||||
|
|
||||||
|
import { ChecklySyncListItemSchema, ChecklySyncSchema, CreateChecklySyncSchema } from "./checkly-sync-schemas";
|
||||||
|
|
||||||
|
export type TChecklySyncListItem = z.infer<typeof ChecklySyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TChecklySync = z.infer<typeof ChecklySyncSchema>;
|
||||||
|
|
||||||
|
export type TChecklySyncInput = z.infer<typeof CreateChecklySyncSchema>;
|
||||||
|
|
||||||
|
export type TChecklySyncWithCredentials = TChecklySync & {
|
||||||
|
connection: TChecklyConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TChecklySecret = TChecklyVariable;
|
||||||
|
|
||||||
|
export type TChecklyVariablesGraphResponse = {
|
||||||
|
data: {
|
||||||
|
variables: Record<string, string>;
|
||||||
|
};
|
||||||
|
};
|
@ -24,7 +24,8 @@ export enum SecretSync {
|
|||||||
CloudflareWorkers = "cloudflare-workers",
|
CloudflareWorkers = "cloudflare-workers",
|
||||||
|
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway"
|
Railway = "railway",
|
||||||
|
Checkly = "checkly"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncInitialSyncBehavior {
|
export enum SecretSyncInitialSyncBehavior {
|
||||||
|
@ -29,6 +29,8 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact
|
|||||||
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
|
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
|
||||||
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
|
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
|
||||||
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||||
|
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
||||||
|
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
||||||
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
||||||
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
||||||
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
||||||
@ -76,7 +78,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
|
[SecretSync.CloudflareWorkers]: CLOUDFLARE_WORKERS_SYNC_LIST_OPTION,
|
||||||
|
|
||||||
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION
|
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretSyncOptions = () => {
|
export const listSecretSyncOptions = () => {
|
||||||
@ -250,6 +253,8 @@ export const SecretSyncFns = {
|
|||||||
return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return ZabbixSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return RailwaySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@ -351,6 +356,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
secretMap = await RailwaySyncFns.getSecrets(secretSync);
|
secretMap = await RailwaySyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
secretMap = await ChecklySyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@ -434,6 +442,8 @@ export const SecretSyncFns = {
|
|||||||
return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return ZabbixSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return RailwaySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
|
@ -27,7 +27,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
|
[SecretSync.CloudflareWorkers]: "Cloudflare Workers",
|
||||||
|
|
||||||
[SecretSync.Zabbix]: "Zabbix",
|
[SecretSync.Zabbix]: "Zabbix",
|
||||||
[SecretSync.Railway]: "Railway"
|
[SecretSync.Railway]: "Railway",
|
||||||
|
[SecretSync.Checkly]: "Checkly"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
@ -56,7 +57,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
||||||
|
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
|
[SecretSync.Checkly]: AppConnection.Checkly
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||||
@ -85,5 +87,6 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
|||||||
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
|
[SecretSync.CloudflareWorkers]: SecretSyncPlanType.Regular,
|
||||||
|
|
||||||
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Railway]: SecretSyncPlanType.Regular
|
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Checkly]: SecretSyncPlanType.Regular
|
||||||
};
|
};
|
||||||
|
@ -72,6 +72,12 @@ import {
|
|||||||
TAzureKeyVaultSyncListItem,
|
TAzureKeyVaultSyncListItem,
|
||||||
TAzureKeyVaultSyncWithCredentials
|
TAzureKeyVaultSyncWithCredentials
|
||||||
} from "./azure-key-vault";
|
} from "./azure-key-vault";
|
||||||
|
import {
|
||||||
|
TChecklySync,
|
||||||
|
TChecklySyncInput,
|
||||||
|
TChecklySyncListItem,
|
||||||
|
TChecklySyncWithCredentials
|
||||||
|
} from "./checkly/checkly-sync-types";
|
||||||
import {
|
import {
|
||||||
TCloudflarePagesSync,
|
TCloudflarePagesSync,
|
||||||
TCloudflarePagesSyncInput,
|
TCloudflarePagesSyncInput,
|
||||||
@ -152,7 +158,8 @@ export type TSecretSync =
|
|||||||
| TCloudflarePagesSync
|
| TCloudflarePagesSync
|
||||||
| TCloudflareWorkersSync
|
| TCloudflareWorkersSync
|
||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync;
|
| TRailwaySync
|
||||||
|
| TChecklySync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
@ -179,7 +186,8 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TCloudflarePagesSyncWithCredentials
|
| TCloudflarePagesSyncWithCredentials
|
||||||
| TCloudflareWorkersSyncWithCredentials
|
| TCloudflareWorkersSyncWithCredentials
|
||||||
| TZabbixSyncWithCredentials
|
| TZabbixSyncWithCredentials
|
||||||
| TRailwaySyncWithCredentials;
|
| TRailwaySyncWithCredentials
|
||||||
|
| TChecklySyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
| TAwsParameterStoreSyncInput
|
| TAwsParameterStoreSyncInput
|
||||||
@ -206,7 +214,8 @@ export type TSecretSyncInput =
|
|||||||
| TCloudflarePagesSyncInput
|
| TCloudflarePagesSyncInput
|
||||||
| TCloudflareWorkersSyncInput
|
| TCloudflareWorkersSyncInput
|
||||||
| TZabbixSyncInput
|
| TZabbixSyncInput
|
||||||
| TRailwaySyncInput;
|
| TRailwaySyncInput
|
||||||
|
| TChecklySyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
| TAwsParameterStoreSyncListItem
|
| TAwsParameterStoreSyncListItem
|
||||||
@ -233,7 +242,8 @@ export type TSecretSyncListItem =
|
|||||||
| TCloudflarePagesSyncListItem
|
| TCloudflarePagesSyncListItem
|
||||||
| TCloudflareWorkersSyncListItem
|
| TCloudflareWorkersSyncListItem
|
||||||
| TZabbixSyncListItem
|
| TZabbixSyncListItem
|
||||||
| TRailwaySyncListItem;
|
| TRailwaySyncListItem
|
||||||
|
| TChecklySyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/checkly/available"
|
||||||
|
---
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/checkly"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Checkly Connections](/integrations/app-connections/checkly) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/checkly/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/checkly/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/checkly/connection-name/{connectionName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/checkly"
|
||||||
|
---
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/checkly/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Checkly Connections](/integrations/app-connections/checkly) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/checkly"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/checkly/{syncId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/checkly/{syncId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/checkly/sync-name/{syncName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/checkly"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/checkly/{syncId}/remove-secrets"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/checkly/{syncId}/sync-secrets"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/checkly/{syncId}"
|
||||||
|
---
|
@ -472,6 +472,7 @@
|
|||||||
"integrations/app-connections/azure-key-vault",
|
"integrations/app-connections/azure-key-vault",
|
||||||
"integrations/app-connections/bitbucket",
|
"integrations/app-connections/bitbucket",
|
||||||
"integrations/app-connections/camunda",
|
"integrations/app-connections/camunda",
|
||||||
|
"integrations/app-connections/checkly",
|
||||||
"integrations/app-connections/cloudflare",
|
"integrations/app-connections/cloudflare",
|
||||||
"integrations/app-connections/databricks",
|
"integrations/app-connections/databricks",
|
||||||
"integrations/app-connections/flyio",
|
"integrations/app-connections/flyio",
|
||||||
@ -513,6 +514,7 @@
|
|||||||
"integrations/secret-syncs/azure-devops",
|
"integrations/secret-syncs/azure-devops",
|
||||||
"integrations/secret-syncs/azure-key-vault",
|
"integrations/secret-syncs/azure-key-vault",
|
||||||
"integrations/secret-syncs/camunda",
|
"integrations/secret-syncs/camunda",
|
||||||
|
"integrations/secret-syncs/checkly",
|
||||||
"integrations/secret-syncs/cloudflare-pages",
|
"integrations/secret-syncs/cloudflare-pages",
|
||||||
"integrations/secret-syncs/cloudflare-workers",
|
"integrations/secret-syncs/cloudflare-workers",
|
||||||
"integrations/secret-syncs/databricks",
|
"integrations/secret-syncs/databricks",
|
||||||
@ -1328,6 +1330,17 @@
|
|||||||
"api-reference/endpoints/app-connections/camunda/delete"
|
"api-reference/endpoints/app-connections/camunda/delete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Checkly",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/app-connections/checkly/list",
|
||||||
|
"api-reference/endpoints/app-connections/checkly/get-by-id",
|
||||||
|
"api-reference/endpoints/app-connections/checkly/get-by-name",
|
||||||
|
"api-reference/endpoints/app-connections/checkly/create",
|
||||||
|
"api-reference/endpoints/app-connections/checkly/update",
|
||||||
|
"api-reference/endpoints/app-connections/checkly/delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Cloudflare",
|
"group": "Cloudflare",
|
||||||
"pages": [
|
"pages": [
|
||||||
@ -1708,6 +1721,19 @@
|
|||||||
"api-reference/endpoints/secret-syncs/camunda/remove-secrets"
|
"api-reference/endpoints/secret-syncs/camunda/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Checkly",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/checkly/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Cloudflare Pages",
|
"group": "Cloudflare Pages",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
After Width: | Height: | Size: 532 KiB |
After Width: | Height: | Size: 434 KiB |
After Width: | Height: | Size: 466 KiB |
After Width: | Height: | Size: 491 KiB |
After Width: | Height: | Size: 702 KiB |
After Width: | Height: | Size: 505 KiB |
After Width: | Height: | Size: 571 KiB |
After Width: | Height: | Size: 336 KiB |
After Width: | Height: | Size: 428 KiB |
BIN
docs/images/app-connections/railway/SCR-20250712-pjrc.png
Normal file
After Width: | Height: | Size: 690 KiB |
Before Width: | Height: | Size: 865 KiB After Width: | Height: | Size: 894 KiB |
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 666 KiB |
Before Width: | Height: | Size: 507 KiB After Width: | Height: | Size: 447 KiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-created.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-destination.png
Normal file
After Width: | Height: | Size: 618 KiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-details.png
Normal file
After Width: | Height: | Size: 616 KiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-options.png
Normal file
After Width: | Height: | Size: 677 KiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-review.png
Normal file
After Width: | Height: | Size: 639 KiB |
BIN
docs/images/secret-syncs/checkly/checkly-sync-source.png
Normal file
After Width: | Height: | Size: 599 KiB |
BIN
docs/images/secret-syncs/checkly/select-option.png
Normal file
After Width: | Height: | Size: 690 KiB |
106
docs/integrations/app-connections/checkly.mdx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
title: "Checkly Connection"
|
||||||
|
description: "Learn how to configure a Checkly Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical supports the use of [API Keys](https://app.checklyhq.com/settings/user/api-keys) to connect with Checkly.
|
||||||
|
<Note>
|
||||||
|
Checkly requires the account user to have Read/Write or Admin permissions
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Create a Checkly API Token
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Click the profile image in the top-right corner and select 'User Settings'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="In the user settings sidebar, select 'API Keys'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="In the api keys page, click on 'Create API Key'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Enter a token name and click on 'Create API Key'">
|
||||||
|
Provide a descriptive name for the token.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Copy the generated key and save it">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Create a Checkly Connection in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
In your Infisical dashboard, go to **Organization Settings** and open the [**App Connections**](https://app.infisical.com/organization/app-connections) tab.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select Checkly Connection">
|
||||||
|
Click **+ Add Connection** and choose **Checkly Connection** from the list of integrations.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Fill out the Checkly Connection form">
|
||||||
|
Complete the form by providing:
|
||||||
|
- A descriptive name for the connection
|
||||||
|
- An optional description
|
||||||
|
- The API Key value from the previous step
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Connection created">
|
||||||
|
After submitting the form, your **Checkly Connection** will be successfully created and ready to use with your Infisical projects.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="API">
|
||||||
|
To create a Checkly Connection via API, send a request to the [Create Checkly Connection](/api-reference/endpoints/app-connections/checkly/create) endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/app-connections/checkly \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-checkly-connection",
|
||||||
|
"method": "api-key",
|
||||||
|
"credentials": {
|
||||||
|
"apiKey": "[API KEY]"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"appConnection": {
|
||||||
|
"id": "e5d18aca-86f7-4026-a95e-efb8aeb0d8e6",
|
||||||
|
"name": "my-checkly-connection",
|
||||||
|
"description": null,
|
||||||
|
"version": 1,
|
||||||
|
"orgId": "6f03caa1-a5de-43ce-b127-95a145d3464c",
|
||||||
|
"createdAt": "2025-04-23T19:46:34.831Z",
|
||||||
|
"updatedAt": "2025-04-23T19:46:34.831Z",
|
||||||
|
"isPlatformManagedCredentials": false,
|
||||||
|
"credentialsHash": "7c2d371dec195f82a6a0d5b41c970a229cfcaf88e894a5b6395e2dbd0280661f",
|
||||||
|
"app": "checkly",
|
||||||
|
"method": "api-key",
|
||||||
|
"credentials": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
@ -30,6 +30,14 @@ Infisical supports connecting to PostgreSQL using a database role.
|
|||||||
-- enable permissions to alter login credentials
|
-- enable permissions to alter login credentials
|
||||||
ALTER ROLE infisical_role WITH CREATEROLE;
|
ALTER ROLE infisical_role WITH CREATEROLE;
|
||||||
```
|
```
|
||||||
|
<Tip>
|
||||||
|
In some configurations, the role performing the rotation must be explicitly granted access to manage each user. To do this, grant the user's role to the rotation role with:
|
||||||
|
```SQL
|
||||||
|
-- grant each user role to admin user for password rotation
|
||||||
|
GRANT <secret_rotation_user> TO <infisical_role> WITH ADMIN OPTION;
|
||||||
|
```
|
||||||
|
Replace `<secret_rotation_user>` with each specific username whose credentials will be rotated, and `<infisical_role>` with the role that will perform the rotation.
|
||||||
|
</Tip>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Step>
|
</Step>
|
||||||
|
163
docs/integrations/secret-syncs/checkly.mdx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
title: "Checkly Sync"
|
||||||
|
description: "Learn how to configure a Checkly Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Create a [Checkly Connection](/integrations/app-connections/checkly)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Add Sync">
|
||||||
|
Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'Checkly'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure source">
|
||||||
|
Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Environment**: The project environment to retrieve secrets from.
|
||||||
|
- **Secret Path**: The folder path to retrieve secrets from.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
|
||||||
|
</Tip>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure destination">
|
||||||
|
Configure the **Destination** to where secrets should be deployed, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Checkly Connection**: The Checkly Connection to authenticate with.
|
||||||
|
- **Account**: The Checkly account to sync secrets to.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Sync Options">
|
||||||
|
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
||||||
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
|
<Note>
|
||||||
|
Checkly does not support importing secrets.
|
||||||
|
</Note>
|
||||||
|
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
|
||||||
|
<Note>
|
||||||
|
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
|
||||||
|
</Note>
|
||||||
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure details">
|
||||||
|
Configure the **Details** of your Checkly Sync, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
</Step>
|
||||||
|
<Step title="Review configuration">
|
||||||
|
Review your Checkly Sync configuration, then click **Create Sync**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Sync created">
|
||||||
|
If enabled, your Checkly Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create a **Checkly Sync**, make an API request to the [Create Checkly Sync](/api-reference/endpoints/secret-syncs/checkly/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/checkly \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-checkly-sync",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "an example sync",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/my-secrets",
|
||||||
|
"isEnabled": true,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"destinationConfig": {
|
||||||
|
"accountId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"accountName": "Example Company"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-checkly-sync",
|
||||||
|
"description": "an example sync",
|
||||||
|
"isEnabled": true,
|
||||||
|
"version": 1,
|
||||||
|
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2023-11-07T05:31:56Z",
|
||||||
|
"updatedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"syncStatus": "succeeded",
|
||||||
|
"lastSyncJobId": "123",
|
||||||
|
"lastSyncMessage": null,
|
||||||
|
"lastSyncedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"importStatus": null,
|
||||||
|
"lastImportJobId": null,
|
||||||
|
"lastImportMessage": null,
|
||||||
|
"lastImportedAt": null,
|
||||||
|
"removeStatus": null,
|
||||||
|
"lastRemoveJobId": null,
|
||||||
|
"lastRemoveMessage": null,
|
||||||
|
"lastRemovedAt": null,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination",
|
||||||
|
"autoSyncEnabled": true,
|
||||||
|
"disableSecretDeletion": false
|
||||||
|
},
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connection": {
|
||||||
|
"app": "checkly",
|
||||||
|
"name": "my-checkly-connection",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"slug": "dev",
|
||||||
|
"name": "Development",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"path": "/my-secrets"
|
||||||
|
},
|
||||||
|
"destination": "checkly",
|
||||||
|
"destinationConfig": {
|
||||||
|
"accountId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"accountName": "Example Company",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
</Tabs>
|
@ -63,6 +63,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
|
|||||||
const { image, name } = SECRET_SYNC_MAP[destination];
|
const { image, name } = SECRET_SYNC_MAP[destination];
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
key={name}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
enterprise && !subscription.enterpriseSecretSyncs
|
enterprise && !subscription.enterpriseSecretSyncs
|
||||||
|
@ -60,7 +60,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
? undefined
|
? undefined
|
||||||
: SecretSyncInitialSyncBehavior.OverwriteDestination
|
: SecretSyncInitialSyncBehavior.OverwriteDestination
|
||||||
}
|
}
|
||||||
},
|
} as Partial<TSecretSyncForm>,
|
||||||
reValidateMode: "onChange"
|
reValidateMode: "onChange"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
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 } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
TChecklyAccount,
|
||||||
|
useChecklyConnectionListAccounts
|
||||||
|
} from "@app/hooks/api/appConnections/checkly";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const ChecklySyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.Checkly }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
|
||||||
|
const { data: accounts = [], isPending: isAccountsLoading } = useChecklyConnectionListAccounts(
|
||||||
|
connectionId,
|
||||||
|
{
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.accountId", "");
|
||||||
|
setValue("destinationConfig.accountName", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.accountId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Select an account"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isAccountsLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={accounts.find((p) => p.id === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TChecklyAccount>;
|
||||||
|
onChange(v?.id ?? null);
|
||||||
|
setValue("destinationConfig.accountName", v?.name ?? "");
|
||||||
|
}}
|
||||||
|
options={accounts}
|
||||||
|
placeholder="Select an account..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -10,6 +10,7 @@ import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFiel
|
|||||||
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
|
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
|
||||||
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
|
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
|
||||||
import { CamundaSyncFields } from "./CamundaSyncFields";
|
import { CamundaSyncFields } from "./CamundaSyncFields";
|
||||||
|
import { ChecklySyncFields } from "./ChecklySyncFields";
|
||||||
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
||||||
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
|
import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields";
|
||||||
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
||||||
@ -85,6 +86,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <ZabbixSyncFields />;
|
return <ZabbixSyncFields />;
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
return <RailwaySyncFields />;
|
return <RailwaySyncFields />;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
return <ChecklySyncFields />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.CloudflareWorkers:
|
case SecretSync.CloudflareWorkers:
|
||||||
case SecretSync.Zabbix:
|
case SecretSync.Zabbix:
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
|
case SecretSync.Checkly:
|
||||||
AdditionalSyncOptionsFieldsComponent = null;
|
AdditionalSyncOptionsFieldsComponent = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
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 ChecklySyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Checkly }>();
|
||||||
|
const accountName = watch("destinationConfig.accountName");
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="Account">{accountName}</GenericFieldLabel>;
|
||||||
|
};
|
@ -19,6 +19,7 @@ import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSy
|
|||||||
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
|
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
|
||||||
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
|
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
|
||||||
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
||||||
|
import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
|
||||||
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
||||||
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
|
import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields";
|
||||||
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
||||||
@ -136,6 +137,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
DestinationFieldsComponent = <RailwaySyncReviewFields />;
|
DestinationFieldsComponent = <RailwaySyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
DestinationFieldsComponent = <ChecklySyncReviewFields />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||||
}
|
}
|
||||||
|
@ -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 ChecklySyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||||
|
z.object({
|
||||||
|
destination: z.literal(SecretSync.Checkly),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
accountId: z.string(),
|
||||||
|
accountName: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
@ -7,6 +7,7 @@ import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configur
|
|||||||
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
|
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
|
||||||
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
|
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
|
||||||
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
|
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
|
||||||
|
import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema";
|
||||||
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
||||||
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
|
import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema";
|
||||||
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
||||||
@ -52,7 +53,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
|||||||
CloudflareWorkersSyncDestinationSchema,
|
CloudflareWorkersSyncDestinationSchema,
|
||||||
|
|
||||||
ZabbixSyncDestinationSchema,
|
ZabbixSyncDestinationSchema,
|
||||||
RailwaySyncDestinationSchema
|
RailwaySyncDestinationSchema,
|
||||||
|
ChecklySyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
||||||
|
@ -41,6 +41,7 @@ import {
|
|||||||
ZabbixConnectionMethod
|
ZabbixConnectionMethod
|
||||||
} from "@app/hooks/api/appConnections/types";
|
} from "@app/hooks/api/appConnections/types";
|
||||||
import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection";
|
import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection";
|
||||||
|
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
|
||||||
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
|
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
|
||||||
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-connection";
|
||||||
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
|
||||||
@ -94,7 +95,8 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
[AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" },
|
[AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" },
|
||||||
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
|
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
|
||||||
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
|
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
|
||||||
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" }
|
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
|
||||||
|
[AppConnection.Checkly]: { name: "Checkly", image: "Checkly.png" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
|
||||||
@ -155,7 +157,9 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
case RailwayConnectionMethod.ProjectToken:
|
case RailwayConnectionMethod.ProjectToken:
|
||||||
return { name: "Project Token", icon: faKey };
|
return { name: "Project Token", icon: faKey };
|
||||||
case RenderConnectionMethod.ApiKey:
|
case RenderConnectionMethod.ApiKey:
|
||||||
|
case ChecklyConnectionMethod.ApiKey:
|
||||||
return { name: "API Key", icon: faKey };
|
return { name: "API Key", icon: faKey };
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
|||||||
[SecretSync.Railway]: {
|
[SecretSync.Railway]: {
|
||||||
name: "Railway",
|
name: "Railway",
|
||||||
image: "Railway.png"
|
image: "Railway.png"
|
||||||
|
},
|
||||||
|
[SecretSync.Checkly]: {
|
||||||
|
name: "Checkly",
|
||||||
|
image: "Checkly.png"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,7 +126,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
||||||
|
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
|
[SecretSync.Checkly]: AppConnection.Checkly
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||||
|
2
frontend/src/hooks/api/appConnections/checkly/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./queries";
|
||||||
|
export * from "./types";
|
37
frontend/src/hooks/api/appConnections/checkly/queries.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||||
|
|
||||||
|
import { TChecklyAccount } from "./types";
|
||||||
|
|
||||||
|
const checklyConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "checkly"] as const,
|
||||||
|
listAccounts: (connectionId: string) =>
|
||||||
|
[...checklyConnectionKeys.all, "workspace-scopes", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChecklyConnectionListAccounts = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TChecklyAccount[],
|
||||||
|
unknown,
|
||||||
|
TChecklyAccount[],
|
||||||
|
ReturnType<typeof checklyConnectionKeys.listAccounts>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: checklyConnectionKeys.listAccounts(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ accounts: TChecklyAccount[] }>(
|
||||||
|
`/api/v1/app-connections/checkly/${connectionId}/accounts`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.accounts;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
5
frontend/src/hooks/api/appConnections/checkly/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type TChecklyAccount = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
runtimeId: string;
|
||||||
|
};
|
@ -30,5 +30,6 @@ export enum AppConnection {
|
|||||||
Cloudflare = "cloudflare",
|
Cloudflare = "cloudflare",
|
||||||
Bitbucket = "bitbucket",
|
Bitbucket = "bitbucket",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway"
|
Railway = "railway",
|
||||||
|
Checkly = "checkly"
|
||||||
}
|
}
|
||||||
|
@ -144,6 +144,10 @@ export type TRailwayConnectionOption = TAppConnectionOptionBase & {
|
|||||||
app: AppConnection.Railway;
|
app: AppConnection.Railway;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TChecklyConnectionOption = TAppConnectionOptionBase & {
|
||||||
|
app: AppConnection.Checkly;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAppConnectionOption =
|
export type TAppConnectionOption =
|
||||||
| TAwsConnectionOption
|
| TAwsConnectionOption
|
||||||
| TGitHubConnectionOption
|
| TGitHubConnectionOption
|
||||||
@ -174,7 +178,8 @@ export type TAppConnectionOption =
|
|||||||
| TCloudflareConnectionOption
|
| TCloudflareConnectionOption
|
||||||
| TBitbucketConnectionOption
|
| TBitbucketConnectionOption
|
||||||
| TZabbixConnectionOption
|
| TZabbixConnectionOption
|
||||||
| TRailwayConnectionOption;
|
| TRailwayConnectionOption
|
||||||
|
| TChecklyConnectionOption;
|
||||||
|
|
||||||
export type TAppConnectionOptionMap = {
|
export type TAppConnectionOptionMap = {
|
||||||
[AppConnection.AWS]: TAwsConnectionOption;
|
[AppConnection.AWS]: TAwsConnectionOption;
|
||||||
@ -209,4 +214,5 @@ export type TAppConnectionOptionMap = {
|
|||||||
[AppConnection.Bitbucket]: TBitbucketConnectionOption;
|
[AppConnection.Bitbucket]: TBitbucketConnectionOption;
|
||||||
[AppConnection.Zabbix]: TZabbixConnectionOption;
|
[AppConnection.Zabbix]: TZabbixConnectionOption;
|
||||||
[AppConnection.Railway]: TRailwayConnectionOption;
|
[AppConnection.Railway]: TRailwayConnectionOption;
|
||||||
|
[AppConnection.Checkly]: TChecklyConnectionOption;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||||
|
|
||||||
|
export enum ChecklyConnectionMethod {
|
||||||
|
ApiKey = "api-key"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TChecklyConnection = TRootAppConnection & {
|
||||||
|
app: AppConnection.Checkly;
|
||||||
|
method: ChecklyConnectionMethod.ApiKey;
|
||||||
|
credentials: {
|
||||||
|
apiKey: string;
|
||||||
|
};
|
||||||
|
};
|
@ -9,6 +9,7 @@ import { TAzureDevOpsConnection } from "./azure-devops-connection";
|
|||||||
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
|
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
|
||||||
import { TBitbucketConnection } from "./bitbucket-connection";
|
import { TBitbucketConnection } from "./bitbucket-connection";
|
||||||
import { TCamundaConnection } from "./camunda-connection";
|
import { TCamundaConnection } from "./camunda-connection";
|
||||||
|
import { TChecklyConnection } from "./checkly-connection";
|
||||||
import { TCloudflareConnection } from "./cloudflare-connection";
|
import { TCloudflareConnection } from "./cloudflare-connection";
|
||||||
import { TDatabricksConnection } from "./databricks-connection";
|
import { TDatabricksConnection } from "./databricks-connection";
|
||||||
import { TFlyioConnection } from "./flyio-connection";
|
import { TFlyioConnection } from "./flyio-connection";
|
||||||
@ -97,7 +98,8 @@ export type TAppConnection =
|
|||||||
| TCloudflareConnection
|
| TCloudflareConnection
|
||||||
| TBitbucketConnection
|
| TBitbucketConnection
|
||||||
| TZabbixConnection
|
| TZabbixConnection
|
||||||
| TRailwayConnection;
|
| TRailwayConnection
|
||||||
|
| TChecklyConnection;
|
||||||
|
|
||||||
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
|
||||||
|
|
||||||
@ -157,4 +159,5 @@ export type TAppConnectionMap = {
|
|||||||
[AppConnection.Bitbucket]: TBitbucketConnection;
|
[AppConnection.Bitbucket]: TBitbucketConnection;
|
||||||
[AppConnection.Zabbix]: TZabbixConnection;
|
[AppConnection.Zabbix]: TZabbixConnection;
|
||||||
[AppConnection.Railway]: TRailwayConnection;
|
[AppConnection.Railway]: TRailwayConnection;
|
||||||
|
[AppConnection.Checkly]: TChecklyConnection;
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,7 @@ export type TSecretApprovalRequest = {
|
|||||||
isReplicated?: boolean;
|
isReplicated?: boolean;
|
||||||
slug: string;
|
slug: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
committerUserId: string;
|
committerUserId: string;
|
||||||
reviewers: {
|
reviewers: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -24,7 +24,8 @@ export enum SecretSync {
|
|||||||
CloudflareWorkers = "cloudflare-workers",
|
CloudflareWorkers = "cloudflare-workers",
|
||||||
|
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway"
|
Railway = "railway",
|
||||||
|
Checkly = "checkly"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncStatus {
|
export enum SecretSyncStatus {
|
||||||
|
17
frontend/src/hooks/api/secretSyncs/types/checkly-sync.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||||
|
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 TChecklySync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.Checkly;
|
||||||
|
destinationConfig: {
|
||||||
|
accountId: string;
|
||||||
|
accountName: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.Checkly;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
@ -9,6 +9,7 @@ import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
|
|||||||
import { TAzureDevOpsSync } from "./azure-devops-sync";
|
import { TAzureDevOpsSync } from "./azure-devops-sync";
|
||||||
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
|
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
|
||||||
import { TCamundaSync } from "./camunda-sync";
|
import { TCamundaSync } from "./camunda-sync";
|
||||||
|
import { TChecklySync } from "./checkly-sync";
|
||||||
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
||||||
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
|
import { TCloudflareWorkersSync } from "./cloudflare-workers-sync";
|
||||||
import { TDatabricksSync } from "./databricks-sync";
|
import { TDatabricksSync } from "./databricks-sync";
|
||||||
@ -59,7 +60,8 @@ export type TSecretSync =
|
|||||||
| TCloudflarePagesSync
|
| TCloudflarePagesSync
|
||||||
| TCloudflareWorkersSync
|
| TCloudflareWorkersSync
|
||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync;
|
| TRailwaySync
|
||||||
|
| TChecklySync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }:
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
reset({
|
reset({
|
||||||
|
method: EnrollmentMethod.EST,
|
||||||
caChain: data.caChain,
|
caChain: data.caChain,
|
||||||
isEnabled: data.isEnabled,
|
isEnabled: data.isEnabled,
|
||||||
disableBootstrapCertValidation: data.disableBootstrapCertValidation
|
disableBootstrapCertValidation: data.disableBootstrapCertValidation
|
||||||
|
@ -3,6 +3,7 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
faCertificate,
|
faCertificate,
|
||||||
|
faCog,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faPencil,
|
faPencil,
|
||||||
faPlus,
|
faPlus,
|
||||||
@ -12,6 +13,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@ -40,12 +42,14 @@ import {
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionPkiTemplateActions,
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
|
useSubscription,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteCertTemplateV2 } from "@app/hooks/api";
|
import { useDeleteCertTemplateV2 } from "@app/hooks/api";
|
||||||
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
|
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
|
||||||
|
|
||||||
|
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
|
||||||
import { PkiTemplateForm } from "./components/PkiTemplateForm";
|
import { PkiTemplateForm } from "./components/PkiTemplateForm";
|
||||||
|
|
||||||
const PER_PAGE_INIT = 25;
|
const PER_PAGE_INIT = 25;
|
||||||
@ -56,9 +60,13 @@ export const PkiTemplateListPage = () => {
|
|||||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"certificateTemplate",
|
"certificateTemplate",
|
||||||
"deleteTemplate"
|
"deleteTemplate",
|
||||||
|
"enrollmentOptions",
|
||||||
|
"estUpgradePlan"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { data, isPending } = useListCertificateTemplates({
|
const { data, isPending } = useListCertificateTemplates({
|
||||||
projectId: currentWorkspace.id,
|
projectId: currentWorkspace.id,
|
||||||
offset: (page - 1) * perPage,
|
offset: (page - 1) * perPage,
|
||||||
@ -92,7 +100,7 @@ export const PkiTemplateListPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
|
<title>{t("common.head-title", { title: "PKI Templates" })}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
<div className="container mx-auto flex flex-col justify-between text-white">
|
<div className="container mx-auto flex flex-col justify-between text-white">
|
||||||
@ -177,7 +185,33 @@ export const PkiTemplateListPage = () => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionPkiTemplateActions.Edit}
|
||||||
|
a={ProjectPermissionSub.CertificateTemplates}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
!isAllowed &&
|
||||||
|
"pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!subscription.pkiEst) {
|
||||||
|
handlePopUpOpen("estUpgradePlan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlePopUpOpen("enrollmentOptions", {
|
||||||
|
id: template.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
|
icon={<FontAwesomeIcon icon={faCog} />}
|
||||||
|
>
|
||||||
|
Manage Enrollment
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionPkiTemplateActions.Delete}
|
I={ProjectPermissionPkiTemplateActions.Delete}
|
||||||
a={ProjectPermissionSub.CertificateTemplates}
|
a={ProjectPermissionSub.CertificateTemplates}
|
||||||
@ -251,7 +285,13 @@ export const PkiTemplateListPage = () => {
|
|||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
</div>
|
</div>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.estUpgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("estUpgradePlan", isOpen)}
|
||||||
|
text="You can only configure template enrollment methods if you switch to Infisical's Enterprise plan."
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,7 @@ import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm";
|
|||||||
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
||||||
import { BitbucketConnectionForm } from "./BitbucketConnectionForm";
|
import { BitbucketConnectionForm } from "./BitbucketConnectionForm";
|
||||||
import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
||||||
|
import { ChecklyConnectionForm } from "./ChecklyConnectionForm";
|
||||||
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
||||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||||
import { FlyioConnectionForm } from "./FlyioConnectionForm";
|
import { FlyioConnectionForm } from "./FlyioConnectionForm";
|
||||||
@ -143,6 +144,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
|||||||
return <ZabbixConnectionForm onSubmit={onSubmit} />;
|
return <ZabbixConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Railway:
|
case AppConnection.Railway:
|
||||||
return <RailwayConnectionForm onSubmit={onSubmit} />;
|
return <RailwayConnectionForm onSubmit={onSubmit} />;
|
||||||
|
case AppConnection.Checkly:
|
||||||
|
return <ChecklyConnectionForm onSubmit={onSubmit} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled App ${app}`);
|
throw new Error(`Unhandled App ${app}`);
|
||||||
}
|
}
|
||||||
@ -243,6 +246,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
|||||||
return <ZabbixConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <ZabbixConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Railway:
|
case AppConnection.Railway:
|
||||||
return <RailwayConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <RailwayConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
|
case AppConnection.Checkly:
|
||||||
|
return <ChecklyConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
ModalClose,
|
||||||
|
SecretInput,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import {
|
||||||
|
ChecklyConnectionMethod,
|
||||||
|
TChecklyConnection
|
||||||
|
} from "@app/hooks/api/appConnections/types/checkly-connection";
|
||||||
|
|
||||||
|
import {
|
||||||
|
genericAppConnectionFieldsSchema,
|
||||||
|
GenericAppConnectionsFields
|
||||||
|
} from "./GenericAppConnectionFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appConnection?: TChecklyConnection;
|
||||||
|
onSubmit: (formData: FormData) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
|
app: z.literal(AppConnection.Checkly)
|
||||||
|
});
|
||||||
|
|
||||||
|
const formSchema = z.discriminatedUnion("method", [
|
||||||
|
rootSchema.extend({
|
||||||
|
method: z.literal(ChecklyConnectionMethod.ApiKey),
|
||||||
|
credentials: z.object({
|
||||||
|
apiKey: z.string().trim().min(1, "Service API Key required")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const ChecklyConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||||
|
const isUpdate = Boolean(appConnection);
|
||||||
|
|
||||||
|
const form = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: appConnection ?? {
|
||||||
|
app: AppConnection.Checkly,
|
||||||
|
method: ChecklyConnectionMethod.ApiKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 type of token you would like to use to connect with ${
|
||||||
|
APP_CONNECTION_MAP[AppConnection.Checkly].name
|
||||||
|
}. This field cannot be changed after creation.`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Token Type"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
isDisabled={isUpdate}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{Object.values(ChecklyConnectionMethod).map((method) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={method} key={method}>
|
||||||
|
{getAppConnectionMethodDetails(method).name}{" "}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="credentials.apiKey"
|
||||||
|
control={control}
|
||||||
|
shouldUnregister
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="API Key Value"
|
||||||
|
>
|
||||||
|
<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 Checkly"}
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import { TChecklySync } from "@app/hooks/api/secretSyncs/types/checkly-sync";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TChecklySync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChecklySyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
};
|
@ -7,6 +7,7 @@ import { AzureAppConfigurationDestinationSyncCol } from "./AzureAppConfiguration
|
|||||||
import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol";
|
import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol";
|
||||||
import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol";
|
import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol";
|
||||||
import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol";
|
import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol";
|
||||||
|
import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol";
|
||||||
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
||||||
import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol";
|
import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol";
|
||||||
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
|
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
|
||||||
@ -82,6 +83,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
return <ZabbixSyncDestinationCol secretSync={secretSync} />;
|
return <ZabbixSyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
return <RailwaySyncDestinationCol secretSync={secretSync} />;
|
return <RailwaySyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||||
|
@ -163,8 +163,12 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
primaryText = "Railway Project";
|
primaryText = destinationConfig.projectName;
|
||||||
secondaryText = destinationConfig.projectName;
|
secondaryText = "Railway Project";
|
||||||
|
break;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
primaryText = destinationConfig.accountName;
|
||||||
|
secondaryText = "Checkly Account";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||||
|
@ -6,16 +6,19 @@ import {
|
|||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
faCodeBranch,
|
faCodeBranch,
|
||||||
|
faCodeMerge,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faSearch
|
faSearch,
|
||||||
|
faXmark
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useSearch } from "@tanstack/react-router";
|
import { useSearch } from "@tanstack/react-router";
|
||||||
import { formatDistance } from "date-fns";
|
import { format, formatDistance } from "date-fns";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -25,7 +28,8 @@ import {
|
|||||||
EmptyState,
|
EmptyState,
|
||||||
Input,
|
Input,
|
||||||
Pagination,
|
Pagination,
|
||||||
Skeleton
|
Skeleton,
|
||||||
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
@ -308,7 +312,9 @@ export const SecretApprovalRequest = () => {
|
|||||||
createdAt,
|
createdAt,
|
||||||
reviewers,
|
reviewers,
|
||||||
status,
|
status,
|
||||||
committerUser
|
committerUser,
|
||||||
|
hasMerged,
|
||||||
|
updatedAt
|
||||||
} = secretApproval;
|
} = secretApproval;
|
||||||
const isReviewed = reviewers.some(
|
const isReviewed = reviewers.some(
|
||||||
({ status: reviewStatus, userId }) =>
|
({ status: reviewStatus, userId }) =>
|
||||||
@ -317,7 +323,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={reqId}
|
key={reqId}
|
||||||
className="flex flex-col border-b border-mineshaft-600 px-8 py-3 last:border-b-0 hover:bg-mineshaft-700"
|
className="flex border-b border-mineshaft-600 px-8 py-3 last:border-b-0 hover:bg-mineshaft-700"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => setSelectedApprovalId(secretApproval.id)}
|
onClick={() => setSelectedApprovalId(secretApproval.id)}
|
||||||
@ -325,29 +331,46 @@ export const SecretApprovalRequest = () => {
|
|||||||
if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id);
|
if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-1 text-sm">
|
<div className="flex flex-col">
|
||||||
<FontAwesomeIcon
|
<div className="mb-1 text-sm">
|
||||||
icon={faCodeBranch}
|
<FontAwesomeIcon
|
||||||
size="sm"
|
icon={faCodeBranch}
|
||||||
className="mr-1.5 text-mineshaft-300"
|
size="sm"
|
||||||
/>
|
className="mr-1.5 text-mineshaft-300"
|
||||||
{secretApproval.isReplicated
|
/>
|
||||||
? `${commits.length} secret pending import`
|
{secretApproval.isReplicated
|
||||||
: generateCommitText(commits)}
|
? `${commits.length} secret pending import`
|
||||||
<span className="text-xs text-bunker-300"> #{secretApproval.slug}</span>
|
: generateCommitText(commits)}
|
||||||
|
<span className="text-xs text-bunker-300"> #{secretApproval.slug}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs leading-3 text-gray-500">
|
||||||
|
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "}
|
||||||
|
{committerUser ? (
|
||||||
|
<>
|
||||||
|
{committerUser?.firstName || ""} {committerUser?.lastName || ""} (
|
||||||
|
{committerUser?.email})
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-600">Deleted User</span>
|
||||||
|
)}
|
||||||
|
{!isReviewed && status === "open" && " - Review required"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs leading-3 text-gray-500">
|
{status === "close" && (
|
||||||
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "}
|
<Tooltip
|
||||||
{committerUser ? (
|
content={updatedAt ? format(new Date(updatedAt), "M/dd/yyyy h:mm a") : ""}
|
||||||
<>
|
>
|
||||||
{committerUser?.firstName || ""} {committerUser?.lastName || ""} (
|
<div className="my-auto ml-auto">
|
||||||
{committerUser?.email})
|
<Badge
|
||||||
</>
|
variant={hasMerged ? "success" : "danger"}
|
||||||
) : (
|
className="flex h-min items-center gap-1"
|
||||||
<span className="text-gray-600">Deleted User</span>
|
>
|
||||||
)}
|
<FontAwesomeIcon icon={hasMerged ? faCodeMerge : faXmark} />
|
||||||
{!isReviewed && status === "open" && " - Review required"}
|
{hasMerged ? "Merged" : "Rejected"}
|
||||||
</span>
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TChecklySync } from "@app/hooks/api/secretSyncs/types/checkly-sync";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TChecklySync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChecklySyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
return <GenericFieldLabel label="Account">{destinationConfig.accountName}</GenericFieldLabel>;
|
||||||
|
};
|
@ -18,6 +18,7 @@ import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigura
|
|||||||
import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationSection";
|
import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationSection";
|
||||||
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
|
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
|
||||||
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
|
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
|
||||||
|
import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection";
|
||||||
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
||||||
import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection";
|
import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection";
|
||||||
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
|
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
|
||||||
@ -126,6 +127,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
DestinationComponents = <RailwaySyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <RailwaySyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Checkly:
|
||||||
|
DestinationComponents = <ChecklySyncDestinationSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
case SecretSync.CloudflareWorkers:
|
case SecretSync.CloudflareWorkers:
|
||||||
case SecretSync.Zabbix:
|
case SecretSync.Zabbix:
|
||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
|
case SecretSync.Checkly:
|
||||||
AdditionalSyncOptionsComponent = null;
|
AdditionalSyncOptionsComponent = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Spinner } from "@app/components/v2";
|
import { ContentLoader } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useListSecretScanningFindings } from "@app/hooks/api/secretScanningV2";
|
import { useListSecretScanningFindings } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
@ -17,12 +17,7 @@ export const SecretScanningFindingsSection = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isFindingsPending)
|
if (isFindingsPending) return <ContentLoader />;
|
||||||
return (
|
|
||||||
<div className="flex h-[60vh] flex-col items-center justify-center gap-2">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|