diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 0f84dbee2..5ee84a7c7 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -8,7 +8,7 @@ import path from "path"; import { seedData1 } from "@app/db/seed-data"; import { getDatabaseCredentials, getHsmConfig, initEnvConfig } from "@app/lib/config/env"; import { initLogger } from "@app/lib/logger"; -import { main } from "@app/server/app"; +import { main, markServerReady } from "@app/server/app"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { mockSmtpServer } from "./mocks/smtp"; @@ -83,7 +83,7 @@ export default { await queue.initialize(); - const server = await main({ + const { server, completeServerInitialization } = await main({ db, smtp, logger, @@ -96,6 +96,10 @@ export default { envConfig: envCfg }); + await completeServerInitialization(); + + markServerReady(); + await bootstrapCheck({ db }); // @ts-expect-error type diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts index 88f6dea69..28f66e20e 100644 --- a/backend/src/auto-start-migrations.ts +++ b/backend/src/auto-start-migrations.ts @@ -12,6 +12,7 @@ type TArgs = { auditLogDb?: Knex; applicationDb: Knex; logger: Logger; + onMigrationLockAcquired?: () => void; }; const isProduction = process.env.NODE_ENV === "production"; @@ -30,7 +31,7 @@ const migrationStatusCheckErrorHandler = (err: Error) => { throw err; }; -export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { +export const runMigrations = async ({ applicationDb, auditLogDb, logger, onMigrationLockAcquired }: TArgs) => { try { // akhilmhdh(Feb 10 2025): 2 years from now remove this if (isProduction) { @@ -85,6 +86,13 @@ export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs await applicationDb.transaction(async (tx) => { await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); + + // Signal that this container is running migrations so that it can be marked as healthy/alive + // This is to prevent the container from being killed by the orchestrator + if (onMigrationLockAcquired) { + onMigrationLockAcquired(); + } + logger.info("Running application migrations."); const didPreviousInstanceRunMigration = !(await applicationDb.migrate diff --git a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts index 26c27d0d3..62911d523 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-lease-router.ts @@ -2,7 +2,6 @@ import { z } from "zod"; import { DynamicSecretLeasesSchema } from "@app/db/schemas"; import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs"; -import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; @@ -32,8 +31,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path), environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug), @@ -127,8 +126,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.projectSlug), path: z diff --git a/backend/src/ee/routes/v1/dynamic-secret-lease-routers/kubernetes-lease-router.ts b/backend/src/ee/routes/v1/dynamic-secret-lease-routers/kubernetes-lease-router.ts index f2751c635..30de7f08a 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-lease-routers/kubernetes-lease-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-lease-routers/kubernetes-lease-router.ts @@ -2,7 +2,6 @@ import { z } from "zod"; import { DynamicSecretLeasesSchema } from "@app/db/schemas"; import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs"; -import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; import { writeLimit } from "@app/server/config/rateLimiter"; @@ -32,8 +31,8 @@ export const registerKubernetesDynamicSecretLeaseRouter = async (server: Fastify const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path), environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug), diff --git a/backend/src/ee/routes/v1/dynamic-secret-router.ts b/backend/src/ee/routes/v1/dynamic-secret-router.ts index b1b3cea8e..0e48206dc 100644 --- a/backend/src/ee/routes/v1/dynamic-secret-router.ts +++ b/backend/src/ee/routes/v1/dynamic-secret-router.ts @@ -3,7 +3,6 @@ import { z } from "zod"; import { DynamicSecretLeasesSchema } from "@app/db/schemas"; import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models"; import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs"; -import { daysToMillisecond } from "@app/lib/dates"; import { removeTrailingSlash } from "@app/lib/fn"; import { ms } from "@app/lib/ms"; import { isValidHandleBarTemplate } from "@app/lib/template/validate-handlebars"; @@ -60,8 +59,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -72,8 +71,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash), @@ -130,8 +129,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -142,8 +141,8 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > daysToMillisecond(1)) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(), diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index d2d683a84..137207659 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -112,7 +112,7 @@ export const dynamicSecretServiceFactory = ({ const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id }); if (existingDynamicSecret) - throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" }); + throw new BadRequestError({ message: "Provided dynamic secret already exists under the folder" }); const selectedProvider = dynamicSecretProviders[provider.type]; const inputs = await selectedProvider.validateProviderInputs(provider.inputs, { projectId }); @@ -265,7 +265,7 @@ export const dynamicSecretServiceFactory = ({ if (newName) { const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id }); if (existingDynamicSecret) - throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" }); + throw new BadRequestError({ message: "Provided dynamic secret already exists under the folder" }); } const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index 400fa31e9..d37766f71 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -84,7 +84,7 @@ export const isHsmActiveAndEnabled = async ({ rootKmsConfigEncryptionStrategy = (rootKmsConfig?.encryptionStrategy || null) as RootKeyEncryptionStrategy | null; if ( - rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.HSM && + (rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.HSM || isHsmConfigured) && licenseService && !licenseService.onPremFeatures.hsm ) { diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index affec39ad..a10a3f568 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -1517,7 +1517,7 @@ export const secretApprovalRequestServiceFactory = ({ })) ); if (secrets.length) - throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` }); + throw new BadRequestError({ message: `Secret already exists: ${secrets.map((el) => el.key).join(",")}` }); commits.push( ...createdSecrets.map((createdSecret) => ({ diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts index f653802b6..829c17e7b 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-queue.ts @@ -42,168 +42,172 @@ export const secretRotationV2QueueServiceFactory = async ({ smtpService, notificationService }: TSecretRotationV2QueueServiceFactoryDep) => { - const appCfg = getConfig(); + const init = async () => { + const appCfg = getConfig(); - if (appCfg.isRotationDevelopmentMode) { - logger.warn("Secret Rotation V2 is in development mode."); - } + if (appCfg.isRotationDevelopmentMode) { + logger.warn("Secret Rotation V2 is in development mode."); + } - await queueService.startPg( - QueueJobs.SecretRotationV2QueueRotations, - async () => { - try { - const rotateBy = getNextUtcRotationInterval(); + await queueService.startPg( + QueueJobs.SecretRotationV2QueueRotations, + async () => { + try { + const rotateBy = getNextUtcRotationInterval(); - const currentTime = new Date(); + const currentTime = new Date(); - const secretRotations = await secretRotationV2DAL.findSecretRotationsToQueue(rotateBy); + const secretRotations = await secretRotationV2DAL.findSecretRotationsToQueue(rotateBy); - logger.info( - `secretRotationV2Queue: Queue Rotations [currentTime=${currentTime.toISOString()}] [rotateBy=${rotateBy.toISOString()}] [count=${ - secretRotations.length - }]` - ); - - for await (const rotation of secretRotations) { logger.info( - `secretRotationV2Queue: Queue Rotation [rotationId=${rotation.id}] [lastRotatedAt=${new Date( - rotation.lastRotatedAt - ).toISOString()}] [rotateAt=${new Date(rotation.nextRotationAt!).toISOString()}]` + `secretRotationV2Queue: Queue Rotations [currentTime=${currentTime.toISOString()}] [rotateBy=${rotateBy.toISOString()}] [count=${ + secretRotations.length + }]` ); - const data = { - rotationId: rotation.id, - queuedAt: currentTime - } as TSecretRotationRotateSecretsJobPayload; - - if (appCfg.isTestMode) { - logger.warn("secretRotationV2Queue: Manually rotating secrets for test mode"); - await rotateSecretsFns({ - job: { - id: uuidv4(), - data, - retryCount: 0, - retryLimit: 0 - }, - secretRotationV2DAL, - secretRotationV2Service - }); - } else { - await queueService.queuePg( - QueueJobs.SecretRotationV2RotateSecrets, - { - rotationId: rotation.id, - queuedAt: currentTime - }, - getSecretRotationRotateSecretJobOptions(rotation) + for await (const rotation of secretRotations) { + logger.info( + `secretRotationV2Queue: Queue Rotation [rotationId=${rotation.id}] [lastRotatedAt=${new Date( + rotation.lastRotatedAt + ).toISOString()}] [rotateAt=${new Date(rotation.nextRotationAt!).toISOString()}]` ); + + const data = { + rotationId: rotation.id, + queuedAt: currentTime + } as TSecretRotationRotateSecretsJobPayload; + + if (appCfg.isTestMode) { + logger.warn("secretRotationV2Queue: Manually rotating secrets for test mode"); + await rotateSecretsFns({ + job: { + id: uuidv4(), + data, + retryCount: 0, + retryLimit: 0 + }, + secretRotationV2DAL, + secretRotationV2Service + }); + } else { + await queueService.queuePg( + QueueJobs.SecretRotationV2RotateSecrets, + { + rotationId: rotation.id, + queuedAt: currentTime + }, + getSecretRotationRotateSecretJobOptions(rotation) + ); + } } + } catch (error) { + logger.error(error, "secretRotationV2Queue: Queue Rotations Error:"); + throw error; } - } catch (error) { - logger.error(error, "secretRotationV2Queue: Queue Rotations Error:"); - throw error; + }, + { + batchSize: 1, + workerCount: 1, + pollingIntervalSeconds: appCfg.isRotationDevelopmentMode ? 0.5 : 30 } - }, - { - batchSize: 1, - workerCount: 1, - pollingIntervalSeconds: appCfg.isRotationDevelopmentMode ? 0.5 : 30 - } - ); + ); - await queueService.startPg( - QueueJobs.SecretRotationV2RotateSecrets, - async ([job]) => { - await rotateSecretsFns({ - job: { - ...job, - data: job.data as TSecretRotationRotateSecretsJobPayload - }, - secretRotationV2DAL, - secretRotationV2Service - }); - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 0.5 - } - ); - - await queueService.startPg( - QueueJobs.SecretRotationV2SendNotification, - async ([job]) => { - const { secretRotation } = job.data as TSecretRotationSendNotificationJobPayload; - try { - const { - name: rotationName, - type, - projectId, - lastRotationAttemptedAt, - folder, - environment, - id: rotationId - } = secretRotation; - - logger.info(`secretRotationV2Queue: Sending Status Notification [rotationId=${rotationId}]`); - - const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); - const project = await projectDAL.findById(projectId); - - const projectAdmins = projectMembers.filter((member) => - member.roles.some((role) => role.role === ProjectMembershipRole.Admin) - ); - - const rotationType = SECRET_ROTATION_NAME_MAP[type as SecretRotation]; - - const rotationPath = `/projects/secret-management/${projectId}/secrets/${environment.slug}`; - - await notificationService.createUserNotifications( - projectAdmins.map((admin) => ({ - userId: admin.userId, - orgId: project.orgId, - type: NotificationType.SECRET_ROTATION_FAILED, - title: "Secret Rotation Failed", - body: `Your **${rotationType}** rotation **${rotationName}** failed to rotate.`, - link: rotationPath - })) - ); - - await smtpService.sendMail({ - recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), - template: SmtpTemplates.SecretRotationFailed, - subjectLine: `Secret Rotation Failed`, - substitutions: { - rotationName, - rotationType, - content: `Your ${rotationType} Rotation failed to rotate during it's scheduled rotation. The last rotation attempt occurred at ${new Date( - lastRotationAttemptedAt - ).toISOString()}. Please check the rotation status in Infisical for more details.`, - secretPath: folder.path, - environment: environment.name, - projectName: project.name, - rotationUrl: encodeURI(`${appCfg.SITE_URL}${rotationPath}`) - } + await queueService.startPg( + QueueJobs.SecretRotationV2RotateSecrets, + async ([job]) => { + await rotateSecretsFns({ + job: { + ...job, + data: job.data as TSecretRotationRotateSecretsJobPayload + }, + secretRotationV2DAL, + secretRotationV2Service }); - } catch (error) { - logger.error( - error, - `secretRotationV2Queue: Failed to Send Status Notification [rotationId=${secretRotation.id}]` - ); - throw error; + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 0.5 } - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 1 - } - ); + ); - await queueService.schedulePg( - QueueJobs.SecretRotationV2QueueRotations, - appCfg.isRotationDevelopmentMode ? "* * * * *" : "0 0 * * *", - undefined, - { tz: "UTC" } - ); + await queueService.startPg( + QueueJobs.SecretRotationV2SendNotification, + async ([job]) => { + const { secretRotation } = job.data as TSecretRotationSendNotificationJobPayload; + try { + const { + name: rotationName, + type, + projectId, + lastRotationAttemptedAt, + folder, + environment, + id: rotationId + } = secretRotation; + + logger.info(`secretRotationV2Queue: Sending Status Notification [rotationId=${rotationId}]`); + + const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); + const project = await projectDAL.findById(projectId); + + const projectAdmins = projectMembers.filter((member) => + member.roles.some((role) => role.role === ProjectMembershipRole.Admin) + ); + + const rotationType = SECRET_ROTATION_NAME_MAP[type as SecretRotation]; + + const rotationPath = `/projects/secret-management/${projectId}/secrets/${environment.slug}`; + + await notificationService.createUserNotifications( + projectAdmins.map((admin) => ({ + userId: admin.userId, + orgId: project.orgId, + type: NotificationType.SECRET_ROTATION_FAILED, + title: "Secret Rotation Failed", + body: `Your **${rotationType}** rotation **${rotationName}** failed to rotate.`, + link: rotationPath + })) + ); + + await smtpService.sendMail({ + recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), + template: SmtpTemplates.SecretRotationFailed, + subjectLine: `Secret Rotation Failed`, + substitutions: { + rotationName, + rotationType, + content: `Your ${rotationType} Rotation failed to rotate during it's scheduled rotation. The last rotation attempt occurred at ${new Date( + lastRotationAttemptedAt + ).toISOString()}. Please check the rotation status in Infisical for more details.`, + secretPath: folder.path, + environment: environment.name, + projectName: project.name, + rotationUrl: encodeURI(`${appCfg.SITE_URL}${rotationPath}`) + } + }); + } catch (error) { + logger.error( + error, + `secretRotationV2Queue: Failed to Send Status Notification [rotationId=${secretRotation.id}]` + ); + throw error; + } + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 1 + } + ); + + await queueService.schedulePg( + QueueJobs.SecretRotationV2QueueRotations, + appCfg.isRotationDevelopmentMode ? "* * * * *" : "0 0 * * *", + undefined, + { tz: "UTC" } + ); + }; + + return { init }; }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts index 406c25e03..4830196f6 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts @@ -141,202 +141,6 @@ export const secretScanningV2QueueServiceFactory = async ({ } }; - await queueService.startPg( - QueueJobs.SecretScanningV2FullScan, - async ([job]) => { - const { scanId, resourceId, dataSourceId } = job.data as TQueueSecretScanningDataSourceFullScan; - const { retryCount, retryLimit } = job; - - const logDetails = `[scanId=${scanId}] [resourceId=${resourceId}] [dataSourceId=${dataSourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; - - const tempFolder = await createTempFolder(); - - const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); - - if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); - - const resource = await secretScanningV2DAL.resources.findById(resourceId); - - if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); - - let lock: Awaited> | undefined; - - try { - try { - lock = await keyStore.acquireLock( - [KeyStorePrefixes.SecretScanningLock(dataSource.id, resource.externalId)], - 60 * 1000 * 5 - ); - } catch (e) { - throw new Error("Failed to acquire scanning lock."); - } - - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Scanning - } - ); - - let connection: TAppConnection | null = null; - if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); - - const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource]({ - kmsService, - appConnectionDAL - }); - - const findingsPath = join(tempFolder, "findings.json"); - - const scanPath = await factory.getFullScanPath({ - dataSource: { - ...dataSource, - connection - } as TSecretScanningDataSourceWithConnection, - resourceName: resource.name, - tempFolder - }); - - const config = await secretScanningV2DAL.configs.findOne({ - projectId: dataSource.projectId - }); - - let configPath: string | undefined; - - if (config && config.content) { - configPath = join(tempFolder, "infisical-scan.toml"); - await writeTextToFile(configPath, config.content); - } - - let findingsPayload: TFindingsPayload; - switch (resource.type) { - case SecretScanningResource.Repository: - case SecretScanningResource.Project: - findingsPayload = await scanGitRepositoryAndGetFindings(scanPath, findingsPath, configPath); - break; - default: - throw new Error("Unhandled resource type"); - } - - const allFindings = await secretScanningV2DAL.findings.transaction(async (tx) => { - let findings: TSecretScanningFindings[] = []; - if (findingsPayload.length) { - findings = await secretScanningV2DAL.findings.upsert( - findingsPayload.map((finding) => ({ - ...finding, - projectId: dataSource.projectId, - dataSourceName: dataSource.name, - dataSourceType: dataSource.type, - resourceName: resource.name, - resourceType: resource.type, - scanId - })), - ["projectId", "fingerprint"], - tx, - ["resourceName", "dataSourceName"] - ); - } - - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Completed, - statusMessage: null - } - ); - - return findings; - }); - - const newFindings = allFindings.filter((finding) => finding.scanId === scanId); - - if (newFindings.length) { - await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { - status: SecretScanningScanStatus.Completed, - resourceName: resource.name, - isDiffScan: false, - dataSource, - numberOfSecrets: newFindings.length, - scanId - }); - } - - await auditLogService.createAuditLog({ - projectId: dataSource.projectId, - actor: { - type: ActorType.PLATFORM, - metadata: {} - }, - event: { - type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, - metadata: { - dataSourceId: dataSource.id, - dataSourceType: dataSource.type, - resourceId: resource.id, - resourceType: resource.type, - scanId, - scanStatus: SecretScanningScanStatus.Completed, - scanType: SecretScanningScanType.FullScan, - numberOfSecretsDetected: findingsPayload.length - } - } - }); - - logger.info(`secretScanningV2Queue: Full Scan Complete ${logDetails} findings=[${findingsPayload.length}]`); - } catch (error) { - if (retryCount === retryLimit) { - const errorMessage = parseScanErrorMessage(error); - - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Failed, - statusMessage: errorMessage - } - ); - - await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { - status: SecretScanningScanStatus.Failed, - resourceName: resource.name, - dataSource, - errorMessage - }); - - await auditLogService.createAuditLog({ - projectId: dataSource.projectId, - actor: { - type: ActorType.PLATFORM, - metadata: {} - }, - event: { - type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, - metadata: { - dataSourceId: dataSource.id, - dataSourceType: dataSource.type, - resourceId: resource.id, - resourceType: resource.type, - scanId, - scanStatus: SecretScanningScanStatus.Failed, - scanType: SecretScanningScanType.FullScan - } - } - }); - } - - logger.error(error, `secretScanningV2Queue: Full Scan Failed ${logDetails}`); - throw error; - } finally { - await deleteTempFolder(tempFolder); - await lock?.release(); - } - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 1 - } - ); - const queueResourceDiffScan = async ({ payload, dataSourceId, @@ -391,148 +195,127 @@ export const secretScanningV2QueueServiceFactory = async ({ } }; - await queueService.startPg( - QueueJobs.SecretScanningV2DiffScan, - async ([job]) => { - const { payload, dataSourceId, resourceId, scanId } = job.data as TQueueSecretScanningResourceDiffScan; - const { retryCount, retryLimit } = job; + const init = async () => { + await queueService.startPg( + QueueJobs.SecretScanningV2FullScan, + async ([job]) => { + const { scanId, resourceId, dataSourceId } = job.data as TQueueSecretScanningDataSourceFullScan; + const { retryCount, retryLimit } = job; - const logDetails = `[dataSourceId=${dataSourceId}] [scanId=${scanId}] [resourceId=${resourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; + const logDetails = `[scanId=${scanId}] [resourceId=${resourceId}] [dataSourceId=${dataSourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; - const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); + const tempFolder = await createTempFolder(); - if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); + const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); - const resource = await secretScanningV2DAL.resources.findById(resourceId); + if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); - if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); + const resource = await secretScanningV2DAL.resources.findById(resourceId); - const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource]({ - kmsService, - appConnectionDAL - }); + if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); - const tempFolder = await createTempFolder(); + let lock: Awaited> | undefined; - try { - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Scanning - } - ); - - let connection: TAppConnection | null = null; - if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); - - const config = await secretScanningV2DAL.configs.findOne({ - projectId: dataSource.projectId - }); - - let configPath: string | undefined; - - if (config && config.content) { - configPath = join(tempFolder, "infisical-scan.toml"); - await writeTextToFile(configPath, config.content); - } - - const findingsPayload = await factory.getDiffScanFindingsPayload({ - dataSource: { - ...dataSource, - connection - } as TSecretScanningDataSourceWithConnection, - resourceName: resource.name, - payload, - configPath - }); - - const allFindings = await secretScanningV2DAL.findings.transaction(async (tx) => { - let findings: TSecretScanningFindings[] = []; - - if (findingsPayload.length) { - findings = await secretScanningV2DAL.findings.upsert( - findingsPayload.map((finding) => ({ - ...finding, - projectId: dataSource.projectId, - dataSourceName: dataSource.name, - dataSourceType: dataSource.type, - resourceName: resource.name, - resourceType: resource.type, - scanId - })), - ["projectId", "fingerprint"], - tx, - ["resourceName", "dataSourceName"] + try { + try { + lock = await keyStore.acquireLock( + [KeyStorePrefixes.SecretScanningLock(dataSource.id, resource.externalId)], + 60 * 1000 * 5 ); + } catch (e) { + throw new Error("Failed to acquire scanning lock."); } await secretScanningV2DAL.scans.update( { id: scanId }, { - status: SecretScanningScanStatus.Completed + status: SecretScanningScanStatus.Scanning } ); - return findings; - }); + let connection: TAppConnection | null = null; + if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); - const newFindings = allFindings.filter((finding) => finding.scanId === scanId); - - if (newFindings.length) { - const finding = newFindings[0] as TSecretScanningFinding; - await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { - status: SecretScanningScanStatus.Completed, - resourceName: resource.name, - isDiffScan: true, - dataSource, - numberOfSecrets: newFindings.length, - scanId, - authorName: finding?.details?.author, - authorEmail: finding?.details?.email + const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource]({ + kmsService, + appConnectionDAL }); - } - await auditLogService.createAuditLog({ - projectId: dataSource.projectId, - actor: { - type: ActorType.PLATFORM, - metadata: {} - }, - event: { - type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, - metadata: { - dataSourceId: dataSource.id, - dataSourceType: dataSource.type, - resourceId, - resourceType: resource.type, - scanId, - scanStatus: SecretScanningScanStatus.Completed, - scanType: SecretScanningScanType.DiffScan, - numberOfSecretsDetected: findingsPayload.length - } + const findingsPath = join(tempFolder, "findings.json"); + + const scanPath = await factory.getFullScanPath({ + dataSource: { + ...dataSource, + connection + } as TSecretScanningDataSourceWithConnection, + resourceName: resource.name, + tempFolder + }); + + const config = await secretScanningV2DAL.configs.findOne({ + projectId: dataSource.projectId + }); + + let configPath: string | undefined; + + if (config && config.content) { + configPath = join(tempFolder, "infisical-scan.toml"); + await writeTextToFile(configPath, config.content); } - }); - logger.info(`secretScanningV2Queue: Diff Scan Complete ${logDetails}`); - } catch (error) { - if (retryCount === retryLimit) { - const errorMessage = parseScanErrorMessage(error); + let findingsPayload: TFindingsPayload; + switch (resource.type) { + case SecretScanningResource.Repository: + case SecretScanningResource.Project: + findingsPayload = await scanGitRepositoryAndGetFindings(scanPath, findingsPath, configPath); + break; + default: + throw new Error("Unhandled resource type"); + } - await secretScanningV2DAL.scans.update( - { id: scanId }, - { - status: SecretScanningScanStatus.Failed, - statusMessage: errorMessage + const allFindings = await secretScanningV2DAL.findings.transaction(async (tx) => { + let findings: TSecretScanningFindings[] = []; + if (findingsPayload.length) { + findings = await secretScanningV2DAL.findings.upsert( + findingsPayload.map((finding) => ({ + ...finding, + projectId: dataSource.projectId, + dataSourceName: dataSource.name, + dataSourceType: dataSource.type, + resourceName: resource.name, + resourceType: resource.type, + scanId + })), + ["projectId", "fingerprint"], + tx, + ["resourceName", "dataSourceName"] + ); } - ); - await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { - status: SecretScanningScanStatus.Failed, - resourceName: resource.name, - dataSource, - errorMessage + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Completed, + statusMessage: null + } + ); + + return findings; }); + const newFindings = allFindings.filter((finding) => finding.scanId === scanId); + + if (newFindings.length) { + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Completed, + resourceName: resource.name, + isDiffScan: false, + dataSource, + numberOfSecrets: newFindings.length, + scanId + }); + } + await auditLogService.createAuditLog({ projectId: dataSource.projectId, actor: { @@ -547,128 +330,348 @@ export const secretScanningV2QueueServiceFactory = async ({ resourceId: resource.id, resourceType: resource.type, scanId, - scanStatus: SecretScanningScanStatus.Failed, - scanType: SecretScanningScanType.DiffScan + scanStatus: SecretScanningScanStatus.Completed, + scanType: SecretScanningScanType.FullScan, + numberOfSecretsDetected: findingsPayload.length } } }); + + logger.info(`secretScanningV2Queue: Full Scan Complete ${logDetails} findings=[${findingsPayload.length}]`); + } catch (error) { + if (retryCount === retryLimit) { + const errorMessage = parseScanErrorMessage(error); + + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Failed, + statusMessage: errorMessage + } + ); + + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Failed, + resourceName: resource.name, + dataSource, + errorMessage + }); + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, + dataSourceType: dataSource.type, + resourceId: resource.id, + resourceType: resource.type, + scanId, + scanStatus: SecretScanningScanStatus.Failed, + scanType: SecretScanningScanType.FullScan + } + } + }); + } + + logger.error(error, `secretScanningV2Queue: Full Scan Failed ${logDetails}`); + throw error; + } finally { + await deleteTempFolder(tempFolder); + await lock?.release(); } - - logger.error(error, `secretScanningV2Queue: Diff Scan Failed ${logDetails}`); - throw error; - } finally { - await deleteTempFolder(tempFolder); + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 1 } - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 1 - } - ); + ); - await queueService.startPg( - QueueJobs.SecretScanningV2SendNotification, - async ([job]) => { - const { dataSource, resourceName, ...payload } = job.data as TQueueSecretScanningSendNotification; + await queueService.startPg( + QueueJobs.SecretScanningV2DiffScan, + async ([job]) => { + const { payload, dataSourceId, resourceId, scanId } = job.data as TQueueSecretScanningResourceDiffScan; + const { retryCount, retryLimit } = job; - const appCfg = getConfig(); + const logDetails = `[dataSourceId=${dataSourceId}] [scanId=${scanId}] [resourceId=${resourceId}] [jobId=${job.id}] retryCount=[${retryCount}/${retryLimit}]`; - if (!appCfg.isSmtpConfigured) return; + const dataSource = await secretScanningV2DAL.dataSources.findById(dataSourceId); - try { - const { projectId } = dataSource; + if (!dataSource) throw new Error(`Data source with ID "${dataSourceId}" not found`); - logger.info( - `secretScanningV2Queue: Sending Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` - ); + const resource = await secretScanningV2DAL.resources.findById(resourceId); - const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); - const project = await projectDAL.findById(projectId); + if (!resource) throw new Error(`Resource with ID "${resourceId}" not found`); - const recipients = projectMembers.filter((member) => { - const isAdmin = member.roles.some((role) => role.role === ProjectMembershipRole.Admin); - const isCompleted = payload.status === SecretScanningScanStatus.Completed; - // We assume that the committer is one of the project members - const isCommitter = isCompleted && payload.authorEmail === member.user.email; - return isAdmin || isCommitter; + const factory = SECRET_SCANNING_FACTORY_MAP[dataSource.type as SecretScanningDataSource]({ + kmsService, + appConnectionDAL }); - const timestamp = new Date().toISOString(); + const tempFolder = await createTempFolder(); - const subjectLine = - payload.status === SecretScanningScanStatus.Completed - ? "Incident Alert: Secret(s) Leaked" - : `Secret Scanning Failed`; + try { + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Scanning + } + ); - await notificationService.createUserNotifications( - recipients.map((member) => ({ - userId: member.userId, - orgId: project.orgId, - type: - payload.status === SecretScanningScanStatus.Completed - ? NotificationType.SECRET_SCANNING_SECRETS_DETECTED - : NotificationType.SECRET_SCANNING_SCAN_FAILED, - title: subjectLine, - body: - payload.status === SecretScanningScanStatus.Completed - ? `Uncovered **${payload.numberOfSecrets}** secret(s) ${payload.isDiffScan ? " from a recent commit to" : " in"} **${resourceName}**.` - : `Encountered an error while attempting to scan the resource **${resourceName}**: ${payload.errorMessage}`, - link: - payload.status === SecretScanningScanStatus.Completed - ? `/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` - : `/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` - })) - ); + let connection: TAppConnection | null = null; + if (dataSource.connection) connection = await decryptAppConnection(dataSource.connection, kmsService); - await smtpService.sendMail({ - recipients: recipients.map((member) => member.user.email!).filter(Boolean), - template: - payload.status === SecretScanningScanStatus.Completed - ? SmtpTemplates.SecretScanningV2SecretsDetected - : SmtpTemplates.SecretScanningV2ScanFailed, - subjectLine, - substitutions: - payload.status === SecretScanningScanStatus.Completed - ? { - authorName: payload.authorName, - authorEmail: payload.authorEmail, - resourceName, - numberOfSecrets: payload.numberOfSecrets, - isDiffScan: payload.isDiffScan, - url: encodeURI( - `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` - ), - timestamp - } - : { + const config = await secretScanningV2DAL.configs.findOne({ + projectId: dataSource.projectId + }); + + let configPath: string | undefined; + + if (config && config.content) { + configPath = join(tempFolder, "infisical-scan.toml"); + await writeTextToFile(configPath, config.content); + } + + const findingsPayload = await factory.getDiffScanFindingsPayload({ + dataSource: { + ...dataSource, + connection + } as TSecretScanningDataSourceWithConnection, + resourceName: resource.name, + payload, + configPath + }); + + const allFindings = await secretScanningV2DAL.findings.transaction(async (tx) => { + let findings: TSecretScanningFindings[] = []; + + if (findingsPayload.length) { + findings = await secretScanningV2DAL.findings.upsert( + findingsPayload.map((finding) => ({ + ...finding, + projectId: dataSource.projectId, dataSourceName: dataSource.name, - resourceName, - projectName: project.name, - timestamp, - errorMessage: payload.errorMessage, - url: encodeURI( - `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` - ) + dataSourceType: dataSource.type, + resourceName: resource.name, + resourceType: resource.type, + scanId + })), + ["projectId", "fingerprint"], + tx, + ["resourceName", "dataSourceName"] + ); + } + + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Completed + } + ); + + return findings; + }); + + const newFindings = allFindings.filter((finding) => finding.scanId === scanId); + + if (newFindings.length) { + const finding = newFindings[0] as TSecretScanningFinding; + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Completed, + resourceName: resource.name, + isDiffScan: true, + dataSource, + numberOfSecrets: newFindings.length, + scanId, + authorName: finding?.details?.author, + authorEmail: finding?.details?.email + }); + } + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, + dataSourceType: dataSource.type, + resourceId, + resourceType: resource.type, + scanId, + scanStatus: SecretScanningScanStatus.Completed, + scanType: SecretScanningScanType.DiffScan, + numberOfSecretsDetected: findingsPayload.length + } + } + }); + + logger.info(`secretScanningV2Queue: Diff Scan Complete ${logDetails}`); + } catch (error) { + if (retryCount === retryLimit) { + const errorMessage = parseScanErrorMessage(error); + + await secretScanningV2DAL.scans.update( + { id: scanId }, + { + status: SecretScanningScanStatus.Failed, + statusMessage: errorMessage + } + ); + + await queueService.queuePg(QueueJobs.SecretScanningV2SendNotification, { + status: SecretScanningScanStatus.Failed, + resourceName: resource.name, + dataSource, + errorMessage + }); + + await auditLogService.createAuditLog({ + projectId: dataSource.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.SECRET_SCANNING_DATA_SOURCE_SCAN, + metadata: { + dataSourceId: dataSource.id, + dataSourceType: dataSource.type, + resourceId: resource.id, + resourceType: resource.type, + scanId, + scanStatus: SecretScanningScanStatus.Failed, + scanType: SecretScanningScanType.DiffScan } - }); - } catch (error) { - logger.error( - error, - `secretScanningV2Queue: Failed to Send Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` - ); - throw error; + } + }); + } + + logger.error(error, `secretScanningV2Queue: Diff Scan Failed ${logDetails}`); + throw error; + } finally { + await deleteTempFolder(tempFolder); + } + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 1 } - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 1 - } - ); + ); + + await queueService.startPg( + QueueJobs.SecretScanningV2SendNotification, + async ([job]) => { + const { dataSource, resourceName, ...payload } = job.data as TQueueSecretScanningSendNotification; + + const appCfg = getConfig(); + + if (!appCfg.isSmtpConfigured) return; + + try { + const { projectId } = dataSource; + + logger.info( + `secretScanningV2Queue: Sending Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` + ); + + const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); + const project = await projectDAL.findById(projectId); + + const recipients = projectMembers.filter((member) => { + const isAdmin = member.roles.some((role) => role.role === ProjectMembershipRole.Admin); + const isCompleted = payload.status === SecretScanningScanStatus.Completed; + // We assume that the committer is one of the project members + const isCommitter = isCompleted && payload.authorEmail === member.user.email; + return isAdmin || isCommitter; + }); + + const timestamp = new Date().toISOString(); + + const subjectLine = + payload.status === SecretScanningScanStatus.Completed + ? "Incident Alert: Secret(s) Leaked" + : `Secret Scanning Failed`; + + await notificationService.createUserNotifications( + recipients.map((member) => ({ + userId: member.userId, + orgId: project.orgId, + type: + payload.status === SecretScanningScanStatus.Completed + ? NotificationType.SECRET_SCANNING_SECRETS_DETECTED + : NotificationType.SECRET_SCANNING_SCAN_FAILED, + title: subjectLine, + body: + payload.status === SecretScanningScanStatus.Completed + ? `Uncovered **${payload.numberOfSecrets}** secret(s) ${payload.isDiffScan ? " from a recent commit to" : " in"} **${resourceName}**.` + : `Encountered an error while attempting to scan the resource **${resourceName}**: ${payload.errorMessage}`, + link: + payload.status === SecretScanningScanStatus.Completed + ? `/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` + : `/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` + })) + ); + + await smtpService.sendMail({ + recipients: recipients.map((member) => member.user.email!).filter(Boolean), + template: + payload.status === SecretScanningScanStatus.Completed + ? SmtpTemplates.SecretScanningV2SecretsDetected + : SmtpTemplates.SecretScanningV2ScanFailed, + subjectLine, + substitutions: + payload.status === SecretScanningScanStatus.Completed + ? { + authorName: payload.authorName, + authorEmail: payload.authorEmail, + resourceName, + numberOfSecrets: payload.numberOfSecrets, + isDiffScan: payload.isDiffScan, + url: encodeURI( + `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` + ), + timestamp + } + : { + dataSourceName: dataSource.name, + resourceName, + projectName: project.name, + timestamp, + errorMessage: payload.errorMessage, + url: encodeURI( + `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` + ) + } + }); + } catch (error) { + logger.error( + error, + `secretScanningV2Queue: Failed to Send Status Notification [dataSourceId=${dataSource.id}] [resourceName=${resourceName}] [status=${payload.status}]` + ); + throw error; + } + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 1 + } + ); + }; return { queueDataSourceFullScan, - queueResourceDiffScan + queueResourceDiffScan, + init }; }; diff --git a/backend/src/main.ts b/backend/src/main.ts index 400804804..d36d706ba 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -16,7 +16,7 @@ import { buildRedisFromConfig } from "./lib/config/redis"; import { removeTemporaryBaseDirectory } from "./lib/files"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; -import { main } from "./server/app"; +import { main, markRunningMigrations, markServerReady } from "./server/app"; import { bootstrapCheck } from "./server/boot-strap-check"; import { kmsRootConfigDALFactory } from "./services/kms/kms-root-config-dal"; import { smtpServiceFactory } from "./services/smtp/smtp-service"; @@ -59,8 +59,6 @@ const run = async () => { }) : undefined; - await runMigrations({ applicationDb: db, auditLogDb, logger }); - const smtp = smtpServiceFactory(formatSmtpConfig()); const queue = queueServiceFactory(envConfig, { @@ -74,7 +72,7 @@ const run = async () => { const keyStore = keyStoreFactory(envConfig, keyValueStoreDAL); const redis = buildRedisFromConfig(envConfig); - const server = await main({ + const { server, completeServerInitialization } = await main({ db, auditLogDb, superAdminDAL, @@ -87,7 +85,6 @@ const run = async () => { redis, envConfig }); - const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line process.on("SIGINT", async () => { @@ -121,12 +118,46 @@ const run = async () => { await server.listen({ port: envConfig.PORT, - host: envConfig.HOST, - listenTextResolver: (address) => { - void bootstrap(); - return address; + host: envConfig.HOST + }); + + logger.info(`Server listening on ${envConfig.HOST}:${envConfig.PORT}`); + logger.info("Running migrations..."); + + // Run migrations while server is up + // All containers start as NOT HEALTHY (waiting for migrations) + // Container that acquires lock: becomes HEALTHY (running migrations) + NOT READY (no traffic) + // Other containers waiting: stay NOT HEALTHY (waiting) + NOT READY (no traffic) + await runMigrations({ + applicationDb: db, + auditLogDb, + logger, + onMigrationLockAcquired: () => { + // Called after successfully acquiring the lock + // This container is now the migration runner + markRunningMigrations(); + logger.info("Migration lock acquired! This container is running migrations."); } }); + + logger.info("Migrations complete. Completing server initialization..."); + + try { + await completeServerInitialization(); + } catch (error) { + logger.error(error, "Failed to complete server initialization"); + await server.close(); + await queue.shutdown(); + process.exit(1); + } + + logger.info("Server initialization complete. Marking server as READY..."); + + markServerReady(); + + logger.info("Server is ready to accept traffic"); + const bootstrap = await bootstrapCheck({ db }); + void bootstrap(); }; void run(); diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index f1176b932..17a9feb2a 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -1,5 +1,6 @@ /* eslint-disable import/extensions */ import path from "node:path"; +import { monitorEventLoopDelay } from "perf_hooks"; import type { FastifyCookieOptions } from "@fastify/cookie"; import cookie from "@fastify/cookie"; @@ -24,6 +25,7 @@ import { TQueueServiceFactory } from "@app/queue"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { TSmtpService } from "@app/services/smtp/smtp-service"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; +import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { globalRateLimiterCfg } from "./config/rateLimiter"; import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas"; @@ -36,6 +38,15 @@ import { registerServeUI } from "./plugins/serve-ui"; import { fastifySwagger } from "./plugins/swagger"; import { registerRoutes } from "./routes"; +const histogram = monitorEventLoopDelay({ resolution: 20 }); +histogram.enable(); + +const serverState = { + isReady: false, + isRunningMigrations: false, + isWaitingForMigrations: true // Start as true - containers are unhealthy until they acquire migration lock or complete +}; + type TMain = { auditLogDb?: Knex; db: Knex; @@ -145,7 +156,72 @@ export const main = async ({ }) }); - await server.register(registerRoutes, { + // Health check - returns 200 only if doing useful work (running migrations or ready) + // Returns 503 if waiting for another container to finish migrations + server.get("/api/health", async (_, reply) => { + if (serverState.isWaitingForMigrations) { + return reply.code(503).send({ + status: "waiting", + message: "Waiting for migrations to complete in another container" + }); + } + return { status: "ok", message: "Server is alive" }; + }); + + // Global preHandler to block requests during migrations + server.addHook("preHandler", async (request, reply) => { + if (request.url === "/api/health" || request.url === "/api/ready") { + return; + } + if (!serverState.isReady) { + return reply.code(503).send({ + status: "unavailable", + message: "Server is starting up, migrations in progress. Please try again in a moment." + }); + } + }); + + // Readiness check - returns 503 until migrations are complete + server.get("/api/ready", async (request, reply) => { + const cfg = getConfig(); + + const meanLagMs = histogram.mean / 1e6; + const maxLagMs = histogram.max / 1e6; + const p99LagMs = histogram.percentile(99) / 1e6; + + request.log.info( + `Event loop stats - Mean: ${meanLagMs.toFixed(2)}ms, Max: ${maxLagMs.toFixed(2)}ms, p99: ${p99LagMs.toFixed(2)}ms` + ); + + request.log.info(`Raw event loop stats: ${JSON.stringify(histogram, null, 2)}`); + + if (!serverState.isReady) { + return reply.code(503).send({ + date: new Date(), + message: "Server is starting up, migrations in progress", + emailConfigured: cfg.isSmtpConfigured, + redisConfigured: cfg.isRedisConfigured, + secretScanningConfigured: cfg.isSecretScanningConfigured, + samlDefaultOrgSlug: cfg.samlDefaultOrgSlug, + auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE) + }); + } + + const serverCfg = await getServerCfg(); + + return { + date: new Date(), + message: "Ok", + emailConfigured: cfg.isSmtpConfigured, + inviteOnlySignup: Boolean(serverCfg.allowSignUp), + redisConfigured: cfg.isRedisConfigured, + secretScanningConfigured: cfg.isSecretScanningConfigured, + samlDefaultOrgSlug: cfg.samlDefaultOrgSlug, + auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE) + }; + }); + + const completeServerInitialization = await registerRoutes(server, { smtp, queue, db, @@ -164,10 +240,21 @@ export const main = async ({ await server.ready(); server.swagger(); - return server; + return { server, completeServerInitialization }; } catch (err) { server.log.error(err); await queue.shutdown(); process.exit(1); } }; + +export const markServerReady = () => { + serverState.isReady = true; + serverState.isRunningMigrations = false; + serverState.isWaitingForMigrations = false; +}; + +export const markRunningMigrations = () => { + serverState.isRunningMigrations = true; + serverState.isWaitingForMigrations = false; +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 31d860201..5bdb8fafc 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2208,7 +2208,7 @@ export const registerRoutes = async ( internalCaFns }); - await secretRotationV2QueueServiceFactory({ + const secretRotationV2Queue = await secretRotationV2QueueServiceFactory({ secretRotationV2Service, secretRotationV2DAL, queueService, @@ -2305,8 +2305,6 @@ export const registerRoutes = async ( // If FIPS is enabled, we check to ensure that the users license includes FIPS mode. crypto.verifyFipsLicense(licenseService); - await superAdminService.initServerCfg(); - // Start HSM service if it's configured/enabled. await hsmService.startService(); @@ -2331,21 +2329,60 @@ export const registerRoutes = async ( } } - await telemetryQueue.startTelemetryCheck(); - await telemetryQueue.startAggregatedEventsJob(); - await dailyResourceCleanUp.init(); - await healthAlert.init(); - await pkiSyncCleanup.init(); - await pamAccountRotation.init(); - await dailyReminderQueueService.startDailyRemindersJob(); - await dailyReminderQueueService.startSecretReminderMigrationJob(); - await dailyExpiringPkiItemAlert.startSendingAlerts(); - await pkiSubscriberQueue.startDailyAutoRenewalJob(); - await certificateV3Queue.init(); - await kmsService.startService(hsmStatus); - await microsoftTeamsService.start(); - await dynamicSecretQueueService.init(); - await eventBusService.init(); + const completeServerInitialization = async () => { + await superAdminService.initServerCfg(); + + await telemetryQueue.startTelemetryCheck(); + await telemetryQueue.startAggregatedEventsJob(); + await dailyResourceCleanUp.init(); + await healthAlert.init(); + await pkiSyncCleanup.init(); + await pamAccountRotation.init(); + await dailyReminderQueueService.startDailyRemindersJob(); + await dailyReminderQueueService.startSecretReminderMigrationJob(); + await dailyExpiringPkiItemAlert.startSendingAlerts(); + await pkiSubscriberQueue.startDailyAutoRenewalJob(); + await certificateV3Queue.init(); + await kmsService.startService(hsmStatus); + await microsoftTeamsService.start(); + await dynamicSecretQueueService.init(); + await secretScanningV2Queue.init(); + await secretRotationV2Queue.init(); + await notificationQueue.init(); + await eventBusService.init(); + + const cronJobs: CronJob[] = []; + if (appCfg.isProductionMode) { + const rateLimitSyncJob = await rateLimitService.initializeBackgroundSync(); + if (rateLimitSyncJob) { + cronJobs.push(rateLimitSyncJob); + } + const licenseSyncJob = await licenseService.initializeBackgroundSync(); + if (licenseSyncJob) { + cronJobs.push(licenseSyncJob); + } + + const microsoftTeamsSyncJob = await microsoftTeamsService.initializeBackgroundSync(); + if (microsoftTeamsSyncJob) { + cronJobs.push(microsoftTeamsSyncJob); + } + + const adminIntegrationsSyncJob = await superAdminService.initializeAdminIntegrationConfigSync(); + if (adminIntegrationsSyncJob) { + cronJobs.push(adminIntegrationsSyncJob); + } + } + + const configSyncJob = await superAdminService.initializeEnvConfigSync(); + if (configSyncJob) { + cronJobs.push(configSyncJob); + } + + const oauthConfigSyncJob = await initializeOauthConfigSync(); + if (oauthConfigSyncJob) { + cronJobs.push(oauthConfigSyncJob); + } + }; // inject all services server.decorate("services", { @@ -2475,38 +2512,6 @@ export const registerRoutes = async ( convertor: convertorService }); - const cronJobs: CronJob[] = []; - if (appCfg.isProductionMode) { - const rateLimitSyncJob = await rateLimitService.initializeBackgroundSync(); - if (rateLimitSyncJob) { - cronJobs.push(rateLimitSyncJob); - } - const licenseSyncJob = await licenseService.initializeBackgroundSync(); - if (licenseSyncJob) { - cronJobs.push(licenseSyncJob); - } - - const microsoftTeamsSyncJob = await microsoftTeamsService.initializeBackgroundSync(); - if (microsoftTeamsSyncJob) { - cronJobs.push(microsoftTeamsSyncJob); - } - - const adminIntegrationsSyncJob = await superAdminService.initializeAdminIntegrationConfigSync(); - if (adminIntegrationsSyncJob) { - cronJobs.push(adminIntegrationsSyncJob); - } - } - - const configSyncJob = await superAdminService.initializeEnvConfigSync(); - if (configSyncJob) { - cronJobs.push(configSyncJob); - } - - const oauthConfigSyncJob = await initializeOauthConfigSync(); - if (oauthConfigSyncJob) { - cronJobs.push(oauthConfigSyncJob); - } - server.decorate("store", { user: userDAL, kmipClient: kmipClientDAL @@ -2595,9 +2600,10 @@ export const registerRoutes = async ( await server.register(registerV4Routes, { prefix: "/api/v4" }); server.addHook("onClose", async () => { - cronJobs.forEach((job) => job.stop()); await telemetryService.flushAll(); await eventBusService.close(); sseService.close(); }); + + return completeServerInitialization; }; diff --git a/backend/src/server/routes/v3/external-migration-router.ts b/backend/src/server/routes/v3/external-migration-router.ts index f0737e01d..367490c82 100644 --- a/backend/src/server/routes/v3/external-migration-router.ts +++ b/backend/src/server/routes/v3/external-migration-router.ts @@ -393,6 +393,56 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider } }); + server.route({ + method: "GET", + url: "/vault/kubernetes-roles", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + namespace: z.string(), + mountPath: z.string() + }), + response: { + 200: z.object({ + roles: z.array( + z.object({ + name: z.string(), + mountPath: z.string(), + allowed_kubernetes_namespaces: z.array(z.string()).nullish(), + allowed_kubernetes_namespace_selector: z.string().nullish(), + token_max_ttl: z.number().nullish(), + token_default_ttl: z.number().nullish(), + token_default_audiences: z.array(z.string()).nullish(), + service_account_name: z.string().nullish(), + kubernetes_role_name: z.string().nullish(), + kubernetes_role_type: z.string().nullish(), + generated_role_rules: z.string().nullish(), + name_template: z.string().nullish(), + extra_annotations: z.record(z.string()).nullish(), + extra_labels: z.record(z.string()).nullish(), + config: z.object({ + kubernetes_host: z.string(), + kubernetes_ca_cert: z.string().nullish() + }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const roles = await server.services.migration.getVaultKubernetesRoles({ + actor: req.permission, + namespace: req.query.namespace, + mountPath: req.query.mountPath + }); + + return { roles }; + } + }); + server.route({ method: "GET", url: "/vault/secret-paths", diff --git a/backend/src/services/app-connection/gitlab/gitlab-connection-enums.ts b/backend/src/services/app-connection/gitlab/gitlab-connection-enums.ts index 3bd01d147..f409a61cf 100644 --- a/backend/src/services/app-connection/gitlab/gitlab-connection-enums.ts +++ b/backend/src/services/app-connection/gitlab/gitlab-connection-enums.ts @@ -5,5 +5,6 @@ export enum GitLabConnectionMethod { export enum GitLabAccessTokenType { Project = "project", - Personal = "personal" + Personal = "personal", + Group = "group" } diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts index 425c9c4d2..5eb9202fd 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts @@ -21,6 +21,8 @@ import { THCVaultKubernetesAuthConfig, THCVaultKubernetesAuthRole, THCVaultKubernetesAuthRoleWithConfig, + THCVaultKubernetesRole, + THCVaultKubernetesSecretsConfig, THCVaultMount, THCVaultMountResponse } from "./hc-vault-connection-types"; @@ -816,3 +818,122 @@ export const getHCVaultKubernetesAuthRoles = async ( }); } }; + +export const getHCVaultKubernetesRoles = async ( + namespace: string, + mountPath: string, + connection: THCVaultConnection, + gatewayService: Pick +): Promise => { + // Remove trailing slash from mount path + const cleanMountPath = mountPath.endsWith("/") ? mountPath.slice(0, -1) : mountPath; + + try { + const instanceUrl = await getHCVaultInstanceUrl(connection); + const accessToken = await getHCVaultAccessToken(connection, gatewayService); + // 1. Get the Kubernetes secrets engine configuration for this mount + const { data: configResponse } = await requestWithHCVaultGateway<{ data: THCVaultKubernetesSecretsConfig }>( + connection, + gatewayService, + { + url: `${instanceUrl}/v1/${cleanMountPath}/config`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + } + ); + + const kubernetesConfig = configResponse.data; + + // 2. List all roles in this mount + let roleNames: string[] = []; + try { + const { data: roleListResponse } = await requestWithHCVaultGateway<{ data: { keys: string[] } }>( + connection, + gatewayService, + { + url: `${instanceUrl}/v1/${cleanMountPath}/roles?list=true`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + } + ); + roleNames = roleListResponse.data.keys || []; + } catch (error) { + // Vault returns 404 when no roles are configured yet + if (error && typeof error === "object" && "response" in error) { + const axiosError = error as { response?: { status?: number } }; + if (axiosError.response?.status === 404) { + return []; + } + } + + throw error; + } + + if (!roleNames || roleNames.length === 0) { + return []; + } + + // 3. Fetch details for each role with concurrency control + const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT); + + const roleDetailsPromises = roleNames.map((roleName) => + limiter(async () => { + const { data: roleResponse } = await requestWithHCVaultGateway<{ + data: { + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + }; + }>(connection, gatewayService, { + url: `${instanceUrl}/v1/${cleanMountPath}/roles/${roleName}`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + }); + + // 4. Merge the role with the config + return { + ...roleResponse.data, + name: roleName, + config: kubernetesConfig, + mountPath: cleanMountPath + } as THCVaultKubernetesRole; + }) + ); + + const roles = await Promise.all(roleDetailsPromises); + + return roles; + } catch (error: unknown) { + logger.error(error, "Unable to list HC Vault Kubernetes secrets engine roles"); + + if (error instanceof AxiosError) { + const errorMessage = + (error.response?.data as { errors?: string[] })?.errors?.[0] || error.message || "Unknown error"; + throw new BadRequestError({ + message: `Failed to list Kubernetes secrets engine roles: ${errorMessage}` + }); + } + + throw new BadRequestError({ + message: "Unable to list Kubernetes secrets engine roles from HashiCorp Vault" + }); + } +}; diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts index d25dbc9e6..2b1956c84 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts @@ -95,3 +95,26 @@ export type THCVaultKubernetesAuthRoleWithConfig = THCVaultKubernetesAuthRole & config: THCVaultKubernetesAuthConfig; mountPath: string; }; + +export type THCVaultKubernetesSecretsConfig = { + kubernetes_host: string; + kubernetes_ca_cert?: string; +}; + +export type THCVaultKubernetesRole = { + name: string; + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + config: THCVaultKubernetesSecretsConfig; + mountPath: string; +}; diff --git a/backend/src/services/external-migration/external-migration-fns/import.ts b/backend/src/services/external-migration/external-migration-fns/import.ts index 62888c5bf..3ffd11088 100644 --- a/backend/src/services/external-migration/external-migration-fns/import.ts +++ b/backend/src/services/external-migration/external-migration-fns/import.ts @@ -82,7 +82,7 @@ export const importDataIntoInfisicalFn = async ({ if (existingEnv) { throw new BadRequestError({ - message: `Environment with slug '${slug}' already exist`, + message: `Environment with slug '${slug}' already exists`, name: "CreateEnvironment" }); } @@ -312,7 +312,7 @@ export const importDataIntoInfisicalFn = async ({ ); if (secretsByKeys.length) { throw new BadRequestError({ - message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}` + message: `Secret already exists: ${secretsByKeys.map((el) => el.key).join(",")}` }); } await fnSecretBulkInsert({ diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index bc2a24c07..42fdc2c26 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -19,6 +19,7 @@ import { convertVaultValueToString, getHCVaultAuthMounts, getHCVaultKubernetesAuthRoles, + getHCVaultKubernetesRoles, getHCVaultSecretsForPath, HCVaultAuthType, listHCVaultMounts, @@ -762,6 +763,56 @@ export const externalMigrationServiceFactory = ({ return roles; }; + const getVaultKubernetesRoles = async ({ + actor, + namespace, + mountPath + }: { + actor: OrgServiceActor; + namespace: string; + mountPath: string; + }) => { + const { hasRole } = await permissionService.getOrgPermission({ + scope: OrganizationActionScope.Any, + actor: actor.type, + actorId: actor.id, + orgId: actor.orgId, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId + }); + + if (!hasRole(OrgMembershipRole.Admin)) { + throw new ForbiddenRequestError({ message: "Only admins can get Kubernetes roles" }); + } + + const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({ + orgId: actor.orgId, + namespace + }); + + if (!vaultConfig) { + throw new NotFoundError({ message: "Vault migration config not found for this namespace" }); + } + + if (!vaultConfig.connection) { + throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" }); + } + + const credentials = await decryptAppConnectionCredentials({ + orgId: vaultConfig.orgId, + encryptedCredentials: vaultConfig.connection.encryptedCredentials, + kmsService, + projectId: null + }); + + const connection = { + ...vaultConfig.connection, + credentials + } as THCVaultConnection; + + return getHCVaultKubernetesRoles(namespace, mountPath, connection, gatewayService); + }; + return { importEnvKeyData, importVaultData, @@ -776,6 +827,7 @@ export const externalMigrationServiceFactory = ({ getVaultAuthMounts, getVaultSecretPaths, importVaultSecrets, - getVaultKubernetesAuthRoles + getVaultKubernetesAuthRoles, + getVaultKubernetesRoles }; }; diff --git a/backend/src/services/notification/notification-queue.ts b/backend/src/services/notification/notification-queue.ts index e5d89c83b..aaff8e23f 100644 --- a/backend/src/services/notification/notification-queue.ts +++ b/backend/src/services/notification/notification-queue.ts @@ -10,6 +10,7 @@ type TNotificationQueueServiceFactoryDep = { export type TNotificationQueueServiceFactory = { pushUserNotifications: (data: TCreateUserNotificationDTO[]) => Promise; + init: () => Promise; }; export const notificationQueueServiceFactory = async ({ @@ -20,20 +21,23 @@ export const notificationQueueServiceFactory = async ({ await queueService.queuePg(QueueJobs.UserNotification, { notifications: data }); }; - await queueService.startPg( - QueueJobs.UserNotification, - async ([job]) => { - const { notifications } = job.data as { notifications: TCreateUserNotificationDTO[] }; - await userNotificationDAL.batchInsert(notifications); - }, - { - batchSize: 1, - workerCount: 2, - pollingIntervalSeconds: 1 - } - ); + const init = async () => { + await queueService.startPg( + QueueJobs.UserNotification, + async ([job]) => { + const { notifications } = job.data as { notifications: TCreateUserNotificationDTO[] }; + await userNotificationDAL.batchInsert(notifications); + }, + { + batchSize: 1, + workerCount: 2, + pollingIntervalSeconds: 1 + } + ); + }; return { - pushUserNotifications + pushUserNotifications, + init }; }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 26de92743..b0bbd9a2a 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -517,7 +517,7 @@ export const orgServiceFactory = ({ if (slug) { const existingOrg = await orgDAL.findOne({ slug, rootOrgId: null }); if (existingOrg && existingOrg?.id !== orgId) - throw new BadRequestError({ message: `Organization with slug ${slug} already exist` }); + throw new BadRequestError({ message: `Organization with slug ${slug} already exists` }); } if (googleSsoAuthEnforced) { @@ -1149,7 +1149,7 @@ export const orgServiceFactory = ({ const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email }); if (doesIncidentContactExist) { throw new BadRequestError({ - message: "Incident contact already exist", + message: "Incident contact already exists", name: "Incident contact exist" }); } diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index bf0bb18af..c402b6030 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -76,7 +76,7 @@ export const projectEnvServiceFactory = ({ const existingEnv = envs.find(({ slug: envSlug }) => envSlug === slug); if (existingEnv) throw new BadRequestError({ - message: "Environment with slug already exist", + message: "Environment with slug already exists", name: "CreateEnvironment" }); @@ -171,7 +171,7 @@ export const projectEnvServiceFactory = ({ const existingEnv = await projectEnvDAL.findOne({ slug, projectId }); if (existingEnv && existingEnv.id !== id) { throw new BadRequestError({ - message: "Environment with slug already exist", + message: "Environment with slug already exists", name: "UpdateEnvironment" }); } diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 05b007a6a..fb2eb70af 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -10,7 +10,8 @@ import { ProjectType, ProjectVersion, TableName, - TProjectEnvironments + TProjectEnvironments, + TProjects } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; @@ -40,7 +41,8 @@ import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-hos import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; -import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { DatabaseErrorCode } from "@app/lib/error-codes"; +import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TProjectPermission } from "@app/lib/types"; @@ -303,19 +305,34 @@ export const projectServiceFactory = ({ }); } - const project = await projectDAL.create( - { - name: workspaceName, - type, - description: workspaceDescription, - orgId: organization.id, - slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), - kmsSecretManagerKeyId: kmsKeyId, - version: ProjectVersion.V3, - pitVersionLimit: 10 - }, - tx - ); + const slug = projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`); + + let project: TProjects; + try { + project = await projectDAL.create( + { + name: workspaceName, + type, + description: workspaceDescription, + orgId: organization.id, + slug, + kmsSecretManagerKeyId: kmsKeyId, + version: ProjectVersion.V3, + pitVersionLimit: 10 + }, + tx + ); + } catch (err) { + if ( + err instanceof DatabaseError && + (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation + ) { + throw new BadRequestError({ + message: `A project with the slug "${slug}" already exists in your organization. Please choose a different name or slug.` + }); + } + throw err; + } if (type === ProjectType.SSH) { await bootstrapSshProject({ @@ -579,39 +596,36 @@ export const projectServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); - if (update.slug) { - const existingProject = await projectDAL.findOne({ - slug: update.slug, - orgId: actorOrgId - }); - if (existingProject && existingProject.id !== project.id) { - throw new BadRequestError({ - message: `Failed to update project slug. The project "${existingProject.name}" with the slug "${existingProject.slug}" already exists in your organization. Please choose a unique slug for your project.` - }); - } - } - if (update.secretDetectionIgnoreValues && !hasRole(ProjectMembershipRole.Admin)) { throw new ForbiddenRequestError({ message: "Only admins can update secret detection ignore values" }); } - const updatedProject = await projectDAL.updateById(project.id, { - name: update.name, - description: update.description, - autoCapitalization: update.autoCapitalization, - enforceCapitalization: update.autoCapitalization, - hasDeleteProtection: update.hasDeleteProtection, - slug: update.slug, - secretSharing: update.secretSharing, - defaultProduct: update.defaultProduct, - showSnapshotsLegacy: update.showSnapshotsLegacy, - secretDetectionIgnoreValues: update.secretDetectionIgnoreValues, - pitVersionLimit: update.pitVersionLimit - }); + try { + const updatedProject = await projectDAL.updateById(project.id, { + name: update.name, + description: update.description, + autoCapitalization: update.autoCapitalization, + enforceCapitalization: update.autoCapitalization, + hasDeleteProtection: update.hasDeleteProtection, + slug: update.slug, + secretSharing: update.secretSharing, + defaultProduct: update.defaultProduct, + showSnapshotsLegacy: update.showSnapshotsLegacy, + secretDetectionIgnoreValues: update.secretDetectionIgnoreValues, + pitVersionLimit: update.pitVersionLimit + }); - return updatedProject; + return updatedProject; + } catch (err) { + if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { + throw new BadRequestError({ + message: `Failed to update project. A project with the slug "${update.slug}" already exists in your organization. Please choose a different slug.` + }); + } + throw err; + } }; const toggleAutoCapitalization = async ({ diff --git a/backend/src/services/secret-tag/secret-tag-service.ts b/backend/src/services/secret-tag/secret-tag-service.ts index 8a08c44dd..58913e08c 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -35,7 +35,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); const existingTag = await secretTagDAL.findOne({ slug, projectId }); - if (existingTag) throw new BadRequestError({ message: "Tag already exist" }); + if (existingTag) throw new BadRequestError({ message: "Tag already exists" }); const newTag = await secretTagDAL.create({ projectId, @@ -53,7 +53,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe if (slug) { const existingTag = await secretTagDAL.findOne({ slug, projectId: tag.projectId }); - if (existingTag && existingTag.id !== tag.id) throw new BadRequestError({ message: "Tag already exist" }); + if (existingTag && existingTag.id !== tag.id) throw new BadRequestError({ message: "Tag already exists" }); } const { permission } = await permissionService.getProjectPermission({ diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 579b19b19..465babebe 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -282,7 +282,7 @@ export const secretV2BridgeServiceFactory = ({ folderId }); if (inputSecret.type === SecretType.Shared && doesSecretExist) - throw new BadRequestError({ message: "Secret already exist" }); + throw new BadRequestError({ message: "Secret already exists" }); // if user creating personal check its shared also exist if (inputSecret.type === SecretType.Personal && !doesSecretExist) { @@ -527,7 +527,7 @@ export const secretV2BridgeServiceFactory = ({ type: SecretType.Shared, folderId }); - if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exist" }); + if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exists" }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretActions.Edit, subject(ProjectPermissionSub.Secrets, { @@ -1674,7 +1674,7 @@ export const secretV2BridgeServiceFactory = ({ } }); if (secrets.length) - throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` }); + throw new BadRequestError({ message: `Secret already exists: ${secrets.map((el) => el.key).join(",")}` }); const project = await projectDAL.findById(projectId); await scanSecretPolicyViolations(projectId, secretPath, inputSecrets, project.secretDetectionIgnoreValues || []); diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index 3b97f5891..820260d29 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -525,7 +525,7 @@ export const fnSecretBlindIndexCheck = async ({ ); if (isNew) { - if (secrets.length) throw new BadRequestError({ message: "Secret already exist" }); + if (secrets.length) throw new BadRequestError({ message: "Secret already exists" }); } else { const secretKeysInDB = unique(secrets, (el) => el.secretBlindIndex as string).map( (el) => blindIndex2KeyName[el.secretBlindIndex as string] @@ -819,7 +819,7 @@ export const createManySecretsRawFnFactory = ({ ); if (secretsStoredInDB.length) throw new BadRequestError({ - message: `Secret already exist: ${secretsStoredInDB.map((el) => el.key).join(",")}` + message: `Secret already exists: ${secretsStoredInDB.map((el) => el.key).join(",")}` }); const inputSecrets = secrets.map((secret) => { diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 723cd368c..9ba1df47d 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -2751,7 +2751,7 @@ export const secretServiceFactory = ({ const existingSecretTags = await secretDAL.getSecretTags(secret.id); if (existingSecretTags.some((tag) => tagSlugs.includes(tag.slug))) { - throw new BadRequestError({ message: "One or more tags already exist on the secret" }); + throw new BadRequestError({ message: "One or more tags already exists on the secret" }); } const combinedTags = new Set([...existingSecretTags.map((tag) => tag.id), ...tags.map((el) => el.id)]); diff --git a/docs/documentation/platform/external-migrations/vault.mdx b/docs/documentation/platform/external-migrations/vault.mdx index e267cc5e9..c58e1ea6b 100644 --- a/docs/documentation/platform/external-migrations/vault.mdx +++ b/docs/documentation/platform/external-migrations/vault.mdx @@ -26,6 +26,12 @@ Infisical provides two approaches for migrating from HashiCorp Vault. This migration approach lets you set up a connection to your Vault instance once, then import specific resources as needed throughout Infisical. + + **Organization Admin Access Required:** All in-platform migration features + (importing secrets, Kubernetes configurations, and policies from Vault) are + only accessible to organization admins. + + ### Step 1: Set Up Your Vault Connection @@ -84,6 +90,19 @@ This migration approach lets you set up a connection to your Vault instance once path "auth/+/role/*" { capabilities = ["read"] } + + # Kubernetes secrets engine - for reading secrets engine configuration and roles + path "+/config" { + capabilities = ["read"] + } + + path "+/roles" { + capabilities = ["list"] + } + + path "+/roles/*" { + capabilities = ["read"] + } ``` @@ -160,6 +179,34 @@ The authentication settings (service accounts, TTL, policies, etc.) will be auto must be manually provided in the form after importing the configuration. +#### Import Kubernetes Dynamic Secret Configurations + +When creating a Kubernetes dynamic secret, you can import the configuration from a Vault Kubernetes secrets engine role: + +1. Navigate to your project and select an environment +2. Click **"+ Add Secret"** dropdown and choose **"Dynamic Secret"** +3. Select **Kubernetes** as the provider +4. Click **"Load from Vault"** at the top of the form + + ![Load Kubernetes Dynamic Secret from Vault](/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png) + +5. Select your Vault namespace, Kubernetes secrets engine mount, and role +6. Click **"Load Configuration"** + +The form will be automatically populated with the role's configuration including: + +- Cluster URL and CA certificate +- Credential type (Static or Dynamic) +- Service account name or Kubernetes role settings +- Allowed namespaces +- Token TTL values +- Token audiences + + + Sensitive values like cluster tokens cannot be retrieved from Vault and must + be manually provided in the form after loading the configuration. + + #### Import and Translate Access Control Policies When configuring project role-based access control, you can import Vault HCL policies and automatically translate them to Infisical permissions. diff --git a/docs/images/app-connections/gitlab/gitlab-group-access-token-created.png b/docs/images/app-connections/gitlab/gitlab-group-access-token-created.png new file mode 100644 index 000000000..f5a7383f2 Binary files /dev/null and b/docs/images/app-connections/gitlab/gitlab-group-access-token-created.png differ diff --git a/docs/images/app-connections/gitlab/gitlab-group-access-token-form-secret-sync.png b/docs/images/app-connections/gitlab/gitlab-group-access-token-form-secret-sync.png new file mode 100644 index 000000000..04dee5a5c Binary files /dev/null and b/docs/images/app-connections/gitlab/gitlab-group-access-token-form-secret-sync.png differ diff --git a/docs/images/app-connections/gitlab/gitlab-group-access-token-list.png b/docs/images/app-connections/gitlab/gitlab-group-access-token-list.png new file mode 100644 index 000000000..e29df0418 Binary files /dev/null and b/docs/images/app-connections/gitlab/gitlab-group-access-token-list.png differ diff --git a/docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png b/docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png new file mode 100644 index 000000000..7cb345637 Binary files /dev/null and b/docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png differ diff --git a/docs/integrations/app-connections/gitlab.mdx b/docs/integrations/app-connections/gitlab.mdx index 4f7223d93..c9af952a7 100644 --- a/docs/integrations/app-connections/gitlab.mdx +++ b/docs/integrations/app-connections/gitlab.mdx @@ -187,31 +187,92 @@ Infisical supports two methods for connecting to GitLab: **OAuth** and **Access - - ## Setup GitLab Access Token Connection in Infisical + + Group access tokens provide access to all projects within a GitLab group, offering group-level control. - - - Navigate to the **App Connections** page in the desired project. - ![App Connections Tab](/images/app-connections/general/add-connection.png) - - - Select the **GitLab Connection** option from the connection options modal. - ![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png) - - - Select the **Access Token** method, paste your GitLab access token in the provided field, and select the appropriate token type. + + + Go to your GitLab group and navigate to Settings > Access Tokens. Click **Add new token** to create a new group access token. + ![GitLab Group Access Tokens](/images/app-connections/gitlab/gitlab-group-access-token-list.png) + + + Fill in the token details: + - **Token name**: A descriptive name for the token + - **Expiration date**: Set an appropriate expiration date + - **Select role and scopes**: Depending on your use case, add the required role and one or more of the following scopes: - ![Configure Access Token](/images/app-connections/gitlab/create-gitlab-access-token-connection.png) + + + For Secret Syncs, the required role depends on your sync destination: + - **Project variables**: Requires **Maintainer** role or higher + - **Group variables**: Requires **Owner** role - Click **Connect** to establish the connection. - - - Your **GitLab Connection** is now available for use. - ![GitLab Access Token Connection](/images/app-connections/gitlab/gitlab-access-token-connection.png) - - + Your token will require the `api` scope. + + ![GitLab Create Group Token](/images/app-connections/gitlab/gitlab-group-access-token-form-secret-sync.png) + + Click **Create group access token** to create the token. + + + Use the **Owner** role if you need to sync to group-level variables. The **Maintainer** role is sufficient only for project-level variables. + + + + To set up Secret Scanning, the required permissions depend on the data source level: + - **Project-level data source:** Requires **Maintainer** role or higher + - **Group-level data source:** Requires **Owner** role + + Your token will require the `api` scope. + + ![GitLab Create Group Token](/images/app-connections/gitlab/gitlab-group-access-token-form-secret-sync.png) + + Click **Create group access token** to create the token. + + + + + Group Access Token connections require manual token rotation when your GitLab access token expires or is regenerated. Monitor your connection status and update the token as needed. + + + + Copy the generated token immediately as it won't be shown again. + ![GitLab Group Token Created](/images/app-connections/gitlab/gitlab-group-access-token-created.png) + + Keep your access token secure and do not share it. Anyone with access to this token can access all projects within your GitLab group. + + + + + + + + +## Setup GitLab Access Token Connection in Infisical + + + + Navigate to the **App Connections** page in the desired project. + ![App Connections Tab](/images/app-connections/general/add-connection.png) + + + Select the **GitLab Connection** option from the connection options modal. + ![Select GitLab Connection](/images/app-connections/gitlab/select-gitlab-connection.png) + + + Select the **Access Token** method, paste your GitLab access token in the provided field, and select the appropriate token type. + + ![Configure Access Token](/images/app-connections/gitlab/create-gitlab-access-token-connection.png) + + Click **Connect** to establish the connection. + + + + + Your **GitLab Connection** is now available for use. + ![GitLab Access Token Connection](/images/app-connections/gitlab/gitlab-access-token-connection.png) + + diff --git a/docs/self-hosting/guides/production-hardening.mdx b/docs/self-hosting/guides/production-hardening.mdx index dfd7b575f..e8f05730c 100644 --- a/docs/self-hosting/guides/production-hardening.mdx +++ b/docs/self-hosting/guides/production-hardening.mdx @@ -137,6 +137,33 @@ Configure database read replicas for high availability PostgreSQL setups: DB_READ_REPLICAS='[{"DB_CONNECTION_URI":"postgresql://user:pass@replica:5432/db?sslmode=require"}]' ``` +### Health Check Endpoints + +Infisical provides two health check endpoints for proper container orchestration and load balancer integration: + +#### `/api/health` - Container Health Check + +Determines whether the application container should be kept alive or terminated. + +- Returns `200` if the application is running and operational +- Returns `200` even during startup tasks +- Returns `503` only if the application has crashed or is unable to start + +**Use for**: Docker health checks, Kubernetes liveness probes, ECS task health checks. + +#### `/api/ready` - Traffic Readiness Check + +Determines whether the application instance is ready to receive production traffic. + +- Returns `200` when the application is fully ready to serve requests +- Returns `503` during startup tasks (e.g., database migrations, initialization) + +**Use for**: Load balancer health checks, Kubernetes readiness probes, ALB target health checks. + +#### Why Two Endpoints? + +Using both endpoints together enables zero-downtime deployments: containers stay alive during startup tasks (`/api/health` returns `200`) while load balancers avoid sending traffic to instances that aren't ready (`/api/ready` returns `503`). This ensures existing instances continue serving traffic until new instances complete their initialization. + ### Operational Security #### User Access Management @@ -207,14 +234,17 @@ docker run --memory=1g --cpus=0.5 infisical/infisical:latest #### Health Monitoring -**Configure health checks**. Set up Docker health checks: +**Configure health checks**. Set up Docker health checks using the appropriate endpoint: ```dockerfile # In Dockerfile or docker-compose.yml +# Use /api/health for container health (keeps container alive during startup) HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:8080/api/status || exit 1 + CMD curl -f http://localhost:8080/api/health || exit 1 ``` +**Note**: Use `/api/health` for container health checks and `/api/ready` for load balancer readiness checks. See [Health Check Endpoints](#health-check-endpoints) for detailed information. + #### Network Security **Host firewall configuration**. Configure host-level firewall for Docker deployments: @@ -433,26 +463,32 @@ stringData: #### Health Monitoring -**Set up health checks**. Configure readiness and liveness probes: +**Set up health checks**. Configure readiness and liveness probes using the appropriate endpoints: ```yaml # Health check configuration containers: - name: infisical + # Use /api/ready for readiness (traffic routing) readinessProbe: httpGet: - path: /api/status + path: /api/ready port: 8080 initialDelaySeconds: 10 periodSeconds: 5 + failureThreshold: 3 + # Use /api/health for liveness (container restart) livenessProbe: httpGet: - path: /api/status + path: /api/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 + failureThreshold: 3 ``` +**Important**: The `readinessProbe` uses `/api/ready` to ensure traffic is only sent to pods that are fully initialized. The `livenessProbe` uses `/api/health` to keep the container alive during startup. See [Health Check Endpoints](#health-check-endpoints) for detailed information. + #### Infrastructure Considerations **Use managed databases (if possible)**. For production deployments, consider using managed PostgreSQL and Redis services instead of in-cluster instances when feasible, as they typically provide better security, backup, and maintenance capabilities. diff --git a/frontend/.storybook/decorators/DocumentDecorator.tsx b/frontend/.storybook/decorators/DocumentDecorator.tsx index 13f4dc8c7..b0d817c6e 100644 --- a/frontend/.storybook/decorators/DocumentDecorator.tsx +++ b/frontend/.storybook/decorators/DocumentDecorator.tsx @@ -3,7 +3,7 @@ import type { Decorator } from "@storybook/react-vite"; export const DocumentDecorator: Decorator = (Story) => { useEffect(() => { - const root = document.getElementsByTagName("html")[0]; + const root = document.documentElement; root.setAttribute("class", "overflow-visible"); }, []); diff --git a/frontend/src/components/app-connections/AppConnectionOption.tsx b/frontend/src/components/app-connections/AppConnectionOption.tsx index 30a37425c..793f9d821 100644 --- a/frontend/src/components/app-connections/AppConnectionOption.tsx +++ b/frontend/src/components/app-connections/AppConnectionOption.tsx @@ -1,9 +1,11 @@ import { components, OptionProps } from "react-select"; import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; -import { faBuilding, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Badge, Tooltip } from "@app/components/v2"; +import { Tooltip } from "@app/components/v2"; +import { Badge, OrgIcon, SubOrgIcon } from "@app/components/v3"; +import { useOrganization } from "@app/context"; import { TAvailableAppConnection } from "@app/hooks/api/appConnections"; export const AppConnectionOption = ({ @@ -13,6 +15,8 @@ export const AppConnectionOption = ({ }: OptionProps) => { const isCreateOption = props.data.id === "_create"; + const { isSubOrganization } = useOrganization(); + return (
@@ -23,15 +27,22 @@ export const AppConnectionOption = ({
) : ( <> -

{children}

+

{children}

{!props.data.projectId && ( - -
- - + + {isSubOrganization ? ( + + + Sub-Organization + + ) : ( + + Organization -
+ )}
)} {isSelected && ( diff --git a/frontend/src/components/pki-syncs/PkiSyncImportStatusBadge.tsx b/frontend/src/components/pki-syncs/PkiSyncImportStatusBadge.tsx index d5b3119d0..2cea3c55b 100644 --- a/frontend/src/components/pki-syncs/PkiSyncImportStatusBadge.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncImportStatusBadge.tsx @@ -1,27 +1,20 @@ import { ReactNode, useEffect, useMemo, useState } from "react"; -import { - faCheck, - faDownload, - faTriangleExclamation, - faXmark, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { differenceInSeconds } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { CheckIcon, DownloadIcon, LucideIcon, TriangleAlertIcon } from "lucide-react"; -import { Badge, Tooltip } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Tooltip } from "@app/components/v2"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs"; type Props = { pkiSync: TPkiSync; - className?: string; mini?: boolean; }; -export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) => { +export const PkiSyncImportStatusBadge = ({ pkiSync, mini }: Props) => { const { importStatus, lastImportMessage, lastImportedAt, destination } = pkiSync; const [hide, setHide] = useState(importStatus === PkiSyncStatus.Succeeded); const destinationName = PKI_SYNC_MAP[destination].name; @@ -50,24 +43,24 @@ export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) => if (!importStatus || hide) return null; - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let label: string; - let icon: IconDefinition; + let Icon: LucideIcon; let tooltipContent: ReactNode; switch (importStatus) { case PkiSyncStatus.Pending: case PkiSyncStatus.Running: - variant = "primary"; + variant = "warning"; label = "Importing Certificates..."; tooltipContent = `Importing certificates from ${destinationName}. This may take a moment.`; - icon = faDownload; + Icon = DownloadIcon; break; case PkiSyncStatus.Failed: variant = "danger"; label = "Failed to Import Certificates"; - icon = faTriangleExclamation; + Icon = TriangleAlertIcon; tooltipContent = (
{failureMessage && ( @@ -93,20 +86,15 @@ export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) => tooltipContent = "Successfully imported certificates."; variant = "success"; label = "Certificates Imported"; - icon = faCheck; + Icon = CheckIcon; } return ( -
- - - {!mini && {label}} - -
+ + + {!mini && label} +
); }; diff --git a/frontend/src/components/pki-syncs/PkiSyncModalHeader.tsx b/frontend/src/components/pki-syncs/PkiSyncModalHeader.tsx index 0e18959dc..32f84c247 100644 --- a/frontend/src/components/pki-syncs/PkiSyncModalHeader.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncModalHeader.tsx @@ -1,6 +1,4 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - +import { DocumentationLinkBadge } from "@app/components/v3"; import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSync } from "@app/hooks/api/pkiSyncs"; @@ -20,23 +18,11 @@ export const PkiSyncModalHeader = ({ destination, isConfigured }: Props) => { className="h-12 w-12 rounded-md bg-bunker-500 p-2" />
-
+
{destinationDetails.name} Certificate Sync - -
- - Docs - -
-
+

{isConfigured diff --git a/frontend/src/components/pki-syncs/PkiSyncRemoveStatusBadge.tsx b/frontend/src/components/pki-syncs/PkiSyncRemoveStatusBadge.tsx index 3e898ba42..968431b84 100644 --- a/frontend/src/components/pki-syncs/PkiSyncRemoveStatusBadge.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncRemoveStatusBadge.tsx @@ -1,27 +1,20 @@ import { ReactNode, useEffect, useMemo, useState } from "react"; -import { - faCheck, - faEraser, - faTriangleExclamation, - faXmark, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { differenceInSeconds } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { AlertTriangleIcon, CheckIcon, EraserIcon, LucideIcon } from "lucide-react"; -import { Badge, Tooltip } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Tooltip } from "@app/components/v2"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs"; type Props = { pkiSync: TPkiSync; - className?: string; mini?: boolean; }; -export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) => { +export const PkiSyncRemoveStatusBadge = ({ pkiSync, mini }: Props) => { const { removeStatus, lastRemoveMessage, lastRemovedAt, destination } = pkiSync; const [hide, setHide] = useState(removeStatus === PkiSyncStatus.Succeeded); const destinationName = PKI_SYNC_MAP[destination].name; @@ -50,24 +43,24 @@ export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) => if (!removeStatus || hide) return null; - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let label: string; - let icon: IconDefinition; + let Icon: LucideIcon; let tooltipContent: ReactNode; switch (removeStatus) { case PkiSyncStatus.Pending: case PkiSyncStatus.Running: - variant = "primary"; + variant = "warning"; label = "Removing Certificates..."; tooltipContent = `Removing certificates from ${destinationName}. This may take a moment.`; - icon = faEraser; + Icon = EraserIcon; break; case PkiSyncStatus.Failed: variant = "danger"; label = "Failed to Remove Certificates"; - icon = faTriangleExclamation; + Icon = AlertTriangleIcon; tooltipContent = (

{failureMessage && ( @@ -93,20 +86,15 @@ export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) => tooltipContent = "Successfully removed certificates."; variant = "success"; label = "Certificates Removed"; - icon = faCheck; + Icon = CheckIcon; } return ( -
- - - {!mini && {label}} - -
+ + + {!mini && label} +
); }; diff --git a/frontend/src/components/pki-syncs/PkiSyncSelect.tsx b/frontend/src/components/pki-syncs/PkiSyncSelect.tsx index 667332c75..aedfebf47 100644 --- a/frontend/src/components/pki-syncs/PkiSyncSelect.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncSelect.tsx @@ -69,7 +69,10 @@ export const PkiSyncSelect = ({ onSelect }: Props) => { type="button" onClick={() => enterprise && !subscription.enterpriseCertificateSyncs - ? handlePopUpOpen("upgradePlan") + ? handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + text: "You can use every Certificate Sync if you switch to Infisical's Enterprise plan." + }) : onSelect(destination) } className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600" @@ -148,6 +151,7 @@ export const PkiSyncSelect = ({ onSelect }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} text="You can use every Certificate Sync if you switch to Infisical's Enterprise plan." />
diff --git a/frontend/src/components/pki-syncs/PkiSyncStatusBadge.tsx b/frontend/src/components/pki-syncs/PkiSyncStatusBadge.tsx index 1fa207202..1c1294280 100644 --- a/frontend/src/components/pki-syncs/PkiSyncStatusBadge.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncStatusBadge.tsx @@ -1,55 +1,51 @@ import { - faCheck, - faExclamationTriangle, - faHourglass, - faRotate, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + AlertTriangleIcon, + CheckIcon, + HourglassIcon, + LucideIcon, + RefreshCwIcon +} from "lucide-react"; -import { Badge, BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { PkiSyncStatus } from "@app/hooks/api/pkiSyncs"; type Props = { status: PkiSyncStatus; -} & Omit; +} & Omit; export const PkiSyncStatusBadge = ({ status }: Props) => { - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let text: string; - let icon: IconDefinition; + let Icon: LucideIcon; switch (status) { case PkiSyncStatus.Failed: variant = "danger"; text = "Failed to Sync"; - icon = faExclamationTriangle; + Icon = AlertTriangleIcon; break; case PkiSyncStatus.Succeeded: variant = "success"; text = "Synced"; - icon = faCheck; + Icon = CheckIcon; break; case PkiSyncStatus.Pending: - variant = "primary"; + variant = "info"; text = "Queued"; - icon = faHourglass; + Icon = HourglassIcon; break; case PkiSyncStatus.Running: default: - variant = "primary"; + variant = "info"; text = "Syncing"; - icon = faRotate; + Icon = RefreshCwIcon; break; } return ( - - - {text} + + + {text} ); }; diff --git a/frontend/src/components/pki-syncs/PkiSyncTable.tsx b/frontend/src/components/pki-syncs/PkiSyncTable.tsx index 983e4687c..4fbc7a16d 100644 --- a/frontend/src/components/pki-syncs/PkiSyncTable.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncTable.tsx @@ -2,7 +2,6 @@ import { faPlug, faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { - Badge, EmptyState, IconButton, Table, @@ -13,6 +12,7 @@ import { THead, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { TPkiSync } from "@app/hooks/api/pkiSyncs"; type Props = { @@ -29,10 +29,10 @@ const getSyncStatusBadge = (status?: string) => { case "FAILED": return Failed; case "RUNNING": - return Running; + return Running; case "PENDING": default: - return Pending; + return Pending; } }; @@ -71,7 +71,7 @@ export const PkiSyncTable = ({ pkiSyncs, onEdit, onDelete, onTrigger }: Props) = {pkiSync.name}
- {pkiSync.destination} + {pkiSync.destination}
{pkiSync.appConnectionName || "Unknown"} diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx index be6b91948..43ee38e52 100644 --- a/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx +++ b/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx @@ -1,6 +1,7 @@ import { useFormContext } from "react-hook-form"; -import { Badge, GenericFieldLabel } from "@app/components/v2"; +import { GenericFieldLabel } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { useListWorkspacePkiSubscribers } from "@app/hooks/api"; diff --git a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx index ad02e1706..ceafc951a 100644 --- a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx @@ -1,6 +1,4 @@ import { useEffect, useState } from "react"; -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useRouterState } from "@tanstack/react-router"; import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms"; @@ -8,6 +6,7 @@ import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader"; import { SecretRotationV2Select } from "@app/components/secret-rotations-v2/SecretRotationV2Select"; import { Modal, ModalContent } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectEnv } from "@app/hooks/api/projects/types"; import { SecretRotation, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; @@ -115,23 +114,9 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: selectedRotation ? ( ) : ( -
+
Add Secret Rotation - -
- - Docs - -
-
+
) } diff --git a/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx b/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx index d44d08046..a834d78dc 100644 --- a/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx +++ b/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx @@ -1,6 +1,4 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - +import { DocumentationLinkBadge } from "@app/components/v3"; import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2"; import { SecretRotation } from "@app/hooks/api/secretRotationsV2"; @@ -20,23 +18,11 @@ export const SecretRotationV2ModalHeader = ({ type, isConfigured }: Props) => { className="h-12 w-12 rounded-md bg-bunker-500 p-2" />
-
+
{destinationDetails.name} Rotation - -
- - Docs - -
-
+ />

{isConfigured diff --git a/frontend/src/components/secret-rotations-v2/SecretRotationV2StatusBadge.tsx b/frontend/src/components/secret-rotations-v2/SecretRotationV2StatusBadge.tsx index c3b38f7a7..edb7cac1b 100644 --- a/frontend/src/components/secret-rotations-v2/SecretRotationV2StatusBadge.tsx +++ b/frontend/src/components/secret-rotations-v2/SecretRotationV2StatusBadge.tsx @@ -1,18 +1,17 @@ -import { faBan, faRotate, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format, formatDistanceToNow } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { BanIcon, RefreshCwIcon, XIcon } from "lucide-react"; import { Tooltip } from "@app/components/v2"; -import { Badge } from "@app/components/v2/Badge/Badge"; +import { Badge } from "@app/components/v3"; import { SecretRotationStatus, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; type Props = { secretRotation: TSecretRotationV2; - className?: string; }; -export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props) => { +export const SecretRotationV2StatusBadge = ({ secretRotation }: Props) => { const { isAutoRotationEnabled, rotationStatus, nextRotationAt, lastRotationMessage } = secretRotation; @@ -50,28 +49,18 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props

} > -
- - - Rotation Failed - -
+ + + Rotation Failed + ); } if (!isAutoRotationEnabled) { return ( - - + + Auto-Rotation Disabled ); @@ -92,20 +81,12 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props } > -
- = 7 ? "success" : "primary"} - className={twMerge( - "flex h-5 w-min items-center gap-1.5 whitespace-nowrap capitalize", - className - )} - > - - {daysToRotation < 0 - ? "Rotating" - : `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`} - -
+ = 7 ? "info" : "warning"} className="capitalize"> + + {daysToRotation < 0 + ? "Rotating" + : `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`} + ); }; diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/shared/SecretsMappingTable.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/shared/SecretsMappingTable.tsx index a012cb392..b5fafe11b 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/shared/SecretsMappingTable.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/shared/SecretsMappingTable.tsx @@ -1,8 +1,10 @@ import { ReactNode } from "react"; -import { faArrowRight, faKey } from "@fortawesome/free-solid-svg-icons"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { KeyIcon } from "lucide-react"; -import { Badge, FormLabel } from "@app/components/v2"; +import { FormLabel } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; type Props = { items: { name: string; input: ReactNode }[]; @@ -32,9 +34,10 @@ export const SecretsMappingTable = ({ items }: Props) => {
- - - {name} + {/* TODO(scott): probably shouldn't be a badge */} + + + {name}
diff --git a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx index e4390024b..ac8c9dc24 100644 --- a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx +++ b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx @@ -1,10 +1,9 @@ import { useEffect, useState } from "react"; -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useRouterState } from "@tanstack/react-router"; import { TSecretScanningDataSourceForm } from "@app/components/secret-scanning/forms/schemas"; import { Modal, ModalContent } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { SecretScanningDataSource, TSecretScanningDataSource @@ -108,23 +107,9 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p selectedDataSource ? ( ) : ( -
+
Add Data Source - -
- - Docs - -
-
+
) } diff --git a/frontend/src/components/secret-scanning/SecretScanningDataSourceModalHeader.tsx b/frontend/src/components/secret-scanning/SecretScanningDataSourceModalHeader.tsx index da964d6b6..6ae644fa5 100644 --- a/frontend/src/components/secret-scanning/SecretScanningDataSourceModalHeader.tsx +++ b/frontend/src/components/secret-scanning/SecretScanningDataSourceModalHeader.tsx @@ -1,6 +1,4 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - +import { DocumentationLinkBadge } from "@app/components/v3"; import { SECRET_SCANNING_DATA_SOURCE_MAP } from "@app/helpers/secretScanningV2"; import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; @@ -20,23 +18,11 @@ export const SecretScanningDataSourceModalHeader = ({ type, isConfigured }: Prop className="h-12 rounded-md bg-bunker-500 p-2" />
-
+
{dataSourceDetails.name} Data Source - -
- - Docs - -
-
+ />

{isConfigured ? "Edit" : "Connect a"} {dataSourceDetails.name} Data Source diff --git a/frontend/src/components/secret-scanning/SecretScanningScanStatus.tsx b/frontend/src/components/secret-scanning/SecretScanningScanStatus.tsx index a02e050f0..35913414f 100644 --- a/frontend/src/components/secret-scanning/SecretScanningScanStatus.tsx +++ b/frontend/src/components/secret-scanning/SecretScanningScanStatus.tsx @@ -1,22 +1,23 @@ -import { faArrowRotateForward, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { formatDistance } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { CheckIcon, RotateCwIcon, XIcon } from "lucide-react"; -import { Badge, Tooltip } from "@app/components/v2"; +import { Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { SecretScanningScanStatus } from "@app/hooks/api/secretScanningV2"; type Props = { status: SecretScanningScanStatus; statusMessage?: string | null; - className?: string; + scannedAt?: string | null; }; export const SecretScanningScanStatusBadge = ({ status, statusMessage, - className, + scannedAt }: Props) => { if (status === SecretScanningScanStatus.Failed) { @@ -53,11 +54,8 @@ export const SecretScanningScanStatusBadge = ({ } >

- - + + Scan Error
@@ -67,26 +65,17 @@ export const SecretScanningScanStatusBadge = ({ if (status === SecretScanningScanStatus.Queued || status === SecretScanningScanStatus.Scanning) { return ( - - - Scanning + + + Scanning ); } return ( - - - Complete + + + Complete ); }; diff --git a/frontend/src/components/secret-syncs/SecretSyncImportStatusBadge.tsx b/frontend/src/components/secret-syncs/SecretSyncImportStatusBadge.tsx index 54cfcd588..494528d00 100644 --- a/frontend/src/components/secret-syncs/SecretSyncImportStatusBadge.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncImportStatusBadge.tsx @@ -1,27 +1,20 @@ import { ReactNode, useEffect, useMemo, useState } from "react"; -import { - faCheck, - faDownload, - faTriangleExclamation, - faXmark, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { differenceInSeconds } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { AlertTriangleIcon, CheckIcon, DownloadIcon, LucideIcon } from "lucide-react"; -import { Badge, Tooltip } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Tooltip } from "@app/components/v2"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs"; type Props = { secretSync: TSecretSync; - className?: string; mini?: boolean; }; -export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Props) => { +export const SecretSyncImportStatusBadge = ({ secretSync, mini }: Props) => { const { importStatus, lastImportMessage, lastImportedAt, destination } = secretSync; const [hide, setHide] = useState(importStatus === SecretSyncStatus.Succeeded); const destinationName = SECRET_SYNC_MAP[destination].name; @@ -50,24 +43,24 @@ export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Pro if (!importStatus || hide) return null; - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let label: string; - let icon: IconDefinition; + let Icon: LucideIcon; let tooltipContent: ReactNode; switch (importStatus) { case SecretSyncStatus.Pending: case SecretSyncStatus.Running: - variant = "primary"; + variant = "warning"; label = "Importing Secrets..."; tooltipContent = `Importing secrets from ${destinationName}. This may take a moment.`; - icon = faDownload; + Icon = DownloadIcon; break; case SecretSyncStatus.Failed: variant = "danger"; label = "Failed to Import Secrets"; - icon = faTriangleExclamation; + Icon = AlertTriangleIcon; tooltipContent = (
{failureMessage && ( @@ -93,20 +86,15 @@ export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Pro tooltipContent = "Successfully imported secrets."; variant = "success"; label = "Secrets Imported"; - icon = faCheck; + Icon = CheckIcon; } return ( -
- - - {!mini && {label}} - -
+ + + {!mini && label} +
); }; diff --git a/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx b/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx index ebbe34d75..41cbbcf58 100644 --- a/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx @@ -1,6 +1,4 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - +import { DocumentationLinkBadge } from "@app/components/v3"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync } from "@app/hooks/api/secretSyncs"; @@ -20,23 +18,11 @@ export const SecretSyncModalHeader = ({ destination, isConfigured }: Props) => { className="h-12 w-12 rounded-md bg-bunker-500 object-contain p-2" />
-
+
{destinationDetails.name} Sync - -
- - Docs - -
-
+ />

{isConfigured diff --git a/frontend/src/components/secret-syncs/SecretSyncRemoveStatusBadge.tsx b/frontend/src/components/secret-syncs/SecretSyncRemoveStatusBadge.tsx index abbd78838..917386818 100644 --- a/frontend/src/components/secret-syncs/SecretSyncRemoveStatusBadge.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncRemoveStatusBadge.tsx @@ -1,27 +1,20 @@ import { ReactNode, useEffect, useMemo, useState } from "react"; -import { - faCheck, - faEraser, - faTriangleExclamation, - faXmark, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { differenceInSeconds } from "date-fns"; -import { twMerge } from "tailwind-merge"; +import { AlertTriangleIcon, CheckIcon, EraserIcon, LucideIcon } from "lucide-react"; -import { Badge, Tooltip } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Tooltip } from "@app/components/v2"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs"; type Props = { secretSync: TSecretSync; - className?: string; mini?: boolean; }; -export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Props) => { +export const SecretSyncRemoveStatusBadge = ({ secretSync, mini }: Props) => { const { removeStatus, lastRemoveMessage, lastRemovedAt, destination } = secretSync; const [hide, setHide] = useState(removeStatus === SecretSyncStatus.Succeeded); const destinationName = SECRET_SYNC_MAP[destination].name; @@ -50,24 +43,24 @@ export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Pro if (!removeStatus || hide) return null; - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let label: string; - let icon: IconDefinition; + let Icon: LucideIcon; let tooltipContent: ReactNode; switch (removeStatus) { case SecretSyncStatus.Pending: case SecretSyncStatus.Running: - variant = "primary"; + variant = "warning"; label = "Removing Secrets..."; tooltipContent = `Removing secrets from ${destinationName}. This may take a moment.`; - icon = faEraser; + Icon = EraserIcon; break; case SecretSyncStatus.Failed: variant = "danger"; label = "Failed to Remove Secrets"; - icon = faTriangleExclamation; + Icon = AlertTriangleIcon; tooltipContent = (

{failureMessage && ( @@ -93,20 +86,15 @@ export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Pro tooltipContent = "Successfully removed secrets."; variant = "success"; label = "Secrets Removed"; - icon = faCheck; + Icon = CheckIcon; } return ( -
- - - {!mini && {label}} - -
+ + + {!mini && label} +
); }; diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index 0c0fdd67b..b3aeab962 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -67,7 +67,9 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { type="button" onClick={() => enterprise && !subscription.enterpriseSecretSyncs - ? handlePopUpOpen("upgradePlan") + ? handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }) : onSelect(destination) } className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600" @@ -145,6 +147,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { )} handlePopUpToggle("upgradePlan", isOpen)} text="You can use every Secret Sync if you switch to Infisical's Enterprise plan." /> diff --git a/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx b/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx index e0d541138..26ba74d2a 100644 --- a/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx @@ -1,55 +1,51 @@ import { - faCheck, - faExclamationTriangle, - faHourglass, - faRotate, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + AlertTriangleIcon, + CheckIcon, + HourglassIcon, + LucideIcon, + RefreshCwIcon +} from "lucide-react"; -import { Badge, BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { SecretSyncStatus } from "@app/hooks/api/secretSyncs"; type Props = { status: SecretSyncStatus; -} & Omit; +} & Omit; export const SecretSyncStatusBadge = ({ status }: Props) => { - let variant: BadgeProps["variant"]; + let variant: TBadgeProps["variant"]; let text: string; - let icon: IconDefinition; + let Icon: LucideIcon; switch (status) { case SecretSyncStatus.Failed: variant = "danger"; text = "Failed to Sync"; - icon = faExclamationTriangle; + Icon = AlertTriangleIcon; break; case SecretSyncStatus.Succeeded: variant = "success"; text = "Synced"; - icon = faCheck; + Icon = CheckIcon; break; case SecretSyncStatus.Pending: - variant = "primary"; + variant = "info"; text = "Queued"; - icon = faHourglass; + Icon = HourglassIcon; break; case SecretSyncStatus.Running: default: - variant = "primary"; + variant = "info"; text = "Syncing"; - icon = faRotate; + Icon = RefreshCwIcon; break; } return ( - - - {text} + + + {text} ); }; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx index e7d988f46..08414c14e 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/GcpSyncFields.tsx @@ -5,14 +5,8 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; -import { - Badge, - FilterableSelect, - FormControl, - Select, - SelectItem, - Tooltip -} from "@app/components/v2"; +import { FilterableSelect, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { GCP_SYNC_SCOPES } from "@app/helpers/secretSyncs"; import { useGcpConnectionListProjectLocations, @@ -26,10 +20,7 @@ import { TSecretSyncForm } from "../schemas"; const formatOptionLabel = ({ displayName, locationId }: TGcpLocation) => (
- {displayName}{" "} - - {locationId} - + {displayName} {locationId}
); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/shared/AwsRegionSelect.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/shared/AwsRegionSelect.tsx index 6b1894626..829d7c44f 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/shared/AwsRegionSelect.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/shared/AwsRegionSelect.tsx @@ -2,7 +2,8 @@ import { components, OptionProps, SingleValue } from "react-select"; import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Badge, FilterableSelect } from "@app/components/v2"; +import { FilterableSelect } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { AWS_REGIONS } from "@app/helpers/appConnections"; const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGIONS)[number]>) => { @@ -10,7 +11,7 @@ const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGI

{children}

- + {props.data.slug} {isSelected && ( diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsParameterStoreSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsParameterStoreSyncReviewFields.tsx index 073cb4175..a70752703 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsParameterStoreSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsParameterStoreSyncReviewFields.tsx @@ -1,10 +1,10 @@ import { useFormContext } from "react-hook-form"; -import { faEye } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { EyeIcon } from "lucide-react"; import { GenericFieldLabel } from "@app/components/secret-syncs"; import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; -import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { AWS_REGIONS } from "@app/helpers/appConnections"; import { SecretSync } from "@app/hooks/api/secretSyncs"; @@ -41,11 +41,9 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => { } >
- - - - {tags.length} Tag{tags.length > 1 ? "s" : ""} - + + + {tags.length} Tag{tags.length > 1 ? "s" : ""}
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields.tsx index 90d7d5023..d37cae3f1 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields.tsx @@ -1,10 +1,10 @@ import { useFormContext } from "react-hook-form"; -import { faEye } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { EyeIcon } from "lucide-react"; import { GenericFieldLabel } from "@app/components/secret-syncs"; import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; -import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { AWS_REGIONS } from "@app/helpers/appConnections"; import { SecretSync } from "@app/hooks/api/secretSyncs"; import { AwsSecretsManagerSyncMappingBehavior } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync"; @@ -26,7 +26,7 @@ export const AwsSecretsManagerSyncReviewFields = () => { <> {awsRegion?.name} - + {awsRegion?.slug}{" "} @@ -73,11 +73,9 @@ export const AwsSecretsManagerSyncOptionsReviewFields = () => { } >
- - - - {tags.length} Tag{tags.length > 1 ? "s" : ""} - + + + {tags.length} Tag{tags.length > 1 ? "s" : ""}
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx index f195f0e72..313d7eb3d 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx @@ -2,7 +2,7 @@ import { useFormContext } from "react-hook-form"; import { GenericFieldLabel } from "@app/components/secret-syncs"; import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { SecretSync } from "@app/hooks/api/secretSyncs"; import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync"; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 5c04fb308..8c8ea523b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { GenericFieldLabel } from "@app/components/secret-syncs"; import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useOrganization, useProject } from "@app/context"; import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync, useDuplicateDestinationCheck } from "@app/hooks/api/secretSyncs"; @@ -257,7 +257,7 @@ export const SecretSyncReviewFields = () => {
- + {isAutoSyncEnabled ? "Enabled" : "Disabled"} @@ -268,7 +268,7 @@ export const SecretSyncReviewFields = () => { {AdditionalSyncOptionsFieldsComponent} {disableSecretDeletion && ( - Disabled + Disabled )}
diff --git a/frontend/src/components/v2/Badge/Badge.tsx b/frontend/src/components/v2/Badge/Badge.tsx deleted file mode 100644 index 36541a781..000000000 --- a/frontend/src/components/v2/Badge/Badge.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { forwardRef } from "react"; -import { cva, VariantProps } from "cva"; -import { twMerge } from "tailwind-merge"; - -interface IProps { - children: React.ReactNode; - className?: string; - onClick?: (e: React.MouseEvent) => void; -} - -const badgeVariants = cva( - [ - "inline-block cursor-default rounded-md bg-yellow/20 px-1.5 py-0.5 text-xs text-yellow opacity-80 hover:opacity-100" - ], - { - variants: { - variant: { - primary: "bg-yellow/20 text-yellow", - danger: "bg-red/20 text-red", - success: "bg-green/20 text-green", - org: "bg-org-v1/20 text-org-v1 [&_svg]:text-org-v1 flex items-center opacity-100 hover:bg-org-v1/10 [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap", - namespace: - "bg-namespace-v1/20 text-namespace-v1 [&_svg]:text-namespace-v1 flex opacity-100 hover:bg-namespace-v1/10 items-center [&_svg]:size-3.5 gap-x-1.5 w-min whitespace-nowrap", - project: - "bg-primary/10 text-primary [&_svg]:text-primary opacity-100 hover:bg-primary/10 flex items-center [&_svg]:size-3 w-min gap-x-1.5 whitespace-nowrap", - instance: - "bg-mineshaft-200/20 text-mineshaft-200 [&_svg]:text-mineshaft-200 opacity-100 hover:bg-mineshaft-200/20 flex items-center [&_svg]:size-3 gap-x-1.5 w-min whitespace-nowrap" - } - } - } -); - -export type BadgeProps = VariantProps & IProps; - -export const Badge = forwardRef( - ({ children, className, variant, ...props }, ref) => { - return ( -
- {children} -
- ); - } -); diff --git a/frontend/src/components/v2/Badge/index.tsx b/frontend/src/components/v2/Badge/index.tsx deleted file mode 100644 index 5c7042709..000000000 --- a/frontend/src/components/v2/Badge/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Badge } from "./Badge"; diff --git a/frontend/src/components/v2/PageHeader/PageHeader.tsx b/frontend/src/components/v2/PageHeader/PageHeader.tsx index e3f72f61b..f84edfcd2 100644 --- a/frontend/src/components/v2/PageHeader/PageHeader.tsx +++ b/frontend/src/components/v2/PageHeader/PageHeader.tsx @@ -1,11 +1,16 @@ -import { IconDefinition } from "@fortawesome/free-brands-svg-icons"; -import { faCube, faCubes, faGlobe, faServer } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createElement } from "react"; import { ReactNode } from "@tanstack/react-router"; +import { LucideIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; -import { Badge } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { + Badge, + InstanceIcon, + OrgIcon, + ProjectIcon, + SubOrgIcon, + TBadgeProps +} from "@app/components/v3"; import { ProjectType } from "@app/hooks/api/projects/types"; type Props = { @@ -16,19 +21,19 @@ type Props = { scope: "org" | "namespace" | "instance" | ProjectType | null; }; -const SCOPE_NAME: Record, { label: string; icon: IconDefinition }> = { - org: { label: "Organization", icon: faGlobe }, - [ProjectType.SecretManager]: { label: "Project", icon: faCube }, - [ProjectType.CertificateManager]: { label: "Project", icon: faCube }, - [ProjectType.SSH]: { label: "Project", icon: faCube }, - [ProjectType.KMS]: { label: "Project", icon: faCube }, - [ProjectType.PAM]: { label: "Project", icon: faCube }, - [ProjectType.SecretScanning]: { label: "Project", icon: faCube }, - namespace: { label: "Sub-Organization", icon: faCubes }, - instance: { label: "Server", icon: faServer } +const SCOPE_NAME: Record, { label: string; icon: LucideIcon }> = { + org: { label: "Organization", icon: OrgIcon }, + [ProjectType.SecretManager]: { label: "Project", icon: ProjectIcon }, + [ProjectType.CertificateManager]: { label: "Project", icon: ProjectIcon }, + [ProjectType.SSH]: { label: "Project", icon: ProjectIcon }, + [ProjectType.KMS]: { label: "Project", icon: ProjectIcon }, + [ProjectType.PAM]: { label: "Project", icon: ProjectIcon }, + [ProjectType.SecretScanning]: { label: "Project", icon: ProjectIcon }, + namespace: { label: "Sub-Organization", icon: SubOrgIcon }, + instance: { label: "Server", icon: InstanceIcon } }; -const SCOPE_VARIANT: Record, BadgeProps["variant"]> = { +const SCOPE_VARIANT: Record, TBadgeProps["variant"]> = { org: "org", [ProjectType.SecretManager]: "project", [ProjectType.CertificateManager]: "project", @@ -36,8 +41,8 @@ const SCOPE_VARIANT: Record, BadgeProps["variant"]> [ProjectType.KMS]: "project", [ProjectType.PAM]: "project", [ProjectType.SecretScanning]: "project", - namespace: "namespace", - instance: "instance" + namespace: "sub-org", + instance: "neutral" }; export const PageHeader = ({ title, description, children, className, scope }: Props) => ( @@ -47,7 +52,7 @@ export const PageHeader = ({ title, description, children, className, scope }: P

{title}

{scope && ( - + {createElement(SCOPE_NAME[scope].icon)} {SCOPE_NAME[scope].label} )} diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index 3954b8b20..99e83823e 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -3,6 +3,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { faCaretDown, faCaretUp, faCheck } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as SelectPrimitive from "@radix-ui/react-select"; +import { LucideIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { Spinner } from "../Spinner"; @@ -19,6 +20,7 @@ type Props = { icon?: IconProp; isMulti?: boolean; iconClassName?: string; + LucideIcon?: LucideIcon; dropdownContainerStyle?: React.CSSProperties; side?: SelectPrimitive.SelectContentProps["side"]; }; @@ -39,6 +41,7 @@ export const Select = forwardRef( iconClassName, dropdownContainerStyle, side, + LucideIcon: Lucide, ...props }, ref @@ -65,6 +68,7 @@ export const Select = forwardRef( >
{props.icon && } + {Lucide && }
diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 8019c3589..cb4430aa4 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -2,7 +2,6 @@ export * from "./AccessRestrictedBanner"; export * from "./Accordion"; export * from "./Alert"; -export * from "./Badge"; export * from "./Breadcrumb"; export * from "./Button"; export * from "./Card"; diff --git a/frontend/src/components/v3/generic/Badge/Badge.stories.tsx b/frontend/src/components/v3/generic/Badge/Badge.stories.tsx index da0743a66..4cbf955f9 100644 --- a/frontend/src/components/v3/generic/Badge/Badge.stories.tsx +++ b/frontend/src/components/v3/generic/Badge/Badge.stories.tsx @@ -1,9 +1,9 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { Link } from "@tanstack/react-router"; import { + AlertTriangleIcon, + AsteriskIcon, BanIcon, - BoxesIcon, - BoxIcon, CheckIcon, ChevronsUpDownIcon, CircleXIcon, @@ -11,9 +11,11 @@ import { GlobeIcon, InfoIcon, RadarIcon, - TriangleAlertIcon + TriangleAlertIcon, + UserIcon } from "lucide-react"; +import { OrgIcon, ProjectIcon, SubOrgIcon } from "../../platform"; import { Badge } from "./Badge"; /** @@ -69,7 +71,29 @@ export const Neutral: Story = { parameters: { docs: { description: { - story: "Use this variant when indicating neutral or disabled states." + story: + "Use this variant when indicating neutral or disabled states or when linking to external documents." + } + } + } +}; + +export const Ghost: Story = { + name: "Variant: Ghost", + args: { + variant: "ghost", + children: ( + <> + + User + + ) + }, + parameters: { + docs: { + description: { + story: + "Use this variant when indicating a configuration or property value. Avoid using this variant as an interactive element as it is not intuitive to interact with." } } } @@ -109,8 +133,7 @@ export const Info: Story = { parameters: { docs: { description: { - story: - "Use this variant when indicating informational states or linking to external references." + story: "Use this variant when indicating informational states." } } } @@ -162,7 +185,7 @@ export const Organization: Story = { variant: "org", children: ( <> - + Organization ) @@ -182,7 +205,7 @@ export const SubOrganization: Story = { variant: "sub-org", children: ( <> - + Sub-Organization ) @@ -202,7 +225,7 @@ export const Project: Story = { variant: "project", children: ( <> - + Project ) @@ -283,10 +306,11 @@ export const AsButton: Story = { export const IsTruncatable: Story = { name: "Example: isTruncatable", args: { + variant: "org", isTruncatable: true, children: ( <> - + Infisical Infrastructure ) @@ -295,7 +319,7 @@ export const IsTruncatable: Story = { docs: { description: { story: - "Use the `isTruncatable` prop with a `span` tag wrapping the text content to support truncation." + "Use the `isTruncatable` prop with a `span` tag wrapping the text content to support truncation. Parent `div` should have a fixed width and `flex` class." } } }, @@ -305,3 +329,48 @@ export const IsTruncatable: Story = {
) }; + +export const IsSquare: Story = { + name: "Example: isSquare", + args: { + variant: "danger", + isSquare: true, + children: + }, + parameters: { + docs: { + description: { + story: + "Use the `isSquare` prop when displaying a squared badge with 1-2 character text or only an icon." + } + } + } +}; + +export const IsFullWidth: Story = { + name: "Example: isFullWidth", + args: { + variant: "neutral", + isFullWidth: true, + + children: ( + <> + + Secret Value + + ) + }, + parameters: { + docs: { + description: { + story: + "Use the `isFullWidth` prop to expand the badges width to fill it's parent container." + } + } + }, + decorators: (Story) => ( +
+ +
+ ) +}; diff --git a/frontend/src/components/v3/generic/Badge/Badge.tsx b/frontend/src/components/v3/generic/Badge/Badge.tsx index cc66806bc..f94bff5f2 100644 --- a/frontend/src/components/v3/generic/Badge/Badge.tsx +++ b/frontend/src/components/v3/generic/Badge/Badge.tsx @@ -6,30 +6,37 @@ import { cn } from "@app/components/v3/utils"; const badgeVariants = cva( [ - "select-none items-center rounded-sm px-1.5 py-0.5 text-xs", - "gap-x-1 [a&,button&]:cursor-pointer inline-flex", - "[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&>svg]:size-3", + "select-none items-center align-middle rounded-sm h-4.5 px-1.5 text-xs", + "gap-x-1 [a&,button&]:cursor-pointer inline-flex font-normal", + "[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&_svg:not([class*='size-'])]:size-3", "transition duration-200 ease-in-out" ], { variants: { isTruncatable: { true: "[&>span,&>p]:truncate min-w-0", - false: "w-fit shrink-0 whitespace-nowrap overflow-hidden" + false: "w-fit shrink-0 min-w-fit whitespace-nowrap overflow-hidden" + }, + isFullWidth: { + true: "w-full justify-center" + }, + isSquare: { + true: "w-4.5 justify-center px-0.5" }, variant: { - neutral: "bg-neutral/30 text-neutral [a&,button&]:hover:bg-neutral/40", - success: "bg-success/30 text-success [a&,button&]:hover:bg-success/40", - info: "bg-info/30 text-info [a&,button&]:hover:bg-info/40", - warning: "bg-warning/30 text-warning [a&,button&]:hover:bg-warning/40", - danger: "bg-danger/30 text-danger [a&,button&]:hover:bg-danger/40", - project: "bg-project/30 text-project [a&,button&]:hover:bg-project/40", - org: "bg-org/30 text-org [a&,button&]:hover:bg-org/40", - "sub-org": "bg-sub-org/30 text-sub-org [a&,button&]:hover:bg-sub-org/40" + ghost: "text-mineshaft-200 gap-x-2", + neutral: "bg-neutral/25 text-neutral [a&,button&]:hover:bg-neutral/35", + success: "bg-success/25 text-success [a&,button&]:hover:bg-success/35", + info: "bg-info/25 text-info [a&,button&]:hover:bg-info/35", + warning: "bg-warning/25 text-warning [a&,button&]:hover:bg-warning/35", + danger: "bg-danger/25 text-danger [a&,button&]:hover:bg-danger/35", + project: "bg-project/25 text-project [a&,button&]:hover:bg-project/35", + org: "bg-org/25 text-org [a&,button&]:hover:bg-org/35", + "sub-org": "bg-sub-org/25 text-sub-org [a&,button&]:hover:bg-sub-org/35" } }, defaultVariants: { - variant: "success" + variant: "neutral" } } ); @@ -37,16 +44,28 @@ const badgeVariants = cva( type TBadgeProps = VariantProps & React.ComponentProps<"span"> & { asChild?: boolean; + variant: NonNullable["variant"]>; // TODO: REMOVE }; const Badge = forwardRef( - ({ className, variant, asChild = false, isTruncatable = false, ...props }, ref): JSX.Element => { + ( + { + className, + variant, + asChild = false, + isTruncatable = false, + isFullWidth = false, + isSquare = false, + ...props + }, + ref + ): JSX.Element => { const Comp = asChild ? Slot : "span"; return ( ); diff --git a/frontend/src/components/v3/index.ts b/frontend/src/components/v3/index.ts index e69de29bb..1b74c0e26 100644 --- a/frontend/src/components/v3/index.ts +++ b/frontend/src/components/v3/index.ts @@ -0,0 +1,2 @@ +export * from "./generic"; +export * from "./platform"; diff --git a/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.stories.tsx b/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.stories.tsx new file mode 100644 index 000000000..bd746532e --- /dev/null +++ b/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.stories.tsx @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; + +import { DocumentationLinkBadge } from "./DocumentationLinkBadge"; + +/** + * The documentation link badge is a re-usable component to link to Infisical documentation references. + */ +const meta = { + title: "Platform/Documentation Link Badge", + component: DocumentationLinkBadge, + parameters: { + layout: "centered" + }, + tags: ["autodocs"], + argTypes: {}, + args: { href: "https://infisical.com/docs/documentation/getting-started/introduction" } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + name: "Example: Default" +}; diff --git a/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.tsx b/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.tsx new file mode 100644 index 000000000..b847f93ae --- /dev/null +++ b/frontend/src/components/v3/platform/DocumentationLinkBadge/DocumentationLinkBadge.tsx @@ -0,0 +1,18 @@ +import { BookOpenIcon } from "lucide-react"; + +import { Badge } from "@app/components/v3"; + +type TDocumentationLinkBadgeProps = { + href: string; +}; + +export function DocumentationLinkBadge({ href }: TDocumentationLinkBadgeProps) { + return ( + + + + Documentation + + + ); +} diff --git a/frontend/src/components/v3/platform/DocumentationLinkBadge/index.ts b/frontend/src/components/v3/platform/DocumentationLinkBadge/index.ts new file mode 100644 index 000000000..aed3c2244 --- /dev/null +++ b/frontend/src/components/v3/platform/DocumentationLinkBadge/index.ts @@ -0,0 +1 @@ +export * from "./DocumentationLinkBadge"; diff --git a/frontend/src/components/v3/platform/ScopeIcons.tsx b/frontend/src/components/v3/platform/ScopeIcons.tsx new file mode 100644 index 000000000..8f8712319 --- /dev/null +++ b/frontend/src/components/v3/platform/ScopeIcons.tsx @@ -0,0 +1,8 @@ +import { BoxesIcon, BoxIcon, Building2Icon, ServerIcon } from "lucide-react"; + +const InstanceIcon = ServerIcon; +const OrgIcon = Building2Icon; +const SubOrgIcon = BoxesIcon; +const ProjectIcon = BoxIcon; + +export { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon }; diff --git a/frontend/src/components/v3/platform/index.ts b/frontend/src/components/v3/platform/index.ts new file mode 100644 index 000000000..ab5839618 --- /dev/null +++ b/frontend/src/components/v3/platform/index.ts @@ -0,0 +1,2 @@ +export * from "./DocumentationLinkBadge"; +export * from "./ScopeIcons"; diff --git a/frontend/src/helpers/policies.ts b/frontend/src/helpers/policies.ts index d798d3c10..58feaf703 100644 --- a/frontend/src/helpers/policies.ts +++ b/frontend/src/helpers/policies.ts @@ -1,20 +1,20 @@ -import { IconDefinition } from "@fortawesome/free-brands-svg-icons"; -import { faArrowRightToBracket, faEdit } from "@fortawesome/free-solid-svg-icons"; +import { LucideIcon, UserCheckIcon, UserPenIcon } from "lucide-react"; +import { TBadgeProps } from "@app/components/v3"; import { PolicyType } from "@app/hooks/api/policies/enums"; export const policyDetails: Record< PolicyType, - { name: string; className: string; icon: IconDefinition } + { name: string; variant: TBadgeProps["variant"]; Icon: LucideIcon } > = { [PolicyType.AccessPolicy]: { - className: "bg-green/20 text-green", + variant: "ghost", name: "Access Policy", - icon: faArrowRightToBracket + Icon: UserCheckIcon }, [PolicyType.ChangePolicy]: { - className: "bg-yellow/20 text-yellow", + variant: "ghost", name: "Change Policy", - icon: faEdit + Icon: UserPenIcon } }; diff --git a/frontend/src/helpers/secretScanningV2.ts b/frontend/src/helpers/secretScanningV2.ts index 4e88b8482..ee5226611 100644 --- a/frontend/src/helpers/secretScanningV2.ts +++ b/frontend/src/helpers/secretScanningV2.ts @@ -1,10 +1,6 @@ -import { - faBan, - faCheck, - faMagnifyingGlassMinus, - faWarning -} from "@fortawesome/free-solid-svg-icons"; +import { AlertTriangleIcon, BanIcon, CheckIcon, LucideIcon, SearchSlashIcon } from "lucide-react"; +import { TBadgeProps } from "@app/components/v3"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { SecretScanningDataSource, @@ -74,12 +70,28 @@ export const RESOURCE_DESCRIPTION_HELPER: Record< } }; -export const SECRET_SCANNING_FINDING_STATUS_ICON_MAP = { - [SecretScanningFindingStatus.Resolved]: { icon: faCheck, className: "text-green" }, - [SecretScanningFindingStatus.Unresolved]: { icon: faWarning, className: "text-yellow" }, - [SecretScanningFindingStatus.Ignore]: { icon: faBan, className: "text-mineshaft-400" }, +export const SECRET_SCANNING_FINDING_STATUS_MAP: Record< + SecretScanningFindingStatus, + { Icon: LucideIcon; variant: TBadgeProps["variant"]; className: string } +> = { + [SecretScanningFindingStatus.Resolved]: { + Icon: CheckIcon, + variant: "success", + className: "text-success" + }, + [SecretScanningFindingStatus.Unresolved]: { + Icon: AlertTriangleIcon, + variant: "warning", + className: "text-warning" + }, + [SecretScanningFindingStatus.Ignore]: { + Icon: BanIcon, + variant: "neutral", + className: "text-neutral" + }, [SecretScanningFindingStatus.FalsePositive]: { - icon: faMagnifyingGlassMinus, - className: "text-mineshaft-400" + Icon: SearchSlashIcon, + variant: "neutral", + className: "text-neutral" } }; diff --git a/frontend/src/hooks/api/appConnections/gitlab/types.ts b/frontend/src/hooks/api/appConnections/gitlab/types.ts index 0d8d9baf0..7d2699c75 100644 --- a/frontend/src/hooks/api/appConnections/gitlab/types.ts +++ b/frontend/src/hooks/api/appConnections/gitlab/types.ts @@ -10,5 +10,6 @@ export type TGitLabGroup = { export enum GitLabAccessTokenType { Personal = "personal", - Project = "project" + Project = "project", + Group = "group" } diff --git a/frontend/src/hooks/api/ca/constants.tsx b/frontend/src/hooks/api/ca/constants.tsx index 778656da4..16a350dbd 100644 --- a/frontend/src/hooks/api/ca/constants.tsx +++ b/frontend/src/hooks/api/ca/constants.tsx @@ -53,6 +53,6 @@ export const getCaStatusBadgeVariant = (status: CaStatus | SshCaStatus | SshCert case CaStatus.DISABLED: return "danger"; default: - return "primary"; + return "warning"; } }; diff --git a/frontend/src/hooks/api/migration/queries.tsx b/frontend/src/hooks/api/migration/queries.tsx index e4ce6824d..198bf887b 100644 --- a/frontend/src/hooks/api/migration/queries.tsx +++ b/frontend/src/hooks/api/migration/queries.tsx @@ -5,7 +5,8 @@ import { apiRequest } from "@app/config/request"; import { ExternalMigrationProviders, TVaultExternalMigrationConfig, - VaultKubernetesAuthRole + VaultKubernetesAuthRole, + VaultKubernetesRole } from "./types"; export const externalMigrationQueryKeys = { @@ -31,6 +32,11 @@ export const externalMigrationQueryKeys = { "vault-kubernetes-auth-roles", namespace, mountPath + ], + vaultKubernetesRoles: (namespace?: string, mountPath?: string) => [ + "vault-kubernetes-roles", + namespace, + mountPath ] }; @@ -172,3 +178,30 @@ export const useGetVaultKubernetesAuthRoles = ( enabled: enabled && !!namespace && !!mountPath }); }; + +export const useGetVaultKubernetesRoles = ( + enabled = true, + namespace?: string, + mountPath?: string +) => { + return useQuery({ + queryKey: externalMigrationQueryKeys.vaultKubernetesRoles(namespace, mountPath), + queryFn: async () => { + if (!namespace || !mountPath) { + throw new Error("Both namespace and mountPath are required"); + } + + const { data } = await apiRequest.get<{ + roles: VaultKubernetesRole[]; + }>("/api/v3/external-migration/vault/kubernetes-roles", { + params: { + namespace, + mountPath + } + }); + + return data.roles; + }, + enabled: enabled && !!namespace && !!mountPath + }); +}; diff --git a/frontend/src/hooks/api/migration/types.ts b/frontend/src/hooks/api/migration/types.ts index f4303ea26..4c101548c 100644 --- a/frontend/src/hooks/api/migration/types.ts +++ b/frontend/src/hooks/api/migration/types.ts @@ -49,3 +49,24 @@ export type VaultKubernetesAuthRole = { disable_local_ca_jwt?: boolean; }; }; + +export type VaultKubernetesRole = { + name: string; + mountPath: string; + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + config: { + kubernetes_host: string; + kubernetes_ca_cert?: string; + }; +}; diff --git a/frontend/src/hooks/api/pkiSubscriber/constants.tsx b/frontend/src/hooks/api/pkiSubscriber/constants.tsx index 1de5e9ddb..768df9f52 100644 --- a/frontend/src/hooks/api/pkiSubscriber/constants.tsx +++ b/frontend/src/hooks/api/pkiSubscriber/constants.tsx @@ -15,6 +15,6 @@ export const getPkiSubscriberStatusBadgeVariant = (status: PkiSubscriberStatus) case PkiSubscriberStatus.DISABLED: return "danger"; default: - return "primary"; + return "warning"; } }; diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx index f9ef51133..014c77baa 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx @@ -7,15 +7,12 @@ import { faCaretDown, faCheck, faChevronRight, - faCubes, faEnvelope, faExclamationTriangle, - faGlobe, faInfinity, faInfo, faInfoCircle, faPlus, - faServer, faSignOut, faToolbox, faUser, @@ -30,7 +27,6 @@ import { Mfa } from "@app/components/auth/Mfa"; import { createNotification } from "@app/components/notifications"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { - Badge, BreadcrumbContainer, Button, DropdownMenu, @@ -46,6 +42,7 @@ import { TBreadcrumbFormat, Tooltip } from "@app/components/v2"; +import { Badge, InstanceIcon, OrgIcon, SubOrgIcon } from "@app/components/v3"; import { envConfig } from "@app/config/env"; import { useOrganization, useSubscription, useUser } from "@app/context"; import { isInfisicalCloud } from "@app/helpers/platform"; @@ -271,9 +268,7 @@ export const Navbar = () => { to="/admin" className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary" > -
- -
+
Server Console

/

@@ -288,24 +283,31 @@ export const Navbar = () => {
{ - navigate({ - to: "/organization/projects", - search: (search) => ({ ...search, subOrganization: undefined }) - }); - if (isSubOrganization) { - await router.invalidate({ sync: true }).catch(() => null); - } - }} + asChild variant="org" + isTruncatable + // TODO(scott): either add badge size/style variant or create designated component for namespace/org nav bar className={twMerge( - "max-w-full min-w-0 cursor-pointer text-sm", + "gap-x-1.5 text-sm", (!isOrgScope || isSubOrganization) && - "bg-transparent text-mineshaft-200 hover:bg-transparent hover:underline" + "bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-org" )} > - -

{currentOrg?.name}

+
{getPlan(subscription)} @@ -443,19 +445,22 @@ export const Navbar = () => { <>

/

- - - -

{currentOrg.subOrganization.name}

-
- + svg]:!text-sub-org" + )} + > + + + {currentOrg.subOrganization.name} + +
{ )} {user.superAdmin && !location.pathname.startsWith("/admin") && ( - + Server Console )} diff --git a/frontend/src/layouts/PamLayout/PamLayout.tsx b/frontend/src/layouts/PamLayout/PamLayout.tsx index f34c29d0d..5b74703dc 100644 --- a/frontend/src/layouts/PamLayout/PamLayout.tsx +++ b/frontend/src/layouts/PamLayout/PamLayout.tsx @@ -18,7 +18,10 @@ export const PamLayout = () => { useEffect(() => { if (subscription && !subscription.pam) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + description: "You can use PAM if you switch to Infisical's Enterprise plan.", + isEnterpriseFeature: true + }); } }, [subscription]); @@ -111,7 +114,8 @@ export const PamLayout = () => { onOpenChange={(isOpen) => { handlePopUpToggle("upgradePlan", isOpen); }} - text="You can use PAM if you switch to a paid Infisical plan." + text={popUp.upgradePlan.data?.description} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx index 72430fec6..c26333332 100644 --- a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx +++ b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx @@ -3,7 +3,6 @@ import { faStar } from "@fortawesome/free-regular-svg-icons"; import { faCaretDown, faCheck, - faCube, faMagnifyingGlass, faPlus, faStar as faSolidStar @@ -16,7 +15,6 @@ import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { NewProjectModal } from "@app/components/projects"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -25,6 +23,7 @@ import { Input, Tooltip } from "@app/components/v2"; +import { Badge, ProjectIcon } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, @@ -120,11 +119,9 @@ export const ProjectSelect = () => {

{currentWorkspace?.name}

- - - - {currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"} - + + + {currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"} diff --git a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx index f3737a2dc..47a2333c7 100644 --- a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx +++ b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx @@ -4,7 +4,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { Badge, Tab, TabList, Tabs } from "@app/components/v2"; +import { Tab, TabList, Tabs } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject, useProjectPermission } from "@app/context"; import { useGetAccessRequestsCount, @@ -88,7 +89,7 @@ export const SecretManagerLayout = () => { {Boolean( secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount ) && ( - + {pendingRequestsCount} )} diff --git a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx index 227f7ae70..0a1fd14f4 100644 --- a/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx +++ b/frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx @@ -1,7 +1,8 @@ import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { motion } from "framer-motion"; -import { Badge, Tab, TabList, Tabs } from "@app/components/v2"; +import { Tab, TabList, Tabs } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub, useProject, @@ -66,7 +67,7 @@ export const SecretScanningLayout = () => { Findings {Boolean(unresolvedFindings) && ( - + {unresolvedFindings} )} diff --git a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx index 5b34aa029..7e207fbc5 100644 --- a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx +++ b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx @@ -7,16 +7,15 @@ import { faTrash, faUsers, faUserXmark, - faWarning, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AlertTriangleIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { - Badge, Button, Checkbox, DeleteActionModal, @@ -38,6 +37,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useSubscription, useUser } from "@app/context"; import { getUserTablePreference, @@ -451,7 +451,7 @@ export const ServerAdminsTable = () => { const email = user.email ?? user.username; return (
  • -
    +

    {user.firstName || user.lastName ? ( <> @@ -464,15 +464,10 @@ export const ServerAdminsTable = () => {

    {userId === user.id && ( -
    - - - Deleting Yourself - -
    + + + Deleting Yourself +
    )}
    diff --git a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx index cac64d135..fbf956b7b 100644 --- a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx +++ b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; -import { faRotate } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { RefreshCwIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; -import { Badge, Button, DeleteActionModal } from "@app/components/v2"; +import { Button, DeleteActionModal } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useUser } from "@app/context"; import { usePopUp } from "@app/hooks"; import { useInvalidateCache } from "@app/hooks/api"; @@ -62,11 +62,8 @@ export const CachingPageForm = () => {
    Secrets Cache {isInvalidating && ( - - + + Invalidating Cache )} diff --git a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx index 69f4139a2..8372dd557 100644 --- a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx +++ b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx @@ -1,13 +1,13 @@ import { useCallback } from "react"; import { Controller, useForm } from "react-hook-form"; -import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import { InfoIcon } from "lucide-react"; import { z } from "zod"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; -import { Badge, Button, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { Button, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useServerConfig, useSubscription } from "@app/context"; import { usePopUp } from "@app/hooks"; import { @@ -54,6 +54,7 @@ export const EncryptionPageForm = () => { if (!subscription.hsm) { handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, description: "Hardware Security Module's (HSM's), are only available on Enterprise plans." }); return; @@ -132,12 +133,10 @@ export const EncryptionPageForm = () => { {config.fipsEnabled && ( -
    - - FIPS Mode: Enabled - - -
    + + FIPS Mode: Enabled + +
    )}
    @@ -146,6 +145,7 @@ export const EncryptionPageForm = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text={(popUp.upgradePlan?.data as { description: string })?.description} + isEnterpriseFeature={popUp.upgradePlan?.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx index 59df496af..5f74593a3 100644 --- a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx +++ b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx @@ -1,8 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Control, Controller, useForm, useWatch } from "react-hook-form"; import { - faArrowUpRightFromSquare, - faBookOpen, faChevronRight, faExclamationTriangle, faMagnifyingGlass @@ -14,6 +12,7 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input, SecretInput, Tooltip } from "@app/components/v2"; import { HighlightText } from "@app/components/v2/HighlightText"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { useGetEnvOverrides, useUpdateServerConfig } from "@app/hooks/api"; type TForm = Record; @@ -213,22 +212,9 @@ export const EnvironmentPageForm = () => { >
    -
    +

    Overrides

    - -
    - - Docs - -
    -
    +

    Override specific environment variables. After saving, it may take up to 5 minutes for diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx index c493fe24d..9929d7500 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx @@ -7,10 +7,10 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ServerCogIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; import { - Badge, DeleteActionModal, DropdownMenu, DropdownMenuContent, @@ -29,6 +29,7 @@ import { THead, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { getUserTablePreference, PreferenceKey, @@ -110,7 +111,8 @@ const IdentityPanelTable = ({ {name} {isInstanceAdmin && ( - + + Server Admin )} diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx index 9405505c8..fbb9462d0 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx @@ -3,7 +3,6 @@ import { faArrowDown, faArrowUp, faBuilding, - faCircleQuestion, faEllipsisV, faEnvelope, faEye, @@ -19,11 +18,11 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; +import { CircleQuestionMarkIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { - Badge, Button, DeleteActionModal, DropdownMenu, @@ -46,6 +45,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useUser } from "@app/context"; import { OrgMembershipRole } from "@app/helpers/roles"; import { @@ -295,15 +295,17 @@ const ViewMembersModalContent = ({ )}

    - - -

    {member.role.replace("-", " ")}

    - {Boolean(member.roleId) && ( - - - - )} -
    + +
    + + + {member.role.replace("-", " ")} + {Boolean(member.roleId) && } + + +
    diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx index 7b5b2c3d5..05d07d426 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx @@ -9,16 +9,15 @@ import { faUsers, faUserShield, faUserXmark, - faWarning, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AlertTriangleIcon, UserCogIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { - Badge, Button, Checkbox, DeleteActionModal, @@ -41,6 +40,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useSubscription, useUser } from "@app/context"; import { getUserTablePreference, @@ -218,7 +218,8 @@ const UserPanelTable = ({ {name ?? Not Set}

    {superAdmin && ( - + + Server Admin )} @@ -548,7 +549,7 @@ export const UserIdentitiesTable = () => { const email = user.email ?? user.username; return (
  • -
    +

    {user.firstName || user.lastName ? ( <> @@ -561,15 +562,10 @@ export const UserIdentitiesTable = () => {

    {userId === user.id && ( -
    - - - Deleting Yourself - -
    + + + Deleting Yourself +
    )}
    diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx index e9f65c4f6..c04b8525a 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaCertificatesSection/CaCertificatesTable.tsx @@ -7,7 +7,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -22,6 +21,7 @@ import { THead, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useGetCaCerts } from "@app/hooks/api"; @@ -60,7 +60,7 @@ export const CaCertificatesTable = ({ caId }: Props) => {
    CA Certificate {caCert.version} {isLastItem && ( - + Current )} diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx index 81fce98c6..09fe39af6 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx @@ -6,7 +6,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -22,6 +21,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useListCasByTypeAndProjectId } from "@app/hooks/api"; import { diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx index a948473e8..0f3fca6cd 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx @@ -10,7 +10,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -26,6 +25,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { CaStatus, CaType, useListExternalCasByProjectId } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx index c4d25a6ad..629553200 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx @@ -104,6 +104,7 @@ export const CertificateTemplatesSection = ({ caId }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} text="Managing template enrollment options for EST is only available on Infisical's Enterprise plan." />
    diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx index e7d137807..c36618309 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx @@ -36,6 +36,7 @@ type Props = { data?: { id?: string; name?: string; + isEnterpriseFeature?: boolean; } ) => void; }; @@ -90,7 +91,9 @@ export const CertificateTemplatesTable = ({ handlePopUpOpen, caId }: Props) => { { if (!subscription?.pkiEst) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx index 0550746b4..500286d67 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx @@ -5,18 +5,17 @@ import { faEllipsis, faEye, faFileExport, - faQuestionCircle, faRedo, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; +import { CircleQuestionMarkIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -33,6 +32,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionCertificateActions, ProjectPermissionSub, @@ -58,7 +58,7 @@ const isExpiringWithinOneDay = (notAfter: string): boolean => { const getAutoRenewalInfo = (certificate: TCertificate) => { if (certificate.renewedByCertificateId) { - return { text: "Renewed", variant: "instance" as const }; + return { text: "Renewed", variant: "neutral" as const }; } const isRevoked = certificate.status === CertStatus.REVOKED; @@ -69,7 +69,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { if (isRevoked) { return { text: "Not Available", - variant: "instance" as const, + variant: "neutral" as const, tooltip: "Renewal is not available for revoked certificates" }; } @@ -77,7 +77,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { if (isExpired) { return { text: "Not Available", - variant: "instance" as const, + variant: "neutral" as const, tooltip: "Renewal is not available for expired certificates" }; } @@ -85,7 +85,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { if (hasNoProfile) { return { text: "Not Available", - variant: "instance" as const, + variant: "neutral" as const, tooltip: "Renewal requires a certificate profile" }; } @@ -93,7 +93,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { if (certificate.hasPrivateKey === false) { return { text: "Not Available", - variant: "instance" as const, + variant: "neutral" as const, tooltip: "Renewal is not available for certificates with externally generated private keys" }; } @@ -101,7 +101,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { if (isExpiringWithinDay) { return { text: "Not Available", - variant: "instance" as const, + variant: "neutral" as const, tooltip: "Auto-renewal is not available for certificates expiring within 24 hours" }; } @@ -115,7 +115,7 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { } if (!certificate.renewBeforeDays) { - return { text: "Auto-Renewal Disabled", variant: "primary" as const }; + return { text: "Auto-Renewal Disabled", variant: "warning" as const }; } const notAfterDate = new Date(certificate.notAfter); @@ -133,11 +133,11 @@ const getAutoRenewalInfo = (certificate: TCertificate) => { ); if (daysUntilRenewal === 0) { - return { text: "Renews today", variant: "primary" as const }; + return { text: "Renews today", variant: "warning" as const }; } if (daysUntilRenewal <= 7) { - return { text: `Renews in ${daysUntilRenewal}d`, variant: "primary" as const }; + return { text: `Renews in ${daysUntilRenewal}d`, variant: "warning" as const }; } return { text: `Renews in ${daysUntilRenewal}d`, variant: "success" as const }; @@ -279,16 +279,12 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { {autoRenewalInfo && (autoRenewalInfo.tooltip ? (
    - - {autoRenewalInfo.text} - - - - + + + {autoRenewalInfo.text} + + +
    ) : ( {autoRenewalInfo.text} diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.utils.ts b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.utils.ts index 93b408bbe..2ac7ec9ce 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.utils.ts +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.utils.ts @@ -5,13 +5,13 @@ export const getCertValidUntilBadgeDetails = (notAfter: string) => { const notAfterDate = new Date(notAfter).getTime(); const diffInMs = notAfterDate - currentDate; - let variant: "success" | "primary" | "danger" = "success"; + let variant: "success" | "warning" | "danger" = "success"; let label = "Healthy"; if (diffInMs > ms("60d")) { variant = "success"; } else if (diffInMs > ms("30d")) { - variant = "primary"; + variant = "warning"; } else { variant = "danger"; } diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx index eb16166fc..1c5bff98c 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx @@ -1,7 +1,6 @@ import { useCallback, useMemo } from "react"; import { subject } from "@casl/ability"; import { - faBan, faCalendarCheck, faCheck, faCopy, @@ -19,6 +18,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { format } from "date-fns"; +import { BanIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; @@ -29,7 +29,6 @@ import { PkiSyncStatusBadge } from "@app/components/pki-syncs"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -39,6 +38,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; @@ -172,10 +172,7 @@ export const PkiSyncRow = ({
    - + Source Deleted @@ -234,12 +231,10 @@ export const PkiSyncRow = ({ className="text-xs" content="Auto-Sync is disabled. Certificate changes in the PKI subscriber will not be automatically synced to the destination." > -
    - - - {!syncStatus && "Auto-Sync Disabled"} - -
    + + + {!syncStatus && "Auto-Sync Disabled"} + )} {syncOption?.canImportCertificates && } diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncsTab.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncsTab.tsx index 564eeb789..1c1141382 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncsTab.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncsTab.tsx @@ -1,11 +1,12 @@ import { useCallback, useEffect, useMemo } from "react"; -import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreatePkiSyncModal } from "@app/components/pki-syncs"; import { Button, Spinner } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; @@ -64,21 +65,10 @@ export const PkiSyncsTab = () => {
    -

    Certificate Syncs

    - -
    - - Docs - -
    -
    +
    +

    Certificate Syncs

    + +

    Use App Connections to sync certificates to third-party services. diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx index d3533d71d..129af29d3 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberCertificatesTable.tsx @@ -7,7 +7,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -24,6 +23,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, @@ -93,7 +93,7 @@ export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen } if (daysUntilExpiry < 30) { - return Expiring Soon; + return Expiring Soon; } return Valid; diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx index d28c2d3a2..89dfd5f02 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscribersTable.tsx @@ -11,7 +11,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -27,6 +26,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx index 462678280..bb8e9d02a 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx @@ -1,7 +1,6 @@ import { useCallback } from "react"; import { subject } from "@casl/ability"; import { - faBan, faCheck, faCopy, faDownload, @@ -15,6 +14,7 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; +import { BanIcon, RefreshCwIcon } from "lucide-react"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; @@ -26,7 +26,6 @@ import { PkiSyncRemoveStatusBadge } from "@app/components/pki-syncs"; import { - Badge, Button, DropdownMenu, DropdownMenuContent, @@ -35,6 +34,7 @@ import { IconButton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; @@ -136,24 +136,19 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => { {syncOption?.canImportCertificates && } {pkiSync.isAutoSyncEnabled ? ( - - - Auto-Sync Enabled + + + Auto-Sync Enabled ) : ( -

    - - - Auto-Sync Disabled - -
    + + + Auto-Sync Disabled + )}
    diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx index 3af7bf189..b6365f1cd 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx @@ -4,7 +4,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, IconButton } from "@app/components/v2"; +import { IconButton } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; import { TPkiSync } from "@app/hooks/api/pkiSyncs"; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncSourceSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncSourceSection.tsx index 5885795e2..c9dc041df 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncSourceSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncSourceSection.tsx @@ -1,10 +1,12 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ import { subject } from "@casl/ability"; -import { faEdit, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AlertTriangleIcon } from "lucide-react"; import { ProjectPermissionCan } from "@app/components/permissions"; -import { Badge, IconButton, Tooltip } from "@app/components/v2"; +import { IconButton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; import { TPkiSync } from "@app/hooks/api/pkiSyncs"; @@ -36,15 +38,10 @@ export const PkiSyncSourceSection = ({ pkiSync, onEditSource }: Props) => {
    {!subscriberId && ( -
    - - - Source Deleted - -
    + + + Source Deleted +
    )} diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx index 440ebb481..b395d0ec2 100644 --- a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx @@ -203,7 +203,9 @@ export const PkiTemplateListPage = () => { onClick={(e) => { e.stopPropagation(); if (!subscription.pkiEst) { - handlePopUpOpen("estUpgradePlan"); + handlePopUpOpen("estUpgradePlan", { + isEnterpriseFeature: true + }); return; } handlePopUpOpen("enrollmentOptions", { @@ -296,6 +298,7 @@ export const PkiTemplateListPage = () => { 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." + isEnterpriseFeature={popUp.estUpgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx index 27a1e7cd1..ab7eb549e 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx @@ -12,7 +12,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -21,6 +20,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProjectPermission } from "@app/context"; import { ProjectPermissionActions, @@ -45,42 +45,26 @@ const MetricsBadges = ({ }; }) => { if (!metrics) { - return ( - - No metrics - - ); + return No metrics; } if (metrics.totalCertificates === 0) { - return ( - - No certificates - - ); + return No certificates; } return ( <> {metrics.activeCertificates > 0 && ( - - {metrics.activeCertificates} active - + {metrics.activeCertificates} active )} {metrics.expiringCertificates > 0 && ( - - {metrics.expiringCertificates} expiring - + {metrics.expiringCertificates} expiring )} {metrics.expiredCertificates > 0 && ( - - {metrics.expiredCertificates} expired - + {metrics.expiredCertificates} expired )} {metrics.revokedCertificates > 0 && ( - - {metrics.revokedCertificates} revoked - + {metrics.revokedCertificates} revoked )} ); @@ -135,7 +119,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = const getEnrollmentTypeBadge = (enrollmentType: string) => { const config = { api: { variant: "success" as const, label: "API" }, - est: { variant: "primary" as const, label: "EST" } + est: { variant: "warning" as const, label: "EST" } } as const; const configKey = Object.keys(config).includes(enrollmentType) diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx index 9ba85beef..474ad158a 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx @@ -170,7 +170,9 @@ export const KmipClientTable = () => { leftIcon={} onClick={() => { if (subscription && !subscription.kmip) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -343,6 +345,7 @@ export const KmipClientTable = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="KMIP requires an enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx index bf721edfb..a6675ea88 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx @@ -23,7 +23,6 @@ import { motion } from "framer-motion"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, Button, DropdownMenu, DropdownMenuContent, @@ -44,7 +43,7 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { BadgeProps } from "@app/components/v2/Badge/Badge"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionCmekActions, @@ -72,7 +71,7 @@ import { DeleteCmekModal } from "./DeleteCmekModal"; const getStatusBadgeProps = ( isDisabled: boolean -): { variant: BadgeProps["variant"]; label: string } => { +): { variant: TBadgeProps["variant"]; label: string } => { if (isDisabled) { return { variant: "danger", diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx index 4163beb5a..5c9eac072 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx @@ -7,7 +7,6 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { decodeBase64 } from "@app/components/utilities/cryptography/crypto"; import { - Badge, Button, FormControl, Modal, @@ -19,6 +18,7 @@ import { TextArea, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { SigningAlgorithm, TCmek, useCmekVerify } from "@app/hooks/api/cmeks"; import { isBase64 } from "@app/lib/fn/base64"; @@ -112,30 +112,22 @@ const VerifyForm = ({ cmek }: FormProps) => {
    Signature Status: - - - {signatureValid ? ( -
    -

    Valid

    -
    - ) : ( -
    -

    Invalid

    -
    - )} -
    -
    + + + {signatureValid ? "Valid" : "Invalid"} + +
    Signing Algorithm: - {signingAlgorithm} + {signingAlgorithm}
    Signature:{" "} diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx index 753ebd93c..88cbc17f0 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -5,6 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import { useDeleteGroup } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -27,7 +28,8 @@ export const OrgGroupsSection = () => { if (!subscription?.groups) { handlePopUpOpen("upgradePlan", { description: - "You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license." + "You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license.", + isEnterpriseFeature: true }); } else { handlePopUpOpen("group"); @@ -57,7 +59,10 @@ export const OrgGroupsSection = () => { return (
    -

    Groups

    +
    +

    Groups

    + +
    {(isAllowed) => (
    diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx index 57663f441..752ab1a34 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx @@ -4,7 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { Badge, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { IdentityAuthMethod } from "@app/hooks/api/identities"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -277,7 +278,7 @@ export const IdentityAuthMethodModalContent = ({ > {label}{" "} {alreadyConfigured && !isSelectedAuthAlreadyConfigured && ( - Configured + Configured )} @@ -291,7 +292,8 @@ export const IdentityAuthMethodModalContent = ({ handlePopUpToggle("upgradePlan", isOpen)} - text="You can use IP allowlisting if you switch to Infisical's Pro plan." + text={`You can use ${popUp.upgradePlan.data?.featureName ?? "IP allowlisting"} if you switch to Infisical's ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 3cfe1c355..9b8624990 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -22,11 +22,12 @@ import { TextArea, Tooltip } from "@app/components/v2"; -import { useOrganization, useSubscription } from "@app/context"; +import { useOrganization, useOrgPermission, useSubscription } from "@app/context"; import { OrgGatewayPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext/types"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { gatewaysQueryKeys, useAddIdentityKubernetesAuth, @@ -129,6 +130,8 @@ export const IdentityKubernetesAuthForm = ({ ] as const); const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs(); const hasVaultConnection = vaultConfigs.some((config) => config.connectionId); + const { hasOrgRole } = useOrgPermission(); + const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const { control, @@ -409,20 +412,29 @@ export const IdentityKubernetesAuthForm = ({ Load values from HashiCorp Vault
    - + +
    )}
    diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx index 878e65cfc..b2096fa06 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx @@ -147,7 +147,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { isEnterpriseFeature?: boolean; featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -304,7 +307,10 @@ export const IdentityLdapAuthForm = ({ useEffect(() => { if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + featureName: "LDAP authentication" + }); handlePopUpToggle("identityAuthMethod", false); } }, [subscription, handlePopUpOpen, handlePopUpToggle]); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx index 5c15a0cdd..355dec803 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx @@ -1,15 +1,11 @@ -import { - faArrowUpRightFromSquare, - faBookOpen, - faLink, - faPlus -} from "@fortawesome/free-solid-svg-icons"; +import { faLink, faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, Modal, ModalContent } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { OrgPermissionIdentityActions, OrgPermissionSubjects, @@ -113,22 +109,9 @@ export const IdentitySection = withPermission(
    -
    +

    Identities

    - -
    - - Docs - -
    -
    +
    {isSubOrganization && (
    -
    +

    Identity Auth Templates

    - -
    - - Docs - -
    -
    +
    } onClick={() => { if (subscription && !subscription.machineIdentityAuthTemplates) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + description: + "You can use Identity Auth Templates if you switch to Infisical's Enterprise plan." + }); return; } handlePopUpOpen("createTemplate"); @@ -279,7 +253,8 @@ export const IdentitySection = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text="You can use Identity Auth Templates if you switch to Infisical's Enterprise plan." + text={popUp.upgradePlan.data?.description} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index a8f5ea059..adb729d75 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; -import { faBan, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { BanIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { - Badge, Button, DeleteActionModal, EmailServiceSetupModal, @@ -15,6 +15,7 @@ import { ModalContent, Tooltip } from "@app/components/v2"; +import { Badge, DocumentationLinkBadge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, @@ -75,7 +76,7 @@ export const OrgMembersSection = () => { if (!isMoreIdentitiesAllowed && !isEnterprise) { handlePopUpOpen("upgradePlan", { - description: "You can add more members if you upgrade your Infisical plan." + description: "You can add more members if you switch to Infisical's Pro plan." }); return; } @@ -207,7 +208,10 @@ export const OrgMembersSection = () => {
    -

    Users

    +
    +

    Users

    + +
    {(isAllowed) => (
    diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index 8e5d223ca..68a4e795a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -11,19 +11,18 @@ import { faMagnifyingGlass, faSearch, faUsers, - faUserShield, faUserSlash, faUserXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; +import { UserCogIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { LastLoginSection } from "@app/components/organization/LastLoginSection"; import { OrgPermissionCan } from "@app/components/permissions"; import { - Badge, Button, Checkbox, DropdownMenu, @@ -50,6 +49,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, @@ -133,7 +133,8 @@ export const OrgMembersTable = ({ if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { - description: "You can assign custom roles to members if you upgrade your Infisical plan." + description: + "You can assign custom roles to members if you switch to Infisical's Pro plan." }); return; } @@ -519,17 +520,17 @@ export const OrgMembersTable = ({ -
    +

    {name ?? Not Set}

    {u.superAdmin && ( - - Server Admin - - - - + + + + Server Admin + + )} {lastLoginAuthMethod && lastLoginTime && ( { if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { description: - "You can set the default org role to a custom role if you upgrade your Infisical plan." + "You can set the default org role to a custom role if you switch to Infisical's Pro plan." }); return; } @@ -200,9 +201,12 @@ export const OrgRoleTable = () => { return (
    -

    - {isSubOrganization ? "Sub-" : ""}Organization Roles -

    +
    +

    + {isSubOrganization ? "Sub-" : ""}Organization Roles +

    + +
    {(isAllowed) => (
    ); diff --git a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx index d7dcfcb3e..cf60ffa59 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { faArrowDownAZ, faBorderAll, - faCheck, faCheckCircle, faFolderOpen, faList, @@ -11,13 +10,13 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; +import { CheckIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { RequestProjectAccessModal } from "@app/components/projects/RequestProjectAccessModal"; import { - Badge, Button, DropdownMenu, DropdownMenuContent, @@ -31,6 +30,7 @@ import { Skeleton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionAdminConsoleAction } from "@app/context/OrgPermissionContext/types"; import { getProjectHomePage, getProjectLottieIcon, getProjectTitle } from "@app/helpers/project"; @@ -308,9 +308,9 @@ export const AllProjectView = ({
    {workspace.isMember ? ( - - - Joined + + + Joined ) : ( Expired ) : ( - + {row.encryptedSecret ? "Secret Provided" : "Pending Secret"} )} - - -
    - {row.accessType === SecretSharingAccessType.Anyone - ? "Anyone" - : "Organization Members"} -
    -
    -
    + + + {row.accessType === SecretSharingAccessType.Anyone ? "Anyone" : "Organization Members"} + + {`${format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}`} {row.expiresAt ? format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a") : "-"} diff --git a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretsRow.tsx b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretsRow.tsx index 2394253e7..d4b40723b 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretsRow.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretsRow.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; import { IconButton, Td, Tooltip, Tr } from "@app/components/v2"; -import { Badge } from "@app/components/v2/Badge"; +import { Badge } from "@app/components/v3"; import { TSharedSecret } from "@app/hooks/api/secretSharing"; import { UsePopUpState } from "@app/hooks/usePopUp"; diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx index f4f7932a4..2ccdfad61 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx @@ -29,7 +29,9 @@ export const AuditLogStreamsTab = withPermission(
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/AuditLogStreamHeader.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/AuditLogStreamHeader.tsx index 1d5cbc4f0..2d473892a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/AuditLogStreamHeader.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/AuditLogStreamHeader.tsx @@ -1,6 +1,6 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { AUDIT_LOG_STREAM_PROVIDER_MAP } from "@app/helpers/auditLogStreams"; import { LogProvider } from "@app/hooks/api/auditLogStreams/enums"; @@ -34,20 +34,9 @@ export const AuditLogStreamHeader = ({ provider, logStreamExists, onBack }: Prop )}
    -
    +
    {providerDetails.name} - -
    - - Docs - -
    -
    +

    {logStreamExists diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx index c8b206cc4..df57dab88 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx @@ -70,7 +70,9 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { type="button" onClick={() => { if (option.provider === LogProvider.QRadar) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); } else { onSelect(option.provider); } @@ -110,6 +112,7 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="This audit log stream provider requires an enterprise license." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />

    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx b/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx index 148b9f443..7203cb9ef 100644 --- a/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx @@ -180,7 +180,9 @@ const OrgConfigSection = ({ className="mt-2" onClick={() => { if (subscription && !subscription.kmip) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -251,6 +253,7 @@ const OrgConfigSection = ({ isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="KMIP requires an enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx index ef47f3195..251cebcbe 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx @@ -2,7 +2,8 @@ import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { createNotification } from "@app/components/notifications"; -import { Badge, Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; +import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useOrganization } from "@app/context"; import { useAddExternalKms, useUpdateExternalKms } from "@app/hooks/api"; import { @@ -244,7 +245,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { > {AWS_REGIONS.map((awsRegion) => ( - {awsRegion.name} {awsRegion.slug} + {awsRegion.name} {awsRegion.slug} ))} diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx index d080b57c6..44fc22331 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx @@ -27,6 +27,7 @@ type Props = { kmsId?: string; name?: string; provider?: string; + isEnterpriseFeature?: boolean; } ) => void; subscription: SubscriptionPlan; @@ -111,7 +112,9 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) = onClick={(e) => { e.stopPropagation(); if (subscription && !subscription?.externalKms) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx index 07711d306..610ac823e 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx @@ -3,7 +3,8 @@ import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { createNotification } from "@app/components/notifications"; -import { Badge, Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; +import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useOrganization } from "@app/context"; import { useAddExternalKms, @@ -71,7 +72,7 @@ const GCP_REGIONS = [ const formatOptionLabel = ({ value, label }: { value: string; label: string }) => (
    {label} - {value} + {value}
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx index c2031381a..a0b09b573 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx @@ -74,7 +74,9 @@ export const OrgEncryptionTab = withPermission(
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx index c8e6fbc3e..3936a20ee 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx @@ -29,7 +29,9 @@ export const OrgScimSection = () => { if (subscription?.scim) { handlePopUpOpen("scimToken"); } else { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); } }; @@ -37,7 +39,9 @@ export const OrgScimSection = () => { try { if (!currentOrg?.id) return; if (!subscription?.scim) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -90,7 +94,9 @@ export const OrgScimSection = () => { if (subscription?.scim) { handleEnableSCIMToggle(value); } else { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); } }} isChecked={currentOrg?.scimEnabled ?? false} @@ -112,6 +118,7 @@ export const OrgScimSection = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx index 817797eff..9d94d595c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx @@ -34,7 +34,9 @@ export const OrgLDAPSection = (): JSX.Element => { try { if (!currentOrg?.id) return; if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -78,7 +80,9 @@ export const OrgLDAPSection = (): JSX.Element => { handlePopUpOpen("addLDAP"); } else { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); } } catch (err) { console.error(err); @@ -87,7 +91,9 @@ export const OrgLDAPSection = (): JSX.Element => { const openLDAPGroupMapModal = () => { if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -186,6 +192,7 @@ export const OrgLDAPSection = (): JSX.Element => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="You can use LDAP authentication if you switch to Infisical's Enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx index ecaa06e05..5e9d4c211 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx @@ -27,7 +27,7 @@ export const OrgSSOSection = (): JSX.Element => { const { mutateAsync } = useUpdateSSOConfig(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "upgradePlan", - "upgradeEnterprisePlan", + // "upgradeEnterprisePlan", "addSSO" ] as const); @@ -38,7 +38,9 @@ export const OrgSSOSection = (): JSX.Element => { if (!currentOrg?.id) return; if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + description: "You can use SAML SSO if you switch to Infisical's Pro plan." + }); return; } @@ -65,7 +67,11 @@ export const OrgSSOSection = (): JSX.Element => { if (!currentOrg?.id) return; if (!subscription?.samlSSO || !subscription?.groups) { - handlePopUpOpen("upgradeEnterprisePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + description: + "You can use SAML group mapping if you switch to Infisical's Enterprise plan." + }); return; } @@ -105,7 +111,9 @@ export const OrgSSOSection = (): JSX.Element => { handlePopUpOpen("addSSO"); } else { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + description: "You can use SAML SSO if you switch to Infisical's Pro plan." + }); } } catch (err) { console.error(err); @@ -234,12 +242,8 @@ export const OrgSSOSection = (): JSX.Element => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use SAML SSO if you switch to Infisical's Pro plan." - /> - handlePopUpToggle("upgradeEnterprisePlan", isOpen)} - text="You can use SAML group mapping if you switch to Infisical's Enterprise plan." + text={popUp.upgradePlan.data?.description} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx index 5c4076159..7700d5c86 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx @@ -93,7 +93,7 @@ export const OrgSsoTab = withPermission( colorSchema="secondary" onClick={() => { if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan", { feature: "SAML SSO", plan: "Pro" }); + handlePopUpOpen("upgradePlan", { feature: "SAML SSO" }); return; } @@ -116,7 +116,7 @@ export const OrgSsoTab = withPermission( colorSchema="secondary" onClick={() => { if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan", { feature: "OIDC SSO", plan: "Pro" }); + handlePopUpOpen("upgradePlan", { feature: "OIDC SSO" }); return; } @@ -134,7 +134,10 @@ export const OrgSsoTab = withPermission( colorSchema="secondary" onClick={() => { if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan", { feature: "LDAP", plan: "Enterprise" }); + handlePopUpOpen("upgradePlan", { + feature: "LDAP", + isEnterpriseFeature: true + }); return; } @@ -208,8 +211,13 @@ export const OrgSsoTab = withPermission( text={`You can use ${ (popUp.upgradePlan.data as { feature: string })?.feature } if you switch to Infisical's ${ - (popUp.upgradePlan.data as { plan: string })?.plan + (popUp.upgradePlan.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature + ? "Enterprise" + : "Pro" } plan.`} + isEnterpriseFeature={ + (popUp.upgradePlan.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature + } /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModalHeader.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModalHeader.tsx index 8ad40ae4b..c244caa81 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModalHeader.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModalHeader.tsx @@ -1,5 +1,4 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DocumentationLinkBadge } from "@app/components/v3"; type ProviderDetails = { label: string; @@ -24,23 +23,9 @@ export const SSOModalHeader = ({ providerDetails, isConnected }: Props) => { className="h-12 w-12 rounded-md bg-bunker-500 p-2" />
    -
    +
    {providerDetails.label} - -
    - - Docs - -
    -
    +

    {isConnected diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab.tsx index bfdd27c02..2bf8fd5d3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab.tsx @@ -7,7 +7,6 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { - Badge, Button, DeleteActionModal, DropdownMenu, @@ -24,6 +23,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; @@ -48,7 +48,7 @@ const renderStatus = (status: WorkflowIntegrationStatus) => { } if (status === WorkflowIntegrationStatus.Pending) { - return Pending; + return Pending; } return Failed; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx index 8d524c41c..e76fea0a3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx @@ -1,10 +1,11 @@ import { useState } from "react"; -import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; import { TProjectTemplate } from "@app/hooks/api/projectTemplates"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -33,22 +34,9 @@ export const ProjectTemplatesSection = () => { project setup

    -
    +

    Project Templates

    - -
    - - Docs - -
    -
    + { leftIcon={} onClick={() => { if (!subscription?.projectTemplates) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -84,6 +74,7 @@ export const ProjectTemplatesSection = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="You can create project templates if you switch to Infisical's Enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx index 29b199a0b..59d83749c 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx @@ -149,7 +149,7 @@ export const UserOrgMembershipModal = ({ popUp, handlePopUpOpen, handlePopUpTogg if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { description: - "You can assign custom roles to members if you upgrade your Infisical plan." + "You can assign custom roles to members if you switch to Infisical's Pro plan." }); return; } diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx index dde2bb4bc..f9ce8f33b 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx @@ -1,23 +1,21 @@ import { useCallback } from "react"; import { - faBoxOpen, faCheck, faCopy, faEdit, faEllipsisV, - faFolder, faRightToBracket, faRotate, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { formatDistance } from "date-fns"; +import { FolderIcon, PackageOpenIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, Button, DropdownMenu, DropdownMenuContent, @@ -29,6 +27,7 @@ import { Tr } from "@app/components/v2"; import { HighlightText } from "@app/components/v2/HighlightText"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types"; import { useToggle } from "@app/hooks"; @@ -88,15 +87,15 @@ export const PamAccountRow = ({ - - + + {isFlatView && accountPath && ( - - + + @@ -108,14 +107,7 @@ export const PamAccountRow = ({ isDisabled={!account.lastRotationMessage} content={account.lastRotationMessage} > - + Rotated {formatDistance(new Date(), account.lastRotatedAt)} ago diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamResourceOption.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamResourceOption.tsx index 06c04e019..278ac3a62 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamResourceOption.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamResourceOption.tsx @@ -3,7 +3,7 @@ import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { PAM_RESOURCE_TYPE_MAP, PamResourceType } from "@app/hooks/api/pam"; export const PamResourceOption = ({ @@ -27,7 +27,7 @@ export const PamResourceOption = ({ <>

    {children}

    - + {`${name} - + diff --git a/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx b/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx index fe95a98aa..b0bba77bc 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx @@ -54,10 +54,10 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { const handleResourceSelect = (resource: PamResourceType) => { if (!subscription.pam) { - handlePopUpOpen( - "upgradePlan", - "PAM (Privileged Access Management) requires an enterprise plan." - ); + handlePopUpOpen("upgradePlan", { + description: "PAM (Privileged Access Management) requires an enterprise plan.", + isEnterpriseFeature: true + }); return; } @@ -67,10 +67,11 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { resource === PamResourceType.SSH || resource === PamResourceType.Kubernetes ) { - handlePopUpOpen( - "upgradePlan", - "This resource type requires a special license add-on to be enabled in your enterprise plan." - ); + handlePopUpOpen("upgradePlan", { + description: + "This resource type requires a special license add-on to be enabled in your enterprise plan.", + isEnterpriseFeature: true + }); return; } @@ -179,7 +180,8 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text={popUp.upgradePlan.data || ""} + text={popUp.upgradePlan.data?.description} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
    ); diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionDetailsSection.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionDetailsSection.tsx index c527c2716..f53803ce8 100644 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionDetailsSection.tsx +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionDetailsSection.tsx @@ -1,7 +1,9 @@ -import { faBoxOpen, faCheck, faCopy, faUser } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faCopy, faUser } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { PackageOpenIcon } from "lucide-react"; -import { Badge, IconButton, Tooltip } from "@app/components/v2"; +import { IconButton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useTimedReset } from "@app/hooks"; import { PAM_RESOURCE_TYPE_MAP, TPamSession } from "@app/hooks/api/pam"; @@ -76,8 +78,8 @@ export const PamSessionDetailsSection = ({ className="size-4" />

    {accountName}

    - - + + {resourceName}
    diff --git a/frontend/src/pages/pam/PamSessionsPage/components/PamSessionStatusBadge.tsx b/frontend/src/pages/pam/PamSessionsPage/components/PamSessionStatusBadge.tsx index bbdb3c391..1b347210e 100644 --- a/frontend/src/pages/pam/PamSessionsPage/components/PamSessionStatusBadge.tsx +++ b/frontend/src/pages/pam/PamSessionsPage/components/PamSessionStatusBadge.tsx @@ -1,45 +1,38 @@ import { - faBan, - faCircle, - faGavel, - faHourglass, - IconDefinition -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; + ActivityIcon, + BanIcon, + ChevronsLeftRightEllipsisIcon, + GavelIcon, + LucideIcon +} from "lucide-react"; -import { Badge } from "@app/components/v2"; +import { Badge, TBadgeProps } from "@app/components/v3"; import { PamSessionStatus } from "@app/hooks/api/pam"; interface StatusConfig { - bgColor: string; - textColor: string; - icon: IconDefinition; - iconClassName?: string; + variant: TBadgeProps["variant"]; + icon: LucideIcon; + className?: string; } const PAM_SESSION_STATUS_CONFIG: Record = { [PamSessionStatus.Active]: { - bgColor: "bg-green/20", - textColor: "text-green", - icon: faCircle, - iconClassName: "size-2 animate-pulse" + variant: "success", + icon: ActivityIcon, + className: "animate-pulse" }, [PamSessionStatus.Terminated]: { - bgColor: "bg-red/20", - textColor: "text-red", - icon: faGavel + variant: "danger", + icon: GavelIcon }, [PamSessionStatus.Starting]: { - bgColor: "bg-yellow/20", - textColor: "text-yellow", - icon: faHourglass, - iconClassName: "animate-spin" + variant: "info", + icon: ChevronsLeftRightEllipsisIcon, + className: "animate-pulse" }, [PamSessionStatus.Ended]: { - bgColor: "bg-bunker-300/20", - textColor: "text-bunker-300", - icon: faBan + variant: "neutral", + icon: BanIcon } }; @@ -48,15 +41,11 @@ export const PamSessionStatusBadge = ({ status }: { status: PamSessionStatus }) const displayName = status[0].toUpperCase() + status.slice(1); + const Icon = config.icon; + return ( - - + + {displayName} ); diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx index b2a2b7cf3..115be60fe 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx @@ -5,6 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -33,7 +34,8 @@ export const GroupsSection = () => { if (!subscription?.groups) { handlePopUpOpen("upgradePlan", { description: - "You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license." + "You can manage users more efficiently with groups if you switch to Infisical's Enterprise plan.", + isEnterpriseFeature: true }); } else { handlePopUpOpen("group"); @@ -68,7 +70,10 @@ export const GroupsSection = () => { return (
    -

    User Groups

    +
    +

    User Groups

    + +
    {(isAllowed) => (
    ); diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index 6e4ec70fe..17dbdca27 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -2,8 +2,6 @@ import { subject } from "@casl/ability"; import { faArrowDown, faArrowUp, - faArrowUpRightFromSquare, - faBookOpen, faCircleXmark, faClock, faEllipsisV, @@ -44,6 +42,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { getProjectBaseURL } from "@app/helpers/project"; import { formatProjectRoleName } from "@app/helpers/roles"; @@ -159,22 +158,9 @@ export const IdentityTab = withProjectPermission( return (
    -
    +

    Identities

    - -
    - - Docs - -
    -
    +

    {children}

    {props.data.isManagedByRootOrg && ( - - - Managed by Root Org + + + Organization )} {isSelected && ( diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx index 6c02ca5b3..5904f12fb 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx @@ -5,6 +5,7 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, @@ -57,7 +58,10 @@ export const MembersSection = () => { return (
    -

    Users

    +
    +

    Users

    + +
    {(isAllowed) => ( + + )} )} @@ -1214,7 +1233,11 @@ export const ActionBar = ({ environment={environment} secretPath={secretPath} projectId={projectId} - onUpgradePlan={() => handlePopUpOpen("upgradePlan")} + onUpgradePlan={() => + handlePopUpOpen("upgradePlan", { + feature: "Secret Imports" + }) + } isOpen={popUp.addSecretImport.isOpen} onClose={() => handlePopUpClose("addSecretImport")} onTogglePopUp={(isOpen) => handlePopUpToggle("addSecretImport", isOpen)} @@ -1277,13 +1300,7 @@ export const ActionBar = ({ isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text={ - subscription.slug === null - ? "You can perform this action under an Enterprise license" - : `You can perform this action if you switch to Infisical's ${ - popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro" - } plan` - } + text={`You can use ${popUp.upgradePlan.data?.feature} if you switch to Infisical's ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} /> )} 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -48,9 +47,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx index 9f9cc01aa..35a416224 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx @@ -95,9 +95,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -107,9 +106,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx index 05f85a9e6..581c473b9 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -49,9 +48,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: z .string() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx index 56a914977..aece11f38 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx @@ -75,8 +75,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -86,8 +86,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx index ef05d8a83..15f9387df 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx @@ -39,9 +39,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -51,9 +50,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx index 3cc41bf2c..17bfeddca 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx @@ -240,8 +240,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -251,8 +251,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx index 3a9b8729a..895b47475 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx @@ -13,16 +13,12 @@ import { } from "react-icons/si"; import { VscAzure } from "react-icons/vsc"; import { faAws, faGithub, faGoogle } from "@fortawesome/free-brands-svg-icons"; -import { - faArrowUpRightFromSquare, - faBookOpen, - faClock, - faDatabase -} from "@fortawesome/free-solid-svg-icons"; +import { faClock, faDatabase } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AnimatePresence, motion } from "framer-motion"; import { Modal, ModalContent } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { ProjectEnv } from "@app/hooks/api/types"; @@ -202,23 +198,11 @@ export const CreateDynamicSecretForm = ({ handleFormReset(state)}> +
    {modalTitle ? `${modalTitle} Dynamic Secret` : "Dynamic Secrets"} - -
    - - Docs - -
    -
    + />
    } subTitle="Configure dynamic secret parameters" @@ -234,11 +218,11 @@ export const CreateDynamicSecretForm = ({ exit={{ opacity: 0, translateX: -30 }} >
    Select a service to connect to:
    -
    +
    {DYNAMIC_SECRET_LIST.map(({ icon, provider, title }) => (
    { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index 31d68e877..1d0b6c268 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -60,9 +60,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -72,9 +71,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index 8e9206695..d15bcc9b8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -1,5 +1,6 @@ +import { useState } from "react"; import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form"; -import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle, faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -21,16 +22,22 @@ import { TextArea, Tooltip } from "@app/components/v2"; +import { useOrgPermission } from "@app/context"; import { OrgPermissionSubjects } from "@app/context/OrgPermissionContext"; import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders, KubernetesDynamicSecretCredentialType } from "@app/hooks/api/dynamicSecret/types"; +import { useGetVaultExternalMigrationConfigs } from "@app/hooks/api/migration/queries"; +import { VaultKubernetesRole } from "@app/hooks/api/migration/types"; import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; +import { VaultKubernetesImportModal } from "./VaultKubernetesImportModal"; + enum RoleType { ClusterRole = "cluster-role", Role = "role" @@ -99,8 +106,8 @@ const formSchema = z const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -110,8 +117,8 @@ const formSchema = z const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), @@ -162,11 +169,14 @@ export const KubernetesInputForm = ({ environments, isSingleEnvironmentMode }: Props) => { + const [isVaultImportModalOpen, setIsVaultImportModalOpen] = useState(false); + const { control, formState: { isSubmitting }, handleSubmit, - watch + watch, + setValue } = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -186,18 +196,88 @@ export const KubernetesInputForm = ({ } }); - const { fields, append, remove } = useFieldArray({ + const { fields, append, remove, replace } = useFieldArray({ control, name: "provider.audiences" }); const createDynamicSecret = useCreateDynamicSecret(); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); + const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs(); + const hasVaultConnection = vaultConfigs.some((config) => config.connectionId); + const { hasOrgRole } = useOrgPermission(); + const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const sslEnabled = watch("provider.sslEnabled"); const credentialType = watch("provider.credentialType"); const authMethod = watch("provider.authMethod"); + const handleVaultImport = (role: VaultKubernetesRole) => { + try { + setValue("name", role.name); + + setValue("provider.url", role.config.kubernetes_host); + + // Set CA certificate if available + if (role.config.kubernetes_ca_cert) { + setValue("provider.ca", role.config.kubernetes_ca_cert); + setValue("provider.sslEnabled", true); + } + + // Determine credential type based on role configuration + if (role.service_account_name) { + // Static credential type + setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Static); + setValue("provider.serviceAccountName", role.service_account_name); + + // Set namespace (single namespace for static) + if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) { + setValue("provider.namespace", role.allowed_kubernetes_namespaces[0]); + } + } else if (role.kubernetes_role_name) { + // Dynamic credential type + setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Dynamic); + setValue("provider.role", role.kubernetes_role_name); + + // Set role type + const roleType = + role.kubernetes_role_type === "ClusterRole" ? RoleType.ClusterRole : RoleType.Role; + setValue("provider.roleType", roleType); + + // Set allowed namespaces (comma-separated for dynamic) + if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) { + setValue("provider.namespace", role.allowed_kubernetes_namespaces.join(", ")); + } + } + + // Set TTLs if available + if (role.token_default_ttl) { + const defaultTTL = `${role.token_default_ttl}s`; + setValue("defaultTTL", defaultTTL); + } + + if (role.token_max_ttl) { + const maxTTL = `${role.token_max_ttl}s`; + setValue("maxTTL", maxTTL); + } + + // Set audiences if available + if (role.token_default_audiences && role.token_default_audiences.length > 0) { + replace(role.token_default_audiences); + } + + createNotification({ + type: "info", + text: "Configuration loaded successfully from HashiCorp Vault" + }); + } catch { + createNotification({ + type: "error", + text: "Failed to load configuration from HashiCorp Vault" + }); + } + }; + const handleCreateDynamicSecret = async (formData: TForm) => { const { provider, usernameTemplate, ...rest } = formData; // wait till previous request is finished @@ -235,6 +315,39 @@ export const KubernetesInputForm = ({ return (
    + {hasVaultConnection && ( +
    +
    + + Load values from HashiCorp Vault +
    + + + +
    + )} +
    -
    - Configuration +
    +

    Configuration

    @@ -654,6 +767,11 @@ export const KubernetesInputForm = ({ Cancel
    + ); }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index 824538706..7b7397625 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -61,9 +61,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -73,9 +72,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index e19c09576..3b0c7bb3b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -50,9 +50,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -62,9 +61,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx index 0cdb72e27..ac60a77b0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx @@ -40,9 +40,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -52,9 +51,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx index 137ee0d95..7b2e56464 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -44,9 +44,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -56,9 +55,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx index 940c43c9c..472d30783 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -49,9 +48,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx index b6909af2a..621451fda 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx @@ -35,9 +35,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -47,9 +46,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx index 66b974f66..034afee2c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -49,9 +48,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx index abbb115a3..11357e26a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx @@ -35,9 +35,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -47,9 +46,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx index 4119cf94d..0a836fec2 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx @@ -75,9 +75,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -87,9 +86,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: slugSchema(), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx new file mode 100644 index 000000000..3e9412ef9 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx @@ -0,0 +1,223 @@ +import { useEffect, useState } from "react"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FilterableSelect, + FormControl, + Modal, + ModalClose, + ModalContent +} from "@app/components/v2"; +import { + useGetVaultKubernetesRoles, + useGetVaultMounts, + useGetVaultNamespaces +} from "@app/hooks/api/migration/queries"; +import { VaultKubernetesRole } from "@app/hooks/api/migration/types"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onImport: (role: VaultKubernetesRole) => void; +}; + +type ContentProps = { + onClose: () => void; + onImport: (role: VaultKubernetesRole) => void; +}; + +const Content = ({ onClose, onImport }: ContentProps) => { + const [selectedNamespace, setSelectedNamespace] = useState(null); + const [selectedMountPath, setSelectedMountPath] = useState(null); + const [selectedRole, setSelectedRole] = useState(null); + const [shouldFetchRoles, setShouldFetchRoles] = useState(false); + const [shouldFetchMounts, setShouldFetchMounts] = useState(false); + + const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces(); + const { data: roles, isLoading: isLoadingRoles } = useGetVaultKubernetesRoles( + shouldFetchRoles, + selectedNamespace ?? undefined, + selectedMountPath ?? undefined + ); + const { data: mounts, isLoading: isLoadingMounts } = useGetVaultMounts( + shouldFetchMounts, + selectedNamespace ?? undefined + ); + + // Filter to only show Kubernetes mounts + const kubernetesMounts = mounts?.filter((mount) => mount.type === "kubernetes"); + + // Enable fetching mounts when namespace is selected + useEffect(() => { + if (selectedNamespace) { + setShouldFetchMounts(true); + } + }, [selectedNamespace]); + + // Enable fetching roles when both namespace and mount path are selected + useEffect(() => { + if (selectedNamespace && selectedMountPath) { + setShouldFetchRoles(true); + } else { + setShouldFetchRoles(false); + } + }, [selectedNamespace, selectedMountPath]); + + const handleImport = () => { + if (!selectedRole) { + createNotification({ + type: "error", + text: "Please select a Vault Kubernetes role to load" + }); + return; + } + + if (!selectedNamespace) { + createNotification({ type: "error", text: "Please select a namespace" }); + return; + } + + if (!mounts || mounts.length === 0) { + createNotification({ + type: "error", + text: "No Vault mounts found. Please ensure you have Kubernetes secrets engine configured." + }); + return; + } + + onImport(selectedRole); + onClose(); + }; + + return ( + <> +
    +
    + +
    +

    + Select a Kubernetes secrets engine role from Vault to pre-fill the form with its + configuration including cluster URL, CA certificate, TTL settings, etc. +

    +
    +
    +
    + + + <> + ns.name === selectedNamespace)} + onChange={(value) => { + if (value && !Array.isArray(value)) { + const namespace = value as { id: string; name: string }; + setSelectedNamespace(namespace.name); + setSelectedMountPath(null); + setSelectedRole(null); + } + }} + options={namespaces || []} + getOptionValue={(option) => option.name} + getOptionLabel={(option) => (option.name === "/" ? "root" : option.name)} + isDisabled={isLoadingNamespaces} + placeholder="Select namespace..." + className="w-full" + /> +

    + Select the Vault namespace to fetch available Kubernetes secrets engines +

    + +
    + + + <> + mount.path === selectedMountPath)} + onChange={(value) => { + if (value && !Array.isArray(value)) { + const mount = value as { path: string; type: string; version: string | null }; + setSelectedMountPath(mount.path.replace(/\/$/, "")); // Remove trailing slash + setSelectedRole(null); + } + }} + options={kubernetesMounts || []} + getOptionValue={(option) => option.path} + getOptionLabel={(option) => option.path.replace(/\/$/, "")} + isDisabled={isLoadingMounts || !kubernetesMounts?.length} + placeholder="Select Kubernetes secrets engine..." + className="w-full" + /> +

    + Choose a Kubernetes secrets engine mount to list available roles +

    + +
    + + + <> + { + if (value && !Array.isArray(value)) { + setSelectedRole(value as VaultKubernetesRole); + } else { + setSelectedRole(null); + } + }} + options={roles || []} + getOptionValue={(option) => option.name} + getOptionLabel={(option) => option.name} + isDisabled={isLoadingRoles || !roles?.length || !selectedMountPath} + placeholder={ + !selectedMountPath ? "Select a mount path first..." : "Select a role to load..." + } + isClearable + className="w-full" + /> +

    + Choose a Kubernetes role from the selected mount to load its configuration +

    + +
    + +
    + + + + +
    + + ); +}; + +export const VaultKubernetesImportModal = ({ isOpen, onOpenChange, onImport }: Props) => { + return ( + + + onOpenChange(false)} onImport={onImport} /> + + + ); +}; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx index 98cd41165..b48eab850 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx @@ -66,8 +66,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -77,8 +77,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"), environment: z.object({ name: z.string(), slug: z.string() }), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx index 08fe0153c..3579a826e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ReplicateFolderFromBoard/ReplicateFolderFromBoard.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; -import { faArrowUpRightFromSquare, faBookOpen, faClone } from "@fortawesome/free-solid-svg-icons"; +import { faClone } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; @@ -14,6 +14,7 @@ import { Switch } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types"; import { useDebounce } from "@app/hooks"; import { useGetAccessibleSecrets } from "@app/hooks/api/dashboard"; @@ -227,23 +228,9 @@ export const ReplicateFolderFromBoard = ({ bodyClassName="overflow-visible" className="max-w-2xl" title={ -
    +
    Replicate Secrets - -
    - - Docs - -
    -
    +
    } subTitle="Copy folder contents from other locations into this context" diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx index 146820bde..07f12d167 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CommitForm/CommitForm.tsx @@ -1,16 +1,12 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ import React, { useCallback, useState } from "react"; -import { - faClipboardCheck, - faCodeCommit, - faFolder, - faKey, - faSave -} from "@fortawesome/free-solid-svg-icons"; +import { faCodeCommit, faFolder, faKey, faSave } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AnimatePresence, motion } from "framer-motion"; +import { ClipboardCheckIcon } from "lucide-react"; -import { Badge, Button, Input, Modal, ModalContent } from "@app/components/v2"; +import { Button, Input, Modal, ModalContent } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { PendingAction } from "@app/hooks/api/secretFolders/types"; import { SecretVersionDiffView } from "@app/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView"; import { HIDDEN_SECRET_VALUE_API_MASK } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem"; @@ -329,9 +325,9 @@ export const CommitForm: React.FC = ({
    {/* Header */}
    -
    +
    Pending Changes - + {totalChangesCount} Change{totalChangesCount !== 1 ? "s" : ""}
    @@ -375,9 +371,9 @@ export const CommitForm: React.FC = ({ - Review Changes - + + {totalChangesCount} Change{totalChangesCount !== 1 ? "s" : ""}
    diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx index 45f1128ed..c95fae63d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CompareEnvironments/CompareEnvironments.tsx @@ -9,14 +9,13 @@ import { faFolder, faKey, faRotate, - faSearch, - faWarning + faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AlertTriangleIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { - Badge, Button, DropdownMenu, DropdownMenuContent, @@ -38,6 +37,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { getUserTablePreference, @@ -396,11 +396,8 @@ export const CompareEnvironments = ({ secretPath }: Props) => { } > - - + + {missingKeyCount} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx index f741e8215..5740e2061 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -49,9 +48,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx index 687d0d52b..555ed9eae 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx @@ -71,9 +71,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -83,9 +82,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx index e6a09d755..c9bfc92cc 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx @@ -22,9 +22,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -34,9 +33,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional() }); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx index 36be1b8e5..77bb7aa1f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx @@ -73,8 +73,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -84,8 +84,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), metadata: z diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx index 3b95dc8d3..515ee19e3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx @@ -39,9 +39,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -51,9 +50,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx index 4282ae256..e69b3460f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx @@ -241,8 +241,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -252,8 +252,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx index e519423a5..000762bc5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx @@ -58,9 +58,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -70,9 +69,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), usernameTemplate: z.string().trim().nullable().optional() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx index 0858816bf..95f40f9f5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx @@ -97,8 +97,8 @@ const formSchema = z const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -108,8 +108,8 @@ const formSchema = z const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), usernameTemplate: z.string().trim().optional() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx index 75edf5600..09d345d3d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx @@ -51,9 +51,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -63,9 +62,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx index c381f395c..5b577192a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx @@ -50,9 +50,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -62,9 +61,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx index 689a56b95..90c31e1f0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx @@ -33,9 +33,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -45,9 +44,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx index ab40914f4..984e68831 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx @@ -35,9 +35,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -47,9 +46,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), usernameTemplate: z.string().trim().nullable().optional() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx index d77c605e1..bc21a17d7 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx @@ -38,9 +38,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -50,9 +49,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx index 86327a767..37dbae5ee 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), usernameTemplate: z.string().trim().nullable().optional(), maxTTL: z @@ -50,9 +49,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional() }); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx index 8051f883c..430f13e83 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx @@ -37,9 +37,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -49,9 +48,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), usernameTemplate: z.string().trim().nullable().optional() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx index 5e23cb2a3..1cbc20cfb 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx @@ -35,9 +35,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -47,9 +46,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: slugSchema().optional(), usernameTemplate: z.string().trim().nullable().optional() diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx index 6beeba169..929a1485d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx @@ -73,9 +73,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), maxTTL: z .string() @@ -85,9 +84,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - // a day - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }) .nullable(), newName: slugSchema().optional(), diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx index e2f3e5b4d..e4ccda943 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx @@ -64,8 +64,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), usernameTemplate: z.string().trim().nullable().optional(), maxTTL: z @@ -76,8 +76,8 @@ const formSchema = z.object({ const valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); - if (valMs > 24 * 60 * 60 * 1000) - ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + if (valMs > ms("10y")) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" }); }), newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") }); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx index 79ac12df5..cb4a1a73a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx @@ -1,15 +1,9 @@ import { ChangeEvent, Dispatch, DragEvent, SetStateAction, useState } from "react"; import { useTranslation } from "react-i18next"; import { subject } from "@casl/ability"; -import { - faArrowRight, - faAsterisk, - faComment, - faKey, - faPlus, - faUpload -} from "@fortawesome/free-solid-svg-icons"; +import { faArrowRight, faPlus, faUpload } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AsteriskIcon, KeyIcon, MessageSquareIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; @@ -22,7 +16,6 @@ import { parseYaml } from "@app/components/utilities/parseSecrets"; import { - Badge, Button, FormLabel, Lottie, @@ -31,6 +24,7 @@ import { Select, SelectItem } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; import { PendingAction } from "@app/hooks/api/secretFolders/types"; @@ -115,23 +109,23 @@ const MatrixImportModalTableRow = ({
    - + {mapKey === "key" && ( <> - - Secret Key + + Secret Key )} {mapKey === "value" && ( <> - - Secret Value + + Secret Value )} {mapKey === "comment" && ( <> - - Comment + + Comment )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx index 09aa0fb7f..927bec31e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -321,7 +321,7 @@ export const SecretDetailSidebar = ({ onOpenChange={(isUpgradeModalOpen) => handlePopUpToggle("secretAccessUpgradePlan", isUpgradeModalOpen) } - text="Secret access analysis is only available on Infisical's Pro plan and above." + text="You can access secret access analysis if you switch to Infisical's Pro plan." /> )}
    - +
    { {secretSync.isAutoSyncEnabled ? ( - - - Auto-Sync Enabled + + + Auto-Sync Enabled ) : ( -
    - - - Auto-Sync Disabled - -
    + + + Auto-Sync Disabled +
    )}
    diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection.tsx index c7fda631c..ccd7ba795 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection.tsx @@ -1,5 +1,5 @@ import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { AWS_REGIONS } from "@app/helpers/appConnections"; import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync"; @@ -18,8 +18,8 @@ export const AwsParameterStoreSyncDestinationSection = ({ secretSync }: Props) = <> {awsRegion?.name} - - {awsRegion?.slug}{" "} + + {awsRegion?.slug} {path} diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection.tsx index b2908535c..48bac5186 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection.tsx @@ -1,5 +1,5 @@ import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { AWS_REGIONS } from "@app/helpers/appConnections"; import { AwsSecretsManagerSyncMappingBehavior, @@ -19,8 +19,8 @@ export const AwsSecretsManagerSyncDestinationSection = ({ secretSync }: Props) = <> {awsRegion?.name} - - {awsRegion?.slug}{" "} + + {awsRegion?.slug} diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsParameterStoreSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsParameterStoreSyncOptionsSection.tsx index 930c06b2f..5354f50b4 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsParameterStoreSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsParameterStoreSyncOptionsSection.tsx @@ -1,8 +1,8 @@ -import { faEye } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { EyeIcon } from "lucide-react"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync"; type Props = { @@ -40,11 +40,9 @@ export const AwsParameterStoreSyncOptionsSection = ({ secretSync }: Props) => { } >
    - - - - {tags.length} Tag{tags.length > 1 ? "s" : ""} - + + + {tags.length} Tag{tags.length > 1 ? "s" : ""}
    diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsSecretsManagerSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsSecretsManagerSyncOptionsSection.tsx index b22755269..f1c990c00 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsSecretsManagerSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/AwsSecretsManagerSyncOptionsSection.tsx @@ -1,8 +1,10 @@ import { faEye } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { EyeIcon } from "lucide-react"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { TAwsSecretsManagerSync } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync"; type Props = { @@ -40,11 +42,10 @@ export const AwsSecretsManagerSyncOptionsSection = ({ secretSync }: Props) => { } >
    - + + - - {tags.length} Tag{tags.length > 1 ? "s" : ""} - + {tags.length} Tag{tags.length > 1 ? "s" : ""}
    diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx index b23b3298b..044a6fe18 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx @@ -1,5 +1,5 @@ import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync"; type Props = { diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx index 61d1d8ccb..46ef832dd 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -5,7 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, IconButton } from "@app/components/v2"; +import { IconButton } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs"; @@ -114,7 +115,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = {AdditionalSyncOptionsComponent} {disableSecretDeletion && ( - Disabled + Disabled )}
    diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncSourceSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncSourceSection.tsx index e4b787281..bbb34d877 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncSourceSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncSourceSection.tsx @@ -1,10 +1,12 @@ import { subject } from "@casl/ability"; -import { faEdit, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AlertTriangleIcon } from "lucide-react"; import { ProjectPermissionCan } from "@app/components/permissions"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, IconButton, Tooltip } from "@app/components/v2"; +import { IconButton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { TSecretSync } from "@app/hooks/api/secretSyncs"; @@ -34,12 +36,9 @@ export const SecretSyncSourceSection = ({ secretSync, onEditSource }: Props) => {(!folder || !environment) && (
    - - - Folder Deleted + + + Folder Deleted
    diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx index ef7617d52..6fd0097ad 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx @@ -2,11 +2,11 @@ import { BsMicrosoftTeams } from "react-icons/bs"; import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { BanIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { OrgPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -15,6 +15,7 @@ import { Td, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { useGetMicrosoftTeamsIntegrationTeams } from "@app/hooks/api"; import { @@ -66,30 +67,36 @@ export const MicrosoftTeamsConfigRow = ({ {microsoftTeamsConfig.isAccessRequestNotificationEnabled && !isLoadingConfig && microsoftTeamsConfig.accessRequestChannels?.channelIds?.length > 0 ? ( - +

    {microsoftTeamsConfig.accessRequestChannels.channelIds .map((channel) => microsoftTeamsChannelIdToName[channel]) .join(", ")} - +

    ) : isLoadingConfig ? ( ) : ( - Disabled + + + Disabled + )} {microsoftTeamsConfig.isSecretRequestNotificationEnabled && !isLoadingConfig && microsoftTeamsConfig.secretRequestChannels?.channelIds?.length > 0 ? ( - +

    {microsoftTeamsConfig.secretRequestChannels.channelIds .map((channel) => microsoftTeamsChannelIdToName[channel]) .join(", ")} - +

    ) : isLoadingConfig ? ( ) : ( - Disabled + + + Disabled + )} diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx index df7211f99..bce21583f 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx @@ -2,11 +2,11 @@ import { BsSlack } from "react-icons/bs"; import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { BanIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { OrgPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -15,6 +15,7 @@ import { Td, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { useGetSlackIntegrationChannels } from "@app/hooks/api"; import { @@ -61,32 +62,38 @@ export const SlackConfigRow = ({ handlePopUpOpen, isSlackConfigLoading, slackCon {slackConfig.isAccessRequestNotificationEnabled && !isLoadingConfig && slackConfig.accessRequestChannels.length > 0 ? ( - +

    {slackConfig.accessRequestChannels .split(", ") .map((channel) => slackChannelIdToName[channel]) .join(", ")} - +

    ) : isLoadingConfig ? ( ) : ( - Disabled + + + Disabled + )} {slackConfig.isSecretRequestNotificationEnabled && !isLoadingConfig && slackConfig.secretRequestChannels.length > 0 ? ( - +

    {slackConfig.secretRequestChannels .split(", ") .map((channel) => slackChannelIdToName[channel]) .join(", ")} - +

    ) : isLoadingConfig ? ( ) : ( - Disabled + + + Disabled + )} diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx index 0b8433d56..d104d6f3b 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx @@ -11,7 +11,6 @@ import { useNavigate, useSearch } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { - Badge, Button, Card, CardTitle, @@ -25,6 +24,7 @@ import { TabPanel, Tabs } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; @@ -254,7 +254,7 @@ export const AWSParameterStoreConfigurePage = () => { > {awsRegions.map((awsRegion) => ( - {awsRegion.name} {awsRegion.slug} + {awsRegion.name} {awsRegion.slug} ))} diff --git a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx index 9ff3677c7..eb532cb06 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx @@ -14,7 +14,6 @@ import { motion } from "framer-motion"; import z from "zod"; import { - Badge, Button, Card, CardTitle, @@ -29,6 +28,7 @@ import { Tabs } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; +import { Badge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { useProject } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; @@ -332,7 +332,7 @@ export const AwsSecretManagerConfigurePage = () => { className="flex w-full justify-between" key={`aws-environment-${awsRegion.slug}`} > - {awsRegion.name} {awsRegion.slug} + {awsRegion.name} {awsRegion.slug} ))} diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx index 583cf69f4..bcc4bce23 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningDataSourceSection.tsx @@ -1,10 +1,12 @@ -import { faBan, faCheck, faEdit, faPlugCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { BanIcon, CheckIcon, UnplugIcon } from "lucide-react"; import { ProjectPermissionCan } from "@app/components/permissions"; import { EditSecretScanningDataSourceModal } from "@app/components/secret-scanning"; import { GenericFieldLabel } from "@app/components/secret-syncs"; -import { Badge, IconButton, Tooltip } from "@app/components/v2"; +import { IconButton, Tooltip } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; @@ -33,12 +35,9 @@ export const SecretScanningDataSourceSection = ({ dataSource }: Props) => { content="The external data source has been removed and can no longer be scanned. Delete this data source and re-initialize the connection." >
    - - - Disconnected + + + Disconnected
    @@ -71,17 +70,14 @@ export const SecretScanningDataSourceSection = ({ dataSource }: Props) => { {isAutoScanEnabled ? ( - - - Enabled + + + Enabled ) : ( - - - Disabled + + + Disabled )} diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx index 7e9164257..740cf6d71 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx @@ -1,24 +1,21 @@ import { useCallback } from "react"; import { - faBan, faCheck, faCopy, faEllipsisV, faExpand, - faInfoCircle, - faSearch, - faWarning + faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { formatDistance } from "date-fns"; +import { AlertTriangleIcon, BanIcon, ScanTextIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { SecretScanningScanStatusBadge } from "@app/components/secret-scanning"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -28,6 +25,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub @@ -141,45 +139,38 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { {/* eslint-disable-next-line no-nested-ternary */} {lastScanStatus?.match(/queued|scanning/) ? ( - - - Scanning For Leaks + + + Scanning For Leaks ) : // eslint-disable-next-line no-nested-ternary lastScannedAt ? ( // eslint-disable-next-line no-nested-ternary unresolvedFindings ? ( - - navigate({ - to: "/projects/secret-scanning/$projectId/findings", - params: { - projectId: dataSource.projectId - }, - search: { - search: name, - status: SecretScanningFindingStatus.Unresolved - } - }) - } - variant="primary" - className="flex h-5 w-min cursor-pointer items-center gap-1.5 whitespace-nowrap" - > - - + + ) : lastScanStatus === SecretScanningScanStatus.Failed ? ( No findings ) : ( - + No Secrets Detected @@ -209,12 +200,10 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { className="text-xs" content={`This ${resourceDetails.singularNoun} will not be scanned due to exclusion in Data Source configuration.`} > -
    - - - Inactive - -
    + + + Inactive + )} diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx index e174aa0ed..a89402c15 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningScanRow.tsx @@ -1,14 +1,14 @@ import { useCallback } from "react"; -import { faCheck, faCopy, faEllipsisV, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faCopy, faEllipsisV } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { format } from "date-fns"; +import { AlertTriangleIcon, CheckIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { SecretScanningScanStatusBadge } from "@app/components/secret-scanning"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -18,6 +18,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { useToggle } from "@app/hooks"; import { @@ -92,40 +93,33 @@ export const SecretScanningScanRow = ({ scan }: Props) => { ) : // eslint-disable-next-line no-nested-ternary totalFindings ? (
    - - navigate({ - to: "/projects/secret-scanning/$projectId/findings", - params: { - projectId: currentProject.id - }, - search: { - search: `scanId:${id}` - } - }) - } - variant={unresolvedFindings ? "primary" : undefined} - className={twMerge( - "flex h-5 w-min cursor-pointer items-center gap-1.5 whitespace-nowrap", - !unresolvedFindings && "bg-mineshaft-400/50 text-bunker-300" - )} - > - - + +
    ) : status === SecretScanningScanStatus.Failed ? ( No findings ) : ( - + No Secrets Detected diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx index d14d3a656..af298ecdc 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourceRow.tsx @@ -1,6 +1,5 @@ import { useCallback } from "react"; import { - faBan, faCheck, faCopy, faEdit, @@ -8,22 +7,20 @@ import { faExpand, faInfoCircle, faPlugCircleXmark, - faSearch, faToggleOff, faToggleOn, - faTrash, - faWarning + faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { formatDistance } from "date-fns"; +import { AlertTriangleIcon, BanIcon, ScanTextIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { SecretScanningScanStatusBadge } from "@app/components/secret-scanning"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -33,6 +30,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ROUTE_PATHS } from "@app/const/routes"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; @@ -139,46 +137,39 @@ export const SecretScanningDataSourceRow = ({ {/* eslint-disable-next-line no-nested-ternary */} {lastScanStatus?.match(/queued|scanning/) ? ( - - - Scanning For Leaks + + + Scanning For Leaks ) : // eslint-disable-next-line no-nested-ternary lastScannedAt ? ( // eslint-disable-next-line no-nested-ternary unresolvedFindings ? ( - { - e.stopPropagation(); - navigate({ - to: "/projects/secret-scanning/$projectId/findings", - params: { - projectId - }, - search: { - search: name, - status: SecretScanningFindingStatus.Unresolved - } - }); - }} - > - - + + ) : lastScanStatus === SecretScanningScanStatus.Failed ? ( No findings ) : ( - + No Secrets Detected @@ -206,12 +197,10 @@ export const SecretScanningDataSourceRow = ({ className="text-xs" content={`Auto-Scan is disabled. Scans will not be automatically triggered when a ${autoScanDescription.verb} occurs to ${autoScanDescription.pluralNoun} associated with this data source`} > -
    - - - Auto-Scan Disabled - -
    + + + Auto-Scan Disabled +
    )} {isDisconnected && ( diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx index 380ac70bd..a786d79bc 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx @@ -1,10 +1,11 @@ -import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CreateSecretScanningDataSourceModal } from "@app/components/secret-scanning"; import { Button, Spinner } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionSub, useProject, useSubscription } from "@app/context"; import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; @@ -40,22 +41,9 @@ export const SecretScanningDataSourcesSection = () => {
    -
    +

    Data Sources

    - -
    - - Docs - -
    -
    +

    Configure Data Sources to scan for secret leaks from third-party services. @@ -72,7 +60,9 @@ export const SecretScanningDataSourcesSection = () => { leftIcon={} onClick={() => { if (!subscription.secretScanning) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); return; } @@ -95,6 +85,7 @@ export const SecretScanningDataSourcesSection = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text="You can create Data Sources by upgrading to Infisical's Enterprise plan." + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx index 579c4ee12..92e77daae 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingRow.tsx @@ -7,7 +7,6 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, Button, Checkbox, DropdownMenu, @@ -20,19 +19,17 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { SECRET_SCANNING_DATA_SOURCE_MAP, - SECRET_SCANNING_FINDING_STATUS_ICON_MAP + SECRET_SCANNING_FINDING_STATUS_MAP } from "@app/helpers/secretScanningV2"; import { useToggle } from "@app/hooks"; -import { - SecretScanningFindingStatus, - TSecretScanningFinding -} from "@app/hooks/api/secretScanningV2"; +import { TSecretScanningFinding } from "@app/hooks/api/secretScanningV2"; type Props = { isSelected: boolean; @@ -83,6 +80,8 @@ export const SecretScanningFindingRow = ({ const [isExpanded, setIsExpanded] = useToggle(false); + const StatusIcon = SECRET_SCANNING_FINDING_STATUS_MAP[status].Icon; + return ( <> {rule} -

    - - - {status.replace("-", " ")} - -
    + + + {status.replace("-", " ")} + @@ -172,7 +161,7 @@ export const SecretScanningFindingRow = ({ - +
    } > diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx index d66dc46cb..1ac526dfc 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsSection.tsx @@ -1,7 +1,5 @@ -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - import { ContentLoader } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { useProject } from "@app/context"; import { useListSecretScanningFindings } from "@app/hooks/api/secretScanningV2"; @@ -23,22 +21,9 @@ export const SecretScanningFindingsSection = () => {
    -
    +

    Findings

    - -
    - - Docs - -
    -
    +

    Secret leaks discovered across your data sources diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsTable.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsTable.tsx index d9ae0e433..cf48ab2c2 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsTable.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningFindingsTable.tsx @@ -38,7 +38,7 @@ import { } from "@app/context/ProjectPermissionContext/types"; import { SECRET_SCANNING_DATA_SOURCE_MAP, - SECRET_SCANNING_FINDING_STATUS_ICON_MAP + SECRET_SCANNING_FINDING_STATUS_MAP } from "@app/helpers/secretScanningV2"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -219,34 +219,40 @@ export const SecretScanningFindingsTable = ({ findings }: Props) => { Status - {Object.values(SecretScanningFindingStatus).map((status) => ( - { - e.preventDefault(); - setFilters((prev) => ({ - ...prev, - status: prev.status.includes(status) - ? prev.status.filter((s) => s !== status) - : [...prev.status, status] - })); - }} - key={status} - icon={ - filters.status.includes(status) && ( - - ) - } - iconPos="right" - > -

    - - {status.replace("-", " ")} -
    - - ))} + {Object.values(SecretScanningFindingStatus).map((status) => { + const { Icon } = SECRET_SCANNING_FINDING_STATUS_MAP[status]; + + return ( + { + e.preventDefault(); + setFilters((prev) => ({ + ...prev, + status: prev.status.includes(status) + ? prev.status.filter((s) => s !== status) + : [...prev.status, status] + })); + }} + key={status} + icon={ + filters.status.includes(status) && ( + + ) + } + iconPos="right" + > +
    + + {status.replace("-", " ")} +
    +
    + ); + })} Platform {findings.length ? ( [...new Set(findings.map(({ dataSourceType: type }) => type))].map((type) => { diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx index 11894db7e..484cbcf36 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx @@ -13,7 +13,7 @@ import { SelectItem, TextArea } from "@app/components/v2"; -import { SECRET_SCANNING_FINDING_STATUS_ICON_MAP } from "@app/helpers/secretScanningV2"; +import { SECRET_SCANNING_FINDING_STATUS_MAP } from "@app/helpers/secretScanningV2"; import { SecretScanningFindingStatus, TSecretScanningFinding, @@ -105,9 +105,9 @@ const Content = ({ findings, onComplete }: ContentProps) => { className="w-full border border-mineshaft-500 capitalize" position="popper" dropdownContainerClassName="max-w-none" - icon={value ? SECRET_SCANNING_FINDING_STATUS_ICON_MAP[value].icon : undefined} + LucideIcon={value ? SECRET_SCANNING_FINDING_STATUS_MAP[value].Icon : undefined} iconClassName={ - value ? SECRET_SCANNING_FINDING_STATUS_ICON_MAP[value].className : undefined + value ? SECRET_SCANNING_FINDING_STATUS_MAP[value].className : undefined } > {Object.values(SecretScanningFindingStatus).map((status) => { diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx index b87b29b7c..c9df333c4 100644 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx +++ b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx @@ -1,12 +1,11 @@ import { Controller, useForm } from "react-hook-form"; -import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, FormControl, TextArea } from "@app/components/v2"; +import { DocumentationLinkBadge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSecretScanningConfigActions } from "@app/context/ProjectPermissionContext/types"; import { @@ -62,21 +61,7 @@ export const SecretScanningConfigForm = ({ config }: Props) => {

    Project Configuration

    - -
    - - Docs - -
    -
    +

    Configure rules and exceptions to customize scanning diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesTable.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesTable.tsx index 4581e7ca9..8f2b71b03 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesTable.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesTable.tsx @@ -10,7 +10,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -26,6 +25,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { SshCertTemplateStatus, useGetSshCaCertTemplates } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx index c94f73664..c00679a1f 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaTable.tsx @@ -5,7 +5,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -21,6 +20,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { SshCaStatus, useListWorkspaceSshCas } from "@app/hooks/api"; import { caStatusToNameMap, getCaStatusBadgeVariant } from "@app/hooks/api/ca/constants"; diff --git a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx index 1ebf3112c..19aefb92a 100644 --- a/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx +++ b/frontend/src/pages/ssh/SshCertsPage/components/SshCertificatesTable.tsx @@ -3,7 +3,6 @@ import { faCertificate } from "@fortawesome/free-solid-svg-icons"; import { format } from "date-fns"; import { - Badge, EmptyState, Pagination, Table, @@ -15,6 +14,7 @@ import { THead, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { useListWorkspaceSshCertificates } from "@app/hooks/api"; diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx index 6bfad3205..df6f2b723 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx @@ -30,7 +30,8 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { if (!subscription?.sshHostGroups) { handlePopUpOpen("upgradePlan", { description: - "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license." + "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license.", + isEnterpriseFeature: true }); } else { handlePopUpOpen("addHostGroupMembers", { @@ -107,6 +108,9 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text={(popUp.upgradePlan?.data as { description: string })?.description} + isEnterpriseFeature={ + (popUp.upgradePlan?.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature + } />

    ); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx index 0d0bd8e50..189a49686 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx @@ -26,7 +26,8 @@ export const SshHostGroupsSection = () => { if (!subscription?.sshHostGroups) { handlePopUpOpen("upgradePlan", { description: - "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license." + "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license.", + isEnterpriseFeature: true }); } else { handlePopUpOpen("sshHostGroup"); @@ -106,6 +107,9 @@ export const SshHostGroupsSection = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} text={(popUp.upgradePlan?.data as { description: string })?.description} + isEnterpriseFeature={ + (popUp.upgradePlan?.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature + } />
    ); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx index b568b9fd1..92cc0d68c 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsTable.tsx @@ -12,7 +12,6 @@ import { twMerge } from "tailwind-merge"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -28,6 +27,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { useListWorkspaceSshHostGroups } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -98,7 +98,7 @@ export const SshHostGroupsTable = ({ handlePopUpOpen }: Props) => { className="text-xs text-yellow/80" /> {username} - user + user
    ))} diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx index 182ceebb2..448e62300 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsTable.tsx @@ -3,18 +3,16 @@ import { faEllipsis, faPencil, faServer, - faTrash, - faUser, - faUsers + faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import FileSaver from "file-saver"; +import { UserIcon, UsersIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { - Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -30,6 +28,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSshHostActions, ProjectPermissionSub, useProject } from "@app/context"; import { fetchSshHostUserCaPublicKey, useListWorkspaceSshHosts } from "@app/hooks/api"; import { LoginMappingSource } from "@app/hooks/api/sshHost/types"; @@ -166,15 +165,14 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { className="flex items-center gap-2" >
    - └─ + └
    - {username} - user + + + User +
    ))} @@ -184,15 +182,14 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => { className="flex items-center gap-2" >
    - └─ + └
    - {group} - group + + + Group +
    ))}