diff --git a/backend/src/db/migrations/20251018061215_sub-org.ts b/backend/src/db/migrations/20251018061215_sub-org.ts index 6378fa66a..089aeef90 100644 --- a/backend/src/db/migrations/20251018061215_sub-org.ts +++ b/backend/src/db/migrations/20251018061215_sub-org.ts @@ -2,7 +2,7 @@ import { Knex } from "knex"; import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; -import { AccessScope, TableName } from "../schemas"; +import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { const hasParentOrgId = await knex.schema.hasColumn(TableName.Organization, "parentOrgId"); @@ -18,8 +18,6 @@ export async function up(knex: Knex): Promise { await dropConstraintIfExists(TableName.Organization, "organizations_slug_unique", knex); t.unique(["rootOrgId", "parentOrgId", "slug"]); }); - - // had to switch to raw for null not distinct } const hasIdentityOrgCol = await knex.schema.hasColumn(TableName.Identity, "orgId"); @@ -28,24 +26,6 @@ export async function up(knex: Knex): Promise { t.uuid("orgId"); t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); }); - - await knex.raw( - ` - UPDATE ?? AS identity - SET "orgId" = membership."scopeOrgId" - FROM ?? AS membership - WHERE - membership."actorIdentityId" = identity."id" - AND membership."scope" = ? -`, - [TableName.Identity, TableName.Membership, AccessScope.Organization] - ); - - await knex.raw(`DELETE FROM ?? WHERE "orgId" IS NULL`, [TableName.Identity]); - - await knex.schema.alterTable(TableName.Identity, (t) => { - t.uuid("orgId").notNullable().alter(); - }); } } diff --git a/backend/src/db/migrations/20251019061215_sub-org-identity-backfill.ts b/backend/src/db/migrations/20251019061215_sub-org-identity-backfill.ts new file mode 100644 index 000000000..9054a03ee --- /dev/null +++ b/backend/src/db/migrations/20251019061215_sub-org-identity-backfill.ts @@ -0,0 +1,48 @@ +import { Knex } from "knex"; + +import { chunkArray } from "@app/lib/fn"; + +import { AccessScope, TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.transaction(async (tx) => { + const hasIdentityOrgCol = await tx.schema.hasColumn(TableName.Identity, "orgId"); + if (hasIdentityOrgCol) { + const identityMemberships = await tx(TableName.Membership) + .where({ + scope: AccessScope.Organization + }) + .whereNotNull("actorIdentityId") + .select("actorIdentityId", "scopeOrgId"); + + const identityToOrgMapping: Record = {}; + identityMemberships.forEach((el) => { + if (el.actorIdentityId) { + identityToOrgMapping[el.actorIdentityId] = el.scopeOrgId; + } + }); + + const batchMemberships = chunkArray(identityMemberships, 500); + for await (const membership of batchMemberships) { + const identityIds = membership.map((el) => el.actorIdentityId).filter(Boolean) as string[]; + if (identityIds.length) { + const identities = await tx(TableName.Identity).whereIn("id", identityIds).select("*"); + await tx(TableName.Identity) + .insert( + identities.map((el) => ({ + ...el, + orgId: identityToOrgMapping[el.id] + })) + ) + .onConflict("id") + .merge(); + } + } + } + }); +} + +export async function down(): Promise {} + +const config = { transaction: false }; +export { config }; diff --git a/backend/src/db/migrations/20251023123213_block-duplicate-sync-destinations-setting.ts b/backend/src/db/migrations/20251023123213_block-duplicate-sync-destinations-setting.ts new file mode 100644 index 000000000..7675eb3e1 --- /dev/null +++ b/backend/src/db/migrations/20251023123213_block-duplicate-sync-destinations-setting.ts @@ -0,0 +1,27 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasOrgBlockDuplicateColumn = await knex.schema.hasColumn( + TableName.Organization, + "blockDuplicateSecretSyncDestinations" + ); + if (!hasOrgBlockDuplicateColumn) { + await knex.schema.table(TableName.Organization, (table) => { + table.boolean("blockDuplicateSecretSyncDestinations").notNullable().defaultTo(false); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasOrgBlockDuplicateColumn = await knex.schema.hasColumn( + TableName.Organization, + "blockDuplicateSecretSyncDestinations" + ); + if (hasOrgBlockDuplicateColumn) { + await knex.schema.table(TableName.Organization, (table) => { + table.dropColumn("blockDuplicateSecretSyncDestinations"); + }); + } +} diff --git a/backend/src/db/migrations/20251028155708_identity-access-token-remove-fk-for-identity-id.ts b/backend/src/db/migrations/20251028155708_identity-access-token-remove-fk-for-identity-id.ts new file mode 100644 index 000000000..0974f39c2 --- /dev/null +++ b/backend/src/db/migrations/20251028155708_identity-access-token-remove-fk-for-identity-id.ts @@ -0,0 +1,22 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.transaction(async (tx) => { + await tx.schema.alterTable(TableName.IdentityAccessToken, (table) => { + table.dropForeign("identityId"); + }); + }); +} + +export async function down(knex: Knex): Promise { + await knex.transaction(async (tx) => { + await tx.schema.alterTable(TableName.IdentityAccessToken, (table) => { + table.foreign("identityId").references("id").inTable(TableName.Identity); + }); + }); +} + +const config = { transaction: false }; +export { config }; diff --git a/backend/src/db/migrations/20251028160921_delete-no-org-identities.ts b/backend/src/db/migrations/20251028160921_delete-no-org-identities.ts new file mode 100644 index 000000000..0eb0e7ae0 --- /dev/null +++ b/backend/src/db/migrations/20251028160921_delete-no-org-identities.ts @@ -0,0 +1,30 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +const MIGRATION_TIMEOUT = 30 * 60 * 1000; // 30 minutes + +export async function up(knex: Knex): Promise { + const result = await knex.raw("SHOW statement_timeout"); + const originalTimeout = result.rows[0].statement_timeout; + + await knex.transaction(async (tx) => { + try { + await tx.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`); + const hasIdentityOrgCol = await tx.schema.hasColumn(TableName.Identity, "orgId"); + if (hasIdentityOrgCol) { + await tx(TableName.Identity).whereNull("orgId").delete(); + await tx.schema.alterTable(TableName.Identity, (t) => { + t.uuid("orgId").notNullable().alter(); + }); + } + } finally { + await tx.raw(`SET statement_timeout = '${originalTimeout}'`); + } + }); +} + +export async function down(): Promise {} + +const config = { transaction: false }; +export { config }; diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index a1c01151f..3cc7fe858 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -40,7 +40,8 @@ export const OrganizationsSchema = z.object({ googleSsoAuthEnforced: z.boolean().default(false), googleSsoAuthLastUsed: z.date().nullable().optional(), parentOrgId: z.string().uuid().nullable().optional(), - rootOrgId: z.string().uuid().nullable().optional() + rootOrgId: z.string().uuid().nullable().optional(), + blockDuplicateSecretSyncDestinations: z.boolean().default(false) }); export type TOrganizations = z.infer; 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/routes/v1/kmip-spec-router.ts b/backend/src/ee/routes/v1/kmip-spec-router.ts index 6fcf05d99..1e3305ac7 100644 --- a/backend/src/ee/routes/v1/kmip-spec-router.ts +++ b/backend/src/ee/routes/v1/kmip-spec-router.ts @@ -182,7 +182,8 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => { algorithm: z.string(), isActive: z.boolean(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + kmipMetadata: z.record(z.any()).nullish() }) } }, @@ -384,7 +385,8 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => { isActive: z.boolean(), algorithm: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + kmipMetadata: z.record(z.any()).nullish() }) .array() }) 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/kmip/kmip-operation-service.ts b/backend/src/ee/services/kmip/kmip-operation-service.ts index b3eace6bc..3955fd0ca 100644 --- a/backend/src/ee/services/kmip/kmip-operation-service.ts +++ b/backend/src/ee/services/kmip/kmip-operation-service.ts @@ -341,7 +341,8 @@ export const kmipOperationServiceFactory = ({ algorithm: completeKeyDetails.internalKms.encryptionAlgorithm, isActive: !key.isDisabled, createdAt: key.createdAt, - updatedAt: key.updatedAt + updatedAt: key.updatedAt, + kmipMetadata: key.kmipMetadata as Record }; }; 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/server/routes/index.ts b/backend/src/server/routes/index.ts index f56026ffb..386db0e8f 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1960,6 +1960,8 @@ export const registerRoutes = async ( secretImportDAL, permissionService, appConnectionService, + projectDAL, + orgDAL, folderDAL, secretSyncQueue, projectBotService, diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 76b3eae51..d4b0058f9 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -323,7 +323,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { .min(1, "Max Shared Secret view count cannot be lower than 1") .max(1000, "Max Shared Secret view count cannot exceed 1000") .nullable() + .optional(), + blockDuplicateSecretSyncDestinations: z + .boolean() .optional() + .describe("Block duplicate secret sync destinations across the organization") }), response: { 200: z.object({ 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/kms/kms-key-dal.ts b/backend/src/services/kms/kms-key-dal.ts index a0dd12191..36ffa3366 100644 --- a/backend/src/services/kms/kms-key-dal.ts +++ b/backend/src/services/kms/kms-key-dal.ts @@ -112,7 +112,8 @@ export const kmskeyDALFactory = (db: TDbClient) => { ...KmsKeysSchema.parse(entry), isActive: !entry.isDisabled, algorithm: entry.internalKmsEncryptionAlgorithm, - version: entry.internalKmsVersion + version: entry.internalKmsVersion, + kmipMetadata: entry.kmipMetadata as Record })); } catch (error) { throw new DatabaseError({ error, name: "Find project cmeks" }); diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index 4a3bdb06e..be5c300b5 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -27,5 +27,6 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ scannerProductEnabled: true, shareSecretsProductEnabled: true, maxSharedSecretLifetime: true, - maxSharedSecretViewLimit: true + maxSharedSecretViewLimit: true, + blockDuplicateSecretSyncDestinations: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 51d907e05..b0bbd9a2a 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -405,7 +405,8 @@ export const orgServiceFactory = ({ scannerProductEnabled, shareSecretsProductEnabled, maxSharedSecretLifetime, - maxSharedSecretViewLimit + maxSharedSecretViewLimit, + blockDuplicateSecretSyncDestinations } }: TUpdateOrgDTO) => { const appCfg = getConfig(); @@ -516,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) { @@ -589,7 +590,8 @@ export const orgServiceFactory = ({ scannerProductEnabled, shareSecretsProductEnabled, maxSharedSecretLifetime, - maxSharedSecretViewLimit + maxSharedSecretViewLimit, + blockDuplicateSecretSyncDestinations }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); return org; @@ -1147,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/org/org-types.ts b/backend/src/services/org/org-types.ts index 48680456c..2587045bb 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -90,6 +90,7 @@ export type TUpdateOrgDTO = { shareSecretsProductEnabled: boolean; maxSharedSecretLifetime: number; maxSharedSecretViewLimit: number | null; + blockDuplicateSecretSyncDestinations: boolean; }>; } & TOrgPermission; 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 39538de2e..25052ba9a 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"; @@ -310,19 +312,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({ @@ -586,39 +603,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-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 6a2f49386..a4e2fc467 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -15,6 +15,8 @@ import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { deepEqualSkipFields } from "@app/lib/fn/object"; import { OrgServiceActor } from "@app/lib/types"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; +import { TOrgDALFactory } from "@app/services/org/org-dal"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; @@ -50,6 +52,8 @@ type TSecretSyncServiceFactoryDep = { secretImportDAL: TSecretImportDALFactory; appConnectionService: Pick; permissionService: Pick; + projectDAL: Pick; + orgDAL: Pick; projectBotService: Pick; folderDAL: Pick; keyStore: Pick; @@ -68,6 +72,8 @@ export const secretSyncServiceFactory = ({ secretImportDAL, permissionService, appConnectionService, + projectDAL, + orgDAL, projectBotService, secretSyncQueue, keyStore, @@ -225,6 +231,61 @@ export const secretSyncServiceFactory = ({ return secretSync as TSecretSync; }; + const checkDuplicateDestination = async ( + { destination, destinationConfig, excludeSyncId, projectId }: TCheckDuplicateDestinationDTO, + actor: OrgServiceActor + ) => { + const skipFields = SECRET_SYNC_SKIP_FIELDS_MAP[destination]; + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.SecretManager, + projectId + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionSecretSyncActions.Read, + ProjectPermissionSub.SecretSyncs + ); + + if (!destinationConfig || Object.keys(destinationConfig).length === 0) { + return { hasDuplicate: false, duplicateProjectId: undefined }; + } + + try { + const existingSyncs = await secretSyncDAL.findByDestinationAndOrgId(destination, actor.orgId); + + const duplicates = existingSyncs.filter((sync) => { + if (sync.id === excludeSyncId) { + return false; + } + + try { + const baseFieldsMatch = deepEqualSkipFields(sync.destinationConfig, destinationConfig, skipFields); + if (baseFieldsMatch) { + return DESTINATION_DUPLICATE_CHECK_MAP[destination]( + sync.destinationConfig as Record, + destinationConfig + ); + } + return false; + } catch { + return false; + } + }); + + const hasDuplicate = duplicates.length > 0; + return { + hasDuplicate, + duplicateProjectId: hasDuplicate ? duplicates[0].projectId : undefined + }; + } catch (error) { + return { hasDuplicate: false, duplicateProjectId: undefined }; + } + }; + const createSecretSync = async ( { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, actor: OrgServiceActor @@ -271,6 +332,30 @@ export const secretSyncServiceFactory = ({ message: `Could not find folder with path "${secretPath}" in environment "${environment}" for project with ID "${projectId}"` }); + const project = await projectDAL.findById(projectId); + if (!project) { + throw new NotFoundError({ message: "Project not found" }); + } + + const organization = await orgDAL.findById(project.orgId); + if (organization?.blockDuplicateSecretSyncDestinations) { + const duplicateCheck = await checkDuplicateDestination( + { + destination: params.destination, + destinationConfig: params.destinationConfig, + projectId + }, + actor + ); + if (duplicateCheck.hasDuplicate) { + throw new BadRequestError({ + message: `A secret sync with this destination already exists${ + duplicateCheck.duplicateProjectId ? ` in project ${duplicateCheck.duplicateProjectId}` : "" + }.` + }); + } + } + const destinationApp = SECRET_SYNC_CONNECTION_MAP[params.destination]; // validates permission to connect and app is valid for sync destination @@ -369,6 +454,33 @@ export const secretSyncServiceFactory = ({ let { folderId } = secretSync; + if (params.destinationConfig) { + const project = await projectDAL.findById(secretSync.projectId); + if (!project) { + throw new NotFoundError({ message: "Project not found" }); + } + const organization = await orgDAL.findById(project.orgId); + + if (organization?.blockDuplicateSecretSyncDestinations) { + const duplicateCheck = await checkDuplicateDestination( + { + destination, + destinationConfig: params.destinationConfig, + projectId: secretSync.projectId, + excludeSyncId: secretSync.id + }, + actor + ); + if (duplicateCheck.hasDuplicate) { + throw new BadRequestError({ + message: `A secret sync with this destination already exists${ + duplicateCheck.duplicateProjectId ? ` in project ${duplicateCheck.duplicateProjectId}` : "" + }.` + }); + } + } + } + if (params.connectionId) { const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync]; @@ -703,61 +815,6 @@ export const secretSyncServiceFactory = ({ return updatedSecretSync as TSecretSync; }; - const checkDuplicateDestination = async ( - { destination, destinationConfig, excludeSyncId, projectId }: TCheckDuplicateDestinationDTO, - actor: OrgServiceActor - ) => { - const skipFields = SECRET_SYNC_SKIP_FIELDS_MAP[destination]; - const { permission } = await permissionService.getProjectPermission({ - actor: actor.type, - actorId: actor.id, - actorAuthMethod: actor.authMethod, - actorOrgId: actor.orgId, - actionProjectType: ActionProjectType.SecretManager, - projectId - }); - - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretSyncActions.Read, - ProjectPermissionSub.SecretSyncs - ); - - if (!destinationConfig || Object.keys(destinationConfig).length === 0) { - return { hasDuplicate: false, duplicateProjectId: undefined }; - } - - try { - const existingSyncs = await secretSyncDAL.findByDestinationAndOrgId(destination, actor.orgId); - - const duplicates = existingSyncs.filter((sync) => { - if (sync.id === excludeSyncId) { - return false; - } - - try { - const baseFieldsMatch = deepEqualSkipFields(sync.destinationConfig, destinationConfig, skipFields); - if (baseFieldsMatch) { - return DESTINATION_DUPLICATE_CHECK_MAP[destination]( - sync.destinationConfig as Record, - destinationConfig - ); - } - return false; - } catch { - return false; - } - }); - - const hasDuplicate = duplicates.length > 0; - return { - hasDuplicate, - duplicateProjectId: hasDuplicate ? duplicates[0].projectId : undefined - }; - } catch (error) { - return { hasDuplicate: false, duplicateProjectId: undefined }; - } - }; - return { listSecretSyncOptions, listSecretSyncsByProjectId, 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/api-reference/overview/introduction.mdx b/docs/api-reference/overview/introduction.mdx index 6d577e15b..2f75bf0f5 100644 --- a/docs/api-reference/overview/introduction.mdx +++ b/docs/api-reference/overview/introduction.mdx @@ -7,4 +7,10 @@ Infisical's Public (REST) API provides users an alternative way to programmatica secrets via HTTPS requests. This can be useful for automating tasks, such as rotating credentials, or for integrating secret management into a larger system. -With the Public API, you can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more. \ No newline at end of file +With the Public API, you can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more. + +## API Versioning + +The API is versioned on a per-resource basis. A resource's version is only incremented for breaking changes, so different endpoints may have different version numbers (e.g., `/api/v4/secrets` vs. `/api/v1/secret-syncs`). + +As a best practice, always use the latest available version for each endpoint to ensure access to the most recent features and improvements. 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/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/CertificateManagementModal.tsx b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx index fa199cb3a..760802b13 100644 --- a/frontend/src/components/pki-syncs/CertificateManagementModal.tsx +++ b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx @@ -4,7 +4,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { - Badge, Button, Checkbox, EmptyState, @@ -20,6 +19,7 @@ import { THead, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { CertStatus, 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 060a5e715..8119ac23a 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 { useListWorkspaceCertificates } from "@app/hooks/api/projects"; 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/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index 45e0297bd..ebad86cbd 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -8,13 +8,14 @@ import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Switch } from "@app/components/v2"; -import { useProject } from "@app/context"; +import { useOrganization, useProject } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync, SecretSyncInitialSyncBehavior, TSecretSync, useCreateSecretSync, + useDuplicateDestinationCheck, useSecretSyncOption } from "@app/hooks/api/secretSyncs"; @@ -48,6 +49,7 @@ export const CreateSecretSyncForm = ({ }: Props) => { const createSecretSync = useCreateSecretSync(); const { currentProject } = useProject(); + const { currentOrg } = useOrganization(); const { name: destinationName } = SECRET_SYNC_MAP[destination]; const [showConfirmation, setShowConfirmation] = useState(false); @@ -106,11 +108,20 @@ export const CreateSecretSyncForm = ({ setSelectedTabIndex((prev) => prev - 1); }; - const { handleSubmit, trigger, control } = formMethods; + const { handleSubmit, trigger, control, watch } = formMethods; + + const { hasDuplicate } = useDuplicateDestinationCheck({ + destination, + projectId: currentProject?.id || "", + enabled: true, + destinationConfig: watch("destinationConfig") + }); const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields); const isFinalStep = selectedTabIndex === FORM_TABS.length - 1; + const isCreateButtonDisabled = + isFinalStep && hasDuplicate && currentOrg?.blockDuplicateSecretSyncDestinations; const handleNext = async () => { if (isFinalStep) { @@ -245,7 +256,7 @@ export const CreateSecretSyncForm = ({
- {selectedTabIndex > 0 && ( diff --git a/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx b/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx index d80dbc640..59871e880 100644 --- a/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx +++ b/frontend/src/components/secret-syncs/forms/DuplicateDestinationConfirmationModal.tsx @@ -6,6 +6,7 @@ type Props = { onConfirm: () => void; isLoading?: boolean; duplicateProjectId?: string; + isDisabled?: boolean; }; export const DuplicateDestinationConfirmationModal = ({ @@ -13,7 +14,8 @@ export const DuplicateDestinationConfirmationModal = ({ onOpenChange, onConfirm, isLoading, - duplicateProjectId + duplicateProjectId, + isDisabled }: Props) => { return ( @@ -21,7 +23,12 @@ export const DuplicateDestinationConfirmationModal = ({

Another secret sync in your organization is already configured with the same - destination. Proceeding may cause conflicts or overwrite existing data. + destination.{" "} + + {isDisabled + ? "Your organization does not allow duplicate destination configurations." + : "Proceeding may cause conflicts or overwrite existing data."} +

{duplicateProjectId && (

@@ -31,26 +38,28 @@ export const DuplicateDestinationConfirmationModal = ({

)} -

Are you sure you want to continue?

+ {!isDisabled &&

Are you sure you want to continue?

}
-
- - - - - - -
+ {!isDisabled && ( +
+ + + + + + +
+ )}
); diff --git a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx index 3c0e4ea17..2085afe9c 100644 --- a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx @@ -5,6 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { createNotification } from "@app/components/notifications"; import { SecretSyncEditFields } from "@app/components/secret-syncs/types"; import { Button, ModalClose } from "@app/components/v2"; +import { useOrganization } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { TSecretSync, @@ -30,6 +31,7 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => const { name: destinationName } = SECRET_SYNC_MAP[secretSync.destination]; const [showDuplicateConfirmation, setShowDuplicateConfirmation] = useState(false); const [pendingFormData, setPendingFormData] = useState(null); + const { currentOrg } = useOrganization(); const formMethods = useForm({ resolver: zodResolver(UpdateSecretSyncFormSchema), @@ -209,6 +211,7 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => onConfirm={handleConfirmDuplicate} isLoading={updateSecretSync.isPending} duplicateProjectId={storedDuplicateProjectId} + isDisabled={currentOrg?.blockDuplicateSecretSyncDestinations} /> ); 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 44d35cd2c..8c8ea523b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -5,8 +5,8 @@ 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 { useProject } from "@app/context"; +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"; @@ -51,6 +51,7 @@ import { ZabbixSyncReviewFields } from "./ZabbixSyncReviewFields"; export const SecretSyncReviewFields = () => { const { watch } = useFormContext(); const { currentProject } = useProject(); + const { currentOrg } = useOrganization(); let DestinationFieldsComponent: ReactNode; let AdditionalSyncOptionsFieldsComponent: ReactNode; @@ -193,18 +194,50 @@ export const SecretSyncReviewFields = () => { {isChecking && Checking...}
{hasDuplicate && ( -
-
- +
+
+

- Another secret sync in your organization is already configured with the same - destination. This may lead to conflicts or unexpected behavior. + {currentOrg?.blockDuplicateSecretSyncDestinations + ? "Another secret sync in your organization is already configured with the same destination. Your organization does not allow duplicate destination configurations." + : "Another secret sync in your organization is already configured with the same destination. This may lead to conflicts or unexpected behavior."}

{duplicateProjectId && ( -

+

Duplicate found in project ID:{" "} - + {duplicateProjectId}

@@ -224,7 +257,7 @@ export const SecretSyncReviewFields = () => {
- + {isAutoSyncEnabled ? "Enabled" : "Disabled"} @@ -235,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/const/routes.ts b/frontend/src/const/routes.ts index c9a6b326d..5f199443d 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -301,10 +301,6 @@ export const ROUTE_PATHS = Object.freeze({ "/projects/cert-management/$projectId/subscribers", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers" ), - CertificatesPage: setRoute( - "/projects/cert-management/$projectId/certificates", - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates" - ), CertificateAuthoritiesPage: setRoute( "/projects/cert-management/$projectId/certificate-authorities", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities" 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/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index bbf73dd25..4340f9718 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -125,7 +125,8 @@ export const useUpdateOrg = () => { scannerProductEnabled, shareSecretsProductEnabled, maxSharedSecretLifetime, - maxSharedSecretViewLimit + maxSharedSecretViewLimit, + blockDuplicateSecretSyncDestinations }) => { return apiRequest.patch(`/api/v1/organization/${orgId}`, { name, @@ -146,7 +147,8 @@ export const useUpdateOrg = () => { scannerProductEnabled, shareSecretsProductEnabled, maxSharedSecretLifetime, - maxSharedSecretViewLimit + maxSharedSecretViewLimit, + blockDuplicateSecretSyncDestinations }); }, onSuccess: () => { diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 867450830..b366277f1 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -29,6 +29,7 @@ export type Organization = { shareSecretsProductEnabled: boolean; maxSharedSecretLifetime: number; maxSharedSecretViewLimit: number | null; + blockDuplicateSecretSyncDestinations: boolean; }; export type UpdateOrgDTO = { @@ -52,6 +53,7 @@ export type UpdateOrgDTO = { shareSecretsProductEnabled?: boolean; maxSharedSecretViewLimit?: number | null; maxSharedSecretLifetime?: number; + blockDuplicateSecretSyncDestinations?: boolean; }; export type BillingDetails = { 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 194ae5789..405b5d7f1 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx @@ -6,18 +6,17 @@ import { faEye, faFileExport, faLink, - 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, @@ -34,6 +33,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionCertificateActions, ProjectPermissionSub, @@ -60,7 +60,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; @@ -71,7 +71,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" }; } @@ -79,7 +79,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" }; } @@ -87,7 +87,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" }; } @@ -95,7 +95,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" }; } @@ -103,7 +103,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" }; } @@ -117,7 +117,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); @@ -135,11 +135,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 }; @@ -282,16 +282,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/PkiSyncCertificatesSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx index 11b59b6ab..b9c243bc4 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx @@ -7,7 +7,6 @@ import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CertificateManagementModal } from "@app/components/pki-syncs/CertificateManagementModal"; import { - Badge, EmptyState, IconButton, Pagination, @@ -20,6 +19,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; import { useListPkiSyncCertificates, useRemoveCertificatesFromPkiSync } from "@app/hooks/api"; @@ -32,7 +32,7 @@ type Props = { const getSyncStatusVariant = (status?: CertificateSyncStatus | null) => { if (status === CertificateSyncStatus.Succeeded) return "success"; if (status === CertificateSyncStatus.Failed) return "danger"; - if (status === CertificateSyncStatus.Syncing) return "primary"; + if (status === CertificateSyncStatus.Syncing) return "neutral"; return "project"; }; 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/OrgTabGroup/OrgTabGroup.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx index 10eb0f6c0..85d4b259c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx @@ -10,6 +10,7 @@ import { ExternalMigrationsTab } from "../ExternalMigrationsTab"; import { KmipTab } from "../KmipTab/OrgKmipTab"; import { OrgEncryptionTab } from "../OrgEncryptionTab"; import { OrgGeneralTab } from "../OrgGeneralTab"; +import { OrgProductSettingsTab } from "../OrgProductSettingsTab"; import { OrgProvisioningTab } from "../OrgProvisioningTab"; import { OrgSecurityTab } from "../OrgSecurityTab"; import { OrgSsoTab } from "../OrgSsoTab"; @@ -63,7 +64,12 @@ export const OrgTabGroup = () => { key: "project-templates", component: ProjectTemplatesTab }, - { name: "KMIP", key: "kmip", component: KmipTab } + { name: "KMIP", key: "kmip", component: KmipTab }, + { + name: "Product Enforcements", + key: "product-enforcements", + component: OrgProductSettingsTab + } ]; const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); 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/SettingsPage/components/index.tsx b/frontend/src/pages/organization/SettingsPage/components/index.tsx index 648613642..82909f799 100644 --- a/frontend/src/pages/organization/SettingsPage/components/index.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/index.tsx @@ -1 +1,2 @@ +export { OrgProductSettingsTab } from "./OrgProductSettingsTab"; export { OrgTabGroup } from "./OrgTabGroup"; 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 8e7507ac9..9c30bae0d 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx @@ -1,23 +1,20 @@ 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, RefreshCwIcon } 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 +26,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,23 +86,23 @@ export const PamAccountRow = ({ - - + + {isFlatView && accountPath && ( - - + + )} {account.lastRotatedAt && ( - - + + 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 +
    ))} diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 6dc265c43..4c6558db9 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -5760,7 +5760,6 @@ export interface FileRouteTypes { | '/organization/app-connections/$appConnection/oauth/callback' | '/projects/cert-management/$projectId/alerting' | '/projects/cert-management/$projectId/certificate-authorities' - | '/projects/cert-management/$projectId/certificates' | '/projects/cert-management/$projectId/policies' | '/projects/cert-management/$projectId/settings' | '/projects/kms/$projectId/kmip' @@ -5992,7 +5991,6 @@ export interface FileRouteTypes { | '/organization/app-connections/$appConnection/oauth/callback' | '/projects/cert-management/$projectId/alerting' | '/projects/cert-management/$projectId/certificate-authorities' - | '/projects/cert-management/$projectId/certificates' | '/projects/cert-management/$projectId/policies' | '/projects/cert-management/$projectId/settings' | '/projects/kms/$projectId/kmip' @@ -6234,7 +6232,6 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities' - | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/kmip' @@ -6870,7 +6867,6 @@ export const routeTree = rootRoute "children": [ "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities", - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management", @@ -6985,10 +6981,6 @@ export const routeTree = rootRoute "filePath": "cert-manager/CertificateAuthoritiesPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" }, - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates": { - "filePath": "cert-manager/CertificatesPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" - }, "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies": { "filePath": "cert-manager/PoliciesPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 16bdaf10f..cc37f3031 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -304,7 +304,6 @@ const certManagerRoutes = route("/projects/cert-management/$projectId", [ route("/$subscriberName", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx") ]), route("/certificate-templates", [index("cert-manager/PkiTemplateListPage/route.tsx")]), - route("/certificates", "cert-manager/CertificatesPage/route.tsx"), route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"), route("/alerting", "cert-manager/AlertingPage/route.tsx"), route("/ca/$caName", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),