diff --git a/backend/src/server/routes/v1/certificate-profiles-router.ts b/backend/src/server/routes/v1/certificate-profiles-router.ts index af32bd6c7..08f532bc4 100644 --- a/backend/src/server/routes/v1/certificate-profiles-router.ts +++ b/backend/src/server/routes/v1/certificate-profiles-router.ts @@ -121,9 +121,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid limit: z.coerce.number().min(1).max(100).default(20), search: z.string().optional(), enrollmentType: z.nativeEnum(EnrollmentType).optional(), - caId: z.string().uuid().optional(), - includeMetrics: z.coerce.boolean().optional().default(false), - expiringDays: z.coerce.number().min(1).max(365).optional().default(7) + caId: z.string().uuid().optional() }), response: { 200: z.object({ @@ -195,10 +193,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid params: z.object({ id: z.string().uuid() }), - querystring: z.object({ - includeMetrics: z.coerce.boolean().optional().default(false), - expiringDays: z.coerce.number().min(1).max(365).optional().default(7) - }), response: { 200: z.object({ certificateProfile: PkiCertificateProfilesSchema.extend({ @@ -232,16 +226,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid autoRenew: z.boolean(), renewBeforeDays: z.number().optional() }) - .optional(), - metrics: z - .object({ - profileId: z.string(), - totalCertificates: z.number(), - activeCertificates: z.number(), - expiredCertificates: z.number(), - expiringCertificates: z.number(), - revokedCertificates: z.number() - }) .optional() }) }) @@ -257,20 +241,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid profileId: req.params.id }); - let result = certificateProfile; - - if (req.query.includeMetrics) { - const metrics = await server.services.certificateProfile.getProfileMetrics({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - profileId: req.params.id, - expiringDays: req.query.expiringDays - }); - result = { ...certificateProfile, metrics }; - } - await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, projectId: certificateProfile.projectId, @@ -283,7 +253,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid } }); - return { certificateProfile: result }; + return { certificateProfile }; } }); diff --git a/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts b/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts index ecf7da4cc..204f38aa4 100644 --- a/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts +++ b/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts @@ -89,6 +89,7 @@ const PkiSyncCertificateSchema = z.object({ updatedAt: z.date(), certificateSerialNumber: z.string().optional(), certificateCommonName: z.string().optional(), + certificateAltNames: z.string().optional(), certificateStatus: z.string().optional(), certificateNotBefore: z.date().optional(), certificateNotAfter: z.date().optional(), diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index 1ffa3e295..b66475bbf 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -10,10 +10,8 @@ import { TCertificateProfile, TCertificateProfileCertificate, TCertificateProfileInsert, - TCertificateProfileMetrics, TCertificateProfileUpdate, - TCertificateProfileWithConfigs, - TCertificateProfileWithRawMetrics + TCertificateProfileWithConfigs } from "./certificate-profile-types"; export type TCertificateProfileDALFactory = ReturnType; @@ -203,21 +201,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => { search?: string; enrollmentType?: EnrollmentType; caId?: string; - includeMetrics?: boolean; - expiringDays?: number; } = {}, tx?: Knex - ): Promise => { + ): Promise => { try { - const { - offset = 0, - limit = 20, - search, - enrollmentType, - caId, - includeMetrics = false, - expiringDays = 7 - } = options; + const { offset = 0, limit = 20, search, enrollmentType, caId } = options; let baseQuery = (tx || db)(TableName.PkiCertificateProfile).where( `${TableName.PkiCertificateProfile}.projectId`, @@ -242,7 +230,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.caId`, caId); } - let query = baseQuery + const query = baseQuery .leftJoin( TableName.PkiEstEnrollmentConfig, `${TableName.PkiCertificateProfile}.estConfigId`, @@ -267,52 +255,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays") ); - if (includeMetrics) { - query = query.leftJoin( - TableName.Certificate, - `${TableName.PkiCertificateProfile}.id`, - `${TableName.Certificate}.profileId` - ); - - const now = new Date(); - const expiringDate = new Date(); - expiringDate.setDate(now.getDate() + expiringDays); - - query = query - .select( - selectAllTableCols(TableName.PkiCertificateProfile), - db.ref("id").withSchema(TableName.PkiEstEnrollmentConfig).as("estId"), - db - .ref("disableBootstrapCaValidation") - .withSchema(TableName.PkiEstEnrollmentConfig) - .as("estDisableBootstrapCaValidation"), - db.ref("hashedPassphrase").withSchema(TableName.PkiEstEnrollmentConfig).as("estHashedPassphrase"), - db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), - db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), - db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), - db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"), - db.raw("COUNT(certificates.id) as total_certificates"), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? THEN 1 END) as active_certificates', - [expiringDate] - ), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" <= ? THEN 1 END) as expired_certificates', - [now] - ), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? AND certificates."notAfter" <= ? THEN 1 END) as expiring_certificates', - [now, expiringDate] - ), - db.raw('COUNT(CASE WHEN certificates."revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates') - ) - .groupBy( - `${TableName.PkiCertificateProfile}.id`, - `${TableName.PkiEstEnrollmentConfig}.id`, - `${TableName.PkiApiEnrollmentConfig}.id` - ); - } - const results = (await query .orderBy(`${TableName.PkiCertificateProfile}.createdAt`, "desc") .offset(offset) @@ -353,17 +295,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { apiConfig }; - if (includeMetrics) { - return { - ...baseProfile, - total_certificates: result.total_certificates, - active_certificates: result.active_certificates, - expired_certificates: result.expired_certificates, - expiring_certificates: result.expiring_certificates, - revoked_certificates: result.revoked_certificates - } as TCertificateProfileWithRawMetrics & TCertificateProfileWithConfigs; - } - return baseProfile as TCertificateProfileWithConfigs; }); } catch (error) { @@ -485,45 +416,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } }; - const getProfileMetrics = async ( - profileId: string, - expiringDays: number = 7, - tx?: Knex - ): Promise => { - try { - const now = new Date(); - const expiringDate = new Date(); - expiringDate.setDate(now.getDate() + expiringDays); - - const metrics = await (tx || db)(TableName.Certificate) - .where("profileId", profileId) - .select( - db.raw("COUNT(*) as total_certificates"), - db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? THEN 1 END) as active_certificates', [ - expiringDate - ]), - db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" <= ? THEN 1 END) as expired_certificates', [now]), - db.raw( - 'COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? AND "notAfter" <= ? THEN 1 END) as expiring_certificates', - [now, expiringDate] - ), - db.raw('COUNT(CASE WHEN "revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates') - ) - .first(); - - return { - profileId, - totalCertificates: parseInt(String((metrics as Record)?.total_certificates || 0), 10), - activeCertificates: parseInt(String((metrics as Record)?.active_certificates || 0), 10), - expiredCertificates: parseInt(String((metrics as Record)?.expired_certificates || 0), 10), - expiringCertificates: parseInt(String((metrics as Record)?.expiring_certificates || 0), 10), - revokedCertificates: parseInt(String((metrics as Record)?.revoked_certificates || 0), 10) - }; - } catch (error) { - throw new DatabaseError({ error, name: "Get certificate profile metrics" }); - } - }; - const isProfileInUse = async (profileId: string, tx?: Knex) => { try { const doc = await (tx || db)(TableName.Certificate).where("profileId", profileId).count("*").first(); @@ -546,7 +438,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { countByProjectId, findByNameAndProjectId, getCertificatesByProfile, - getProfileMetrics, isProfileInUse }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-schemas.ts b/backend/src/services/certificate-profile/certificate-profile-schemas.ts index a2c391c2a..8ac494fe6 100644 --- a/backend/src/services/certificate-profile/certificate-profile-schemas.ts +++ b/backend/src/services/certificate-profile/certificate-profile-schemas.ts @@ -127,8 +127,3 @@ export const listCertificatesByProfileSchema = z.object({ status: z.enum(["active", "expired", "revoked"]).optional(), search: z.string().optional() }); - -export const getCertificateProfileMetricsSchema = z.object({ - profileId: z.string().uuid(), - expiringDays: z.coerce.number().min(1).max(365).default(30) -}); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index 26b1e976a..bb30b8d5c 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -47,7 +47,6 @@ describe("CertificateProfileService", () => { findByNameAndProjectId: vi.fn(), findByIdWithConfigs: vi.fn(), getCertificatesByProfile: vi.fn(), - getProfileMetrics: vi.fn(), isProfileInUse: vi.fn(), transaction: vi.fn(), find: vi.fn(), @@ -493,9 +492,7 @@ describe("CertificateProfileService", () => { limit: 20, search: undefined, enrollmentType: undefined, - caId: undefined, - includeMetrics: false, - expiringDays: 30 + caId: undefined }); }); @@ -515,51 +512,7 @@ describe("CertificateProfileService", () => { limit: 5, search: "test", enrollmentType: EnrollmentType.API, - caId: "ca-123", - includeMetrics: false, - expiringDays: 30 - }); - }); - - it("should list profiles with metrics when includeMetrics is true", async () => { - const mockProfilesWithMetrics = [ - { - ...sampleProfile, - total_certificates: 10, - active_certificates: 8, - expired_certificates: 1, - expiring_certificates: 1, - revoked_certificates: 0 - } - ]; - (mockCertificateProfileDAL.findByProjectId as any).mockResolvedValue(mockProfilesWithMetrics); - - const result = await service.listProfiles({ - ...mockActor, - projectId: "project-123", - includeMetrics: true, - expiringDays: 15 - }); - - expect(result.profiles).toHaveLength(1); - expect(result.profiles[0]).toHaveProperty("metrics"); - expect(result.profiles[0].metrics).toEqual({ - profileId: sampleProfile.id, - totalCertificates: 10, - activeCertificates: 8, - expiredCertificates: 1, - expiringCertificates: 1, - revokedCertificates: 0 - }); - - expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith("project-123", { - offset: 0, - limit: 20, - search: undefined, - enrollmentType: undefined, - caId: undefined, - includeMetrics: true, - expiringDays: 15 + caId: "ca-123" }); }); }); @@ -659,54 +612,6 @@ describe("CertificateProfileService", () => { }); }); - describe("getProfileMetrics", () => { - const mockMetrics = { - profileId: "profile-123", - totalCertificates: 10, - activeCertificates: 8, - expiredCertificates: 1, - expiringCertificates: 2, - revokedCertificates: 1 - }; - - beforeEach(() => { - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(mockMetrics); - }); - - it("should get profile metrics successfully", async () => { - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }); - - expect(result).toEqual(mockMetrics); - expect(mockCertificateProfileDAL.findById).toHaveBeenCalledWith("profile-123"); - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 30); - }); - - it("should get profile metrics with custom expiring days", async () => { - await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123", - expiringDays: 60 - }); - - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 60); - }); - - it("should throw NotFoundError when profile not found", async () => { - (mockCertificateProfileDAL.findById as any).mockResolvedValue(null); - - await expect( - service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }) - ).rejects.toThrow(NotFoundError); - }); - }); - describe("comprehensive certificate profile scenarios", () => { describe("profile configuration validation", () => { it("should validate EST enrollment configuration", async () => { @@ -929,53 +834,6 @@ describe("CertificateProfileService", () => { }); }); - describe("metrics and monitoring", () => { - it("should calculate profile metrics correctly", async () => { - const detailedMetrics = { - profileId: "profile-123", - totalCertificates: 50, - activeCertificates: 40, - expiredCertificates: 5, - expiringCertificates: 3, - revokedCertificates: 2 - }; - - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(detailedMetrics); - - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123", - expiringDays: 14 - }); - - expect(result).toEqual(detailedMetrics); - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 14); - }); - - it("should handle zero certificate metrics", async () => { - const emptyMetrics = { - profileId: "profile-123", - totalCertificates: 0, - activeCertificates: 0, - expiredCertificates: 0, - expiringCertificates: 0, - revokedCertificates: 0 - }; - - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(emptyMetrics); - - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }); - - expect(result.totalCertificates).toBe(0); - expect(result.activeCertificates).toBe(0); - }); - }); - describe("error scenarios", () => { it("should handle database connection errors gracefully", async () => { (mockCertificateProfileDAL.findById as any).mockRejectedValue(new Error("Database connection failed")); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index 7b48af8f1..f858a8d4f 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -27,10 +27,8 @@ import { TCertificateProfile, TCertificateProfileCertificate, TCertificateProfileInsert, - TCertificateProfileMetrics, TCertificateProfileUpdate, - TCertificateProfileWithConfigs, - TCertificateProfileWithRawMetrics + TCertificateProfileWithConfigs } from "./certificate-profile-types"; const validateAndEncryptPemCaChain = async ( @@ -361,18 +359,14 @@ export const certificateProfileServiceFactory = ({ actorId, actorAuthMethod, actorOrgId, - profileId, - includeMetrics = false, - expiringDays = 30 + profileId }: { actor: ActorType; actorId: string; actorAuthMethod: ActorAuthMethod; actorOrgId: string; profileId: string; - includeMetrics?: boolean; - expiringDays?: number; - }): Promise => { + }): Promise => { const profile = await certificateProfileDAL.findById(profileId); if (!profile) { throw new NotFoundError({ message: "Certificate profile not found" }); @@ -393,14 +387,6 @@ export const certificateProfileServiceFactory = ({ const converted = convertDalToService(profile); - if (includeMetrics) { - const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays); - return { - ...converted, - metrics - }; - } - return converted; }; @@ -506,9 +492,7 @@ export const certificateProfileServiceFactory = ({ limit = 20, search, enrollmentType, - caId, - includeMetrics = false, - expiringDays = 30 + caId }: { actor: ActorType; actorId: string; @@ -520,10 +504,8 @@ export const certificateProfileServiceFactory = ({ search?: string; enrollmentType?: EnrollmentType; caId?: string; - includeMetrics?: boolean; - expiringDays?: number; }): Promise<{ - profiles: (TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics })[]; + profiles: TCertificateProfileWithConfigs[]; totalCount: number; }> => { const { permission } = await permissionService.getProjectPermission({ @@ -544,9 +526,7 @@ export const certificateProfileServiceFactory = ({ limit, search, enrollmentType, - caId, - includeMetrics, - expiringDays + caId }); const totalCount = await certificateProfileDAL.countByProjectId(projectId, { @@ -591,27 +571,12 @@ export const certificateProfileServiceFactory = ({ } const converted = convertDalToService(profileWithConfigs); - let result: TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics } = { + const result: TCertificateProfileWithConfigs = { ...converted, estConfig: decryptedEstConfig, apiConfig: profileWithConfigs.apiConfig }; - if (includeMetrics) { - const profileWithMetrics = profile as TCertificateProfileWithRawMetrics; - result = { - ...result, - metrics: { - profileId: converted.id, - totalCertificates: parseInt(String(profileWithMetrics.total_certificates || 0), 10), - activeCertificates: parseInt(String(profileWithMetrics.active_certificates || 0), 10), - expiredCertificates: parseInt(String(profileWithMetrics.expired_certificates || 0), 10), - expiringCertificates: parseInt(String(profileWithMetrics.expiring_certificates || 0), 10), - revokedCertificates: parseInt(String(profileWithMetrics.revoked_certificates || 0), 10) - } - }; - } - return result; }) ); @@ -709,43 +674,6 @@ export const certificateProfileServiceFactory = ({ return certificates; }; - const getProfileMetrics = async ({ - actor, - actorId, - actorAuthMethod, - actorOrgId, - profileId, - expiringDays = 30 - }: { - actor: ActorType; - actorId: string; - actorAuthMethod: ActorAuthMethod; - actorOrgId: string; - profileId: string; - expiringDays?: number; - }): Promise => { - const profile = await certificateProfileDAL.findById(profileId); - if (!profile) { - throw new NotFoundError({ message: "Certificate profile not found" }); - } - - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: profile.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionCertificateProfileActions.Read, - ProjectPermissionSub.CertificateProfiles - ); - - const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays); - return metrics; - }; - const getEstConfigurationByProfile = async ( params: | { @@ -818,7 +746,6 @@ export const certificateProfileServiceFactory = ({ listProfiles, deleteProfile, getProfileCertificates, - getProfileMetrics, getEstConfigurationByProfile }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-types.ts b/backend/src/services/certificate-profile/certificate-profile-types.ts index 1c22a5e75..5dac470c8 100644 --- a/backend/src/services/certificate-profile/certificate-profile-types.ts +++ b/backend/src/services/certificate-profile/certificate-profile-types.ts @@ -54,18 +54,8 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & { autoRenew: boolean; renewBeforeDays?: number; }; - metrics?: TCertificateProfileMetrics; }; -export interface TCertificateProfileMetrics { - profileId: string; - totalCertificates: number; - activeCertificates: number; - expiredCertificates: number; - expiringCertificates: number; - revokedCertificates: number; -} - export interface TCertificateProfileCertificate { id: string; serialNumber: string; @@ -76,11 +66,3 @@ export interface TCertificateProfileCertificate { revokedAt: Date | null; createdAt: Date; } - -export type TCertificateProfileWithRawMetrics = TCertificateProfile & { - total_certificates?: string; - active_certificates?: string; - expired_certificates?: string; - expiring_certificates?: string; - revoked_certificates?: string; -}; diff --git a/backend/src/services/certificate-sync/certificate-sync-dal.ts b/backend/src/services/certificate-sync/certificate-sync-dal.ts index 278641844..174bc6141 100644 --- a/backend/src/services/certificate-sync/certificate-sync-dal.ts +++ b/backend/src/services/certificate-sync/certificate-sync-dal.ts @@ -180,6 +180,7 @@ export const certificateSyncDALFactory = (db: TDbClient) => { certificateDetails: (TCertificateSyncs & { certificateSerialNumber?: string; certificateCommonName?: string; + certificateAltNames?: string; certificateStatus?: string; certificateNotBefore?: Date; certificateNotAfter?: Date; @@ -211,6 +212,7 @@ export const certificateSyncDALFactory = (db: TDbClient) => { .select( db.ref("serialNumber").withSchema(TableName.Certificate).as("certificateSerialNumber"), db.ref("commonName").withSchema(TableName.Certificate).as("certificateCommonName"), + db.ref("altNames").withSchema(TableName.Certificate).as("certificateAltNames"), db.ref("status").withSchema(TableName.Certificate).as("certificateStatus"), db.ref("notBefore").withSchema(TableName.Certificate).as("certificateNotBefore"), db.ref("notAfter").withSchema(TableName.Certificate).as("certificateNotAfter"), @@ -229,6 +231,7 @@ export const certificateSyncDALFactory = (db: TDbClient) => { const certificateDetails = (await query) as (TCertificateSyncs & { certificateSerialNumber?: string; certificateCommonName?: string; + certificateAltNames?: string; certificateStatus?: string; certificateNotBefore?: Date; certificateNotAfter?: Date; diff --git a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts index 2f10afbdd..774da6000 100644 --- a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts +++ b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts @@ -520,7 +520,7 @@ export const awsCertificateManagerPkiSyncFactory = ({ try { // Small delay to ensure AWS ACM has processed the certificate import await new Promise((resolve) => { - setTimeout(() => resolve(), 100); + setTimeout(() => resolve(), 500); }); await withRateLimitRetry( diff --git a/backend/src/services/pki-sync/pki-sync-service.ts b/backend/src/services/pki-sync/pki-sync-service.ts index a04d27580..c3bda528e 100644 --- a/backend/src/services/pki-sync/pki-sync-service.ts +++ b/backend/src/services/pki-sync/pki-sync-service.ts @@ -606,6 +606,7 @@ export const pkiSyncServiceFactory = ({ updatedAt: detail.updatedAt, certificateSerialNumber: detail.certificateSerialNumber || undefined, certificateCommonName: detail.certificateCommonName || undefined, + certificateAltNames: detail.certificateAltNames || undefined, certificateStatus: detail.certificateStatus || undefined, certificateNotBefore: detail.certificateNotBefore || undefined, certificateNotAfter: detail.certificateNotAfter || undefined, diff --git a/backend/src/services/pki-sync/pki-sync-types.ts b/backend/src/services/pki-sync/pki-sync-types.ts index e51facc8e..a9e236470 100644 --- a/backend/src/services/pki-sync/pki-sync-types.ts +++ b/backend/src/services/pki-sync/pki-sync-types.ts @@ -170,13 +170,14 @@ export type TPkiSyncCertificate = { lastSyncedAt?: Date; createdAt: Date; updatedAt: Date; - certificate?: { - serialNumber: string; - commonName: string; - status: string; - notBefore: Date; - notAfter: Date; - }; + certificateSerialNumber?: string; + certificateCommonName?: string; + certificateAltNames?: string; + certificateStatus?: string; + certificateNotBefore?: Date; + certificateNotAfter?: Date; + pkiSyncName?: string; + pkiSyncDestination?: string; }; export type TPkiSyncRaw = NonNullable>>; diff --git a/frontend/src/components/pki-syncs/CertificateManagementModal.tsx b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx index 760802b13..f5fd72309 100644 --- a/frontend/src/components/pki-syncs/CertificateManagementModal.tsx +++ b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx @@ -17,9 +17,9 @@ import { Td, Th, THead, + Tooltip, Tr } from "@app/components/v2"; -import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { CertStatus, @@ -299,119 +299,129 @@ export const CertificateManagementModal = ({ - {allCertificates.length === 0 ? ( - - {searchTerm - ? "No certificates match your search criteria." - : "No certificates available for sync."} - - ) : ( - <> - - - - - - - - - - - - - {allCertificates.map((cert) => { - const isExpired = new Date(cert.notAfter) < new Date(); - const isRevoked = cert.status === CertStatus.REVOKED; - const cannotBeAdded = isExpired || isRevoked; - const isAlreadySynced = syncedCertificateIds.includes(cert.id); + +
- 0 && - allCertificates.every((cert) => selectedIds.includes(cert.id)) - } - onCheckedChange={handleSelectAll} - /> - Common NameSerial NumberStatusExpires
+ + + + + + + + + + + {allCertificates.map((cert) => { + const isExpired = new Date(cert.notAfter) < new Date(); + const isRevoked = cert.status === CertStatus.REVOKED; + const cannotBeAdded = isExpired || isRevoked; + const isAlreadySynced = syncedCertificateIds.includes(cert.id); - return ( - { + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + return ( + { + if (!cannotBeAdded || isAlreadySynced) { + handleToggleSelection(cert.id); + } + }} + > + + + - - - - - - ); - })} - -
+ 0 && + allCertificates.every((cert) => selectedIds.includes(cert.id)) + } + onCheckedChange={handleSelectAll} + /> + SAN / CNSerial NumberIssued AtExpires At
e.stopPropagation()}> + { if (!cannotBeAdded || isAlreadySynced) { handleToggleSelection(cert.id); } }} + isDisabled={cannotBeAdded && !isAlreadySynced} + /> + + {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
-
- { - if (!cannotBeAdded || isAlreadySynced) { - handleToggleSelection(cert.id); - } - }} - isDisabled={cannotBeAdded && !isAlreadySynced} - /> - -
- {cert.commonName} -
-
-
- {cert.serialNumber} -
-
- - {(() => { - if (isRevoked) return "Revoked"; - if (isExpired) return "Expired"; - return cert.status === CertStatus.ACTIVE ? "Active" : cert.status; - })()} - - - - {new Date(cert.notAfter).toLocaleDateString()} - -
-
+ {truncatedSerial} + + + + + {new Date(cert.notBefore).toLocaleDateString()} + + + + + {new Date(cert.notAfter).toLocaleDateString()} + + + + ); + })} + + + {allCertificates.length === 0 && ( + + {searchTerm + ? "No certificates match your search criteria." + : "No certificates available for sync."} + + )} + - {totalPages > 1 && ( -
- setCurrentPage(page)} - onChangePerPage={() => {}} - /> -
- )} - + {totalPages > 1 && ( +
+ setCurrentPage(page)} + onChangePerPage={() => {}} + /> +
)} diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx index 51a93f4c9..a5abcb60b 100644 --- a/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx +++ b/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; -import { faEdit, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faCertificate, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -13,6 +13,7 @@ import { Td, Th, THead, + Tooltip, Tr } from "@app/components/v2"; import { useProject } from "@app/context"; @@ -76,25 +77,71 @@ export const PkiSyncCertificatesFields = () => { > Add Certificates - {selectedCertificates.length === 0 ? ( - - ) : ( -
- - - - - - - - - - - {selectedCertificates.map((cert) => ( +
+ +
Common NameSerial NumberRemove
+ + + + + + + + + + + {selectedCertificates.map((cert) => { + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + const isExpired = new Date(cert.notAfter) < new Date(); + + return ( - - + + + - ))} - -
SAN / CNSerial NumberIssued AtExpires AtRemove
{cert.commonName} - {cert.serialNumber} + + {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
+ {truncatedSerial} +
+
+ + {new Date(cert.notBefore).toLocaleDateString()} + + + + {new Date(cert.notAfter).toLocaleDateString()} +
-
-
- )} + ); + })} + + + {selectedCertificates.length === 0 && ( + + )} + + )} diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx index 8119ac23a..61bf047ba 100644 --- a/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx +++ b/frontend/src/components/pki-syncs/forms/PkiSyncReviewFields.tsx @@ -1,6 +1,16 @@ import { useFormContext } from "react-hook-form"; -import { GenericFieldLabel } from "@app/components/v2"; +import { + GenericFieldLabel, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; import { Badge } from "@app/components/v3"; import { useProject } from "@app/context"; import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; @@ -45,20 +55,71 @@ export const PkiSyncReviewFields = () => {
Certificates
-
-
- {selectedCertificates.length === 0 ? ( - No certificates selected - ) : ( -
- {selectedCertificates.map((cert) => ( -
- {cert.commonName} -
- ))} -
- )} -
+
+ {selectedCertificates.length === 0 ? ( + No certificates selected + ) : ( + + + + + + + + + + + {selectedCertificates.map((cert) => { + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + return ( + + + + + + ); + })} + +
SAN / CNSerial NumberExpires At
+ {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
+ {truncatedSerial} +
+
+ + {new Date(cert.notAfter).toLocaleDateString()} + +
+
+ )}
@@ -79,11 +140,13 @@ export const PkiSyncReviewFields = () => {
Sync Options
-
+
- - {isAutoSyncEnabled ? "Enabled" : "Disabled"} - +
+ + {isAutoSyncEnabled ? "Enabled" : "Disabled"} + +
{/* Hidden for now - Import certificates functionality disabled {syncOptions?.canImportCertificates !== undefined && ( @@ -96,9 +159,11 @@ export const PkiSyncReviewFields = () => { */} {syncOptions?.canRemoveCertificates !== undefined && ( - - {syncOptions.canRemoveCertificates ? "Enabled" : "Disabled"} - +
+ + {syncOptions.canRemoveCertificates ? "Enabled" : "Disabled"} + +
)}
diff --git a/frontend/src/components/utilities/certificateDisplayUtils.tsx b/frontend/src/components/utilities/certificateDisplayUtils.tsx new file mode 100644 index 000000000..4a2b2de26 --- /dev/null +++ b/frontend/src/components/utilities/certificateDisplayUtils.tsx @@ -0,0 +1,100 @@ +import { ReactNode } from "react"; + +import { Tooltip } from "@app/components/v2"; + +interface CertificateNameData { + altNames?: string | null; + commonName?: string | null; + certificateAltNames?: string | null; + certificateCommonName?: string | null; +} + +interface DisplayNameResult { + originalDisplayName: string; + displayName: string; + isTruncated: boolean; +} + +/** + * Extracts and formats the display name for a certificate from SAN/CN data + * @param cert - Certificate object with potential altNames/commonName fields + * @param maxLength - Maximum length before truncating (default: 64) + * @param fallback - Fallback text when no name is found (default: "—") + * @returns Object with original name, truncated name, and truncation flag + */ +export const getCertificateDisplayName = ( + cert: CertificateNameData, + maxLength: number = 64, + fallback: string = "—" +): DisplayNameResult => { + // Extract original display name - prioritize SAN over CN + let originalDisplayName = fallback; + + // Handle different property name variations + const altNames = cert.altNames || cert.certificateAltNames; + const commonName = cert.commonName || cert.certificateCommonName; + + if (altNames && altNames.trim()) { + originalDisplayName = altNames.trim(); + } else if (commonName && commonName.trim()) { + originalDisplayName = commonName.trim(); + } + + // Handle truncation + let displayName = originalDisplayName; + let isTruncated = false; + + if (originalDisplayName.length > maxLength) { + displayName = `${originalDisplayName.substring(0, maxLength)}...`; + isTruncated = true; + } + + return { + originalDisplayName, + displayName, + isTruncated + }; +}; + +/** + * Renders a certificate display name with optional tooltip for truncated names + * @param cert - Certificate object with potential altNames/commonName fields + * @param maxLength - Maximum length before truncating (default: 64) + * @param fallback - Fallback text when no name is found (default: "—") + * @param className - Optional CSS class for the display element + * @param tooltipClassName - Optional CSS class for the tooltip (default: "max-w-lg") + * @returns JSX element with certificate name and optional tooltip + */ +export const CertificateDisplayName = ({ + cert, + maxLength = 64, + fallback = "—", + className = "truncate", + tooltipClassName = "max-w-lg" +}: { + cert: CertificateNameData; + maxLength?: number; + fallback?: string; + className?: string; + tooltipClassName?: string; +}): ReactNode => { + const { originalDisplayName, displayName, isTruncated } = getCertificateDisplayName( + cert, + maxLength, + fallback + ); + + if (isTruncated) { + return ( + +
{displayName}
+
+ ); + } + + return ( +
+ {displayName} +
+ ); +}; diff --git a/frontend/src/hooks/api/ca/mutations.tsx b/frontend/src/hooks/api/ca/mutations.tsx index fa422054c..49a53dce7 100644 --- a/frontend/src/hooks/api/ca/mutations.tsx +++ b/frontend/src/hooks/api/ca/mutations.tsx @@ -152,7 +152,7 @@ export const useCreateCertificate = () => { }); }; -export const useCreateCertificateV3 = () => { +export const useCreateCertificateV3 = (options?: { projectId?: string }) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (body) => { @@ -167,6 +167,12 @@ export const useCreateCertificateV3 = () => { queryKey: projectKeys.forProjectCertificates(projectSlug) }); + if (options?.projectId) { + queryClient.invalidateQueries({ + queryKey: projectKeys.forProjectCertificates(options.projectId) + }); + } + queryClient.invalidateQueries({ queryKey: ["certificate-profiles"] }); diff --git a/frontend/src/hooks/api/certificateProfiles/index.ts b/frontend/src/hooks/api/certificateProfiles/index.ts index dc5c17efa..e12e066c4 100644 --- a/frontend/src/hooks/api/certificateProfiles/index.ts +++ b/frontend/src/hooks/api/certificateProfiles/index.ts @@ -8,7 +8,6 @@ export { useGetCertificateProfileById, useGetCertificateProfileBySlug, useGetProfileCertificates, - useGetProfileMetrics, useListCertificateProfiles } from "./queries"; export type * from "./types"; diff --git a/frontend/src/hooks/api/certificateProfiles/queries.tsx b/frontend/src/hooks/api/certificateProfiles/queries.tsx index abdc93ddb..19859b02f 100644 --- a/frontend/src/hooks/api/certificateProfiles/queries.tsx +++ b/frontend/src/hooks/api/certificateProfiles/queries.tsx @@ -4,7 +4,6 @@ import { apiRequest } from "@app/config/request"; import { TCertificateProfile, - TCertificateProfileMetrics, TCertificateProfileWithDetails, TGetCertificateProfileByIdDTO, TGetCertificateProfileBySlugDTO, @@ -20,7 +19,6 @@ export const certificateProfileKeys = { limit?: number; offset?: number; search?: string; - includeMetrics?: boolean; includeConfigs?: boolean; enrollmentType?: string; expiringDays?: number; @@ -51,10 +49,8 @@ export const useListCertificateProfiles = ({ limit = 20, offset = 0, search, - includeMetrics = false, includeConfigs = false, - enrollmentType, - expiringDays = 7 + enrollmentType }: TListCertificateProfilesDTO) => { return useQuery({ queryKey: certificateProfileKeys.list({ @@ -62,10 +58,8 @@ export const useListCertificateProfiles = ({ limit, offset, search, - includeMetrics, includeConfigs, - enrollmentType, - expiringDays + enrollmentType }), queryFn: async () => { const { data } = await apiRequest.get<{ @@ -77,10 +71,8 @@ export const useListCertificateProfiles = ({ limit, offset, search, - includeMetrics, includeConfigs, - enrollmentType, - expiringDays + enrollmentType } }); return data; @@ -145,18 +137,3 @@ export const useGetProfileCertificates = ({ enabled: Boolean(profileId) }); }; - -export const useGetProfileMetrics = ({ profileId, expiringDays = 7 }: TGetProfileMetricsDTO) => { - return useQuery({ - queryKey: certificateProfileKeys.getMetrics(profileId, { expiringDays }), - queryFn: async () => { - const { data } = await apiRequest.get<{ - metrics: TCertificateProfileMetrics; - }>(`/api/v1/pki/certificate-profiles/${profileId}/metrics`, { - params: { expiringDays } - }); - return data.metrics; - }, - enabled: Boolean(profileId) - }); -}; diff --git a/frontend/src/hooks/api/certificateProfiles/types.ts b/frontend/src/hooks/api/certificateProfiles/types.ts index b5c53e11b..f3584b12d 100644 --- a/frontend/src/hooks/api/certificateProfiles/types.ts +++ b/frontend/src/hooks/api/certificateProfiles/types.ts @@ -10,7 +10,6 @@ export type TCertificateProfile = { apiConfigId?: string; createdAt: string; updatedAt: string; - metrics?: TCertificateProfileMetrics; }; export type TCertificateProfileWithDetails = TCertificateProfile & { @@ -81,10 +80,8 @@ export type TListCertificateProfilesDTO = { limit?: number; offset?: number; search?: string; - includeMetrics?: boolean; includeConfigs?: boolean; enrollmentType?: "api" | "est"; - expiringDays?: number; }; export type TGetCertificateProfileByIdDTO = { @@ -96,15 +93,6 @@ export type TGetCertificateProfileBySlugDTO = { slug: string; }; -export type TCertificateProfileMetrics = { - profileId: string; - totalCertificates: number; - activeCertificates: number; - expiredCertificates: number; - expiringCertificates: number; - revokedCertificates: number; -}; - export type TProfileCertificate = { id: string; serialNumber: string; @@ -126,5 +114,4 @@ export type TGetProfileCertificatesDTO = { export type TGetProfileMetricsDTO = { profileId: string; - expiringDays?: number; }; diff --git a/frontend/src/hooks/api/certificates/types.ts b/frontend/src/hooks/api/certificates/types.ts index 622276e24..adfb815a6 100644 --- a/frontend/src/hooks/api/certificates/types.ts +++ b/frontend/src/hooks/api/certificates/types.ts @@ -9,6 +9,7 @@ export type TCertificate = { friendlyName: string; commonName: string; subjectAltNames: string; + altNames?: string; serialNumber: string; notBefore: string; notAfter: string; diff --git a/frontend/src/hooks/api/pkiSyncs/types/common.ts b/frontend/src/hooks/api/pkiSyncs/types/common.ts index 556cd0aa3..db6a6c4db 100644 --- a/frontend/src/hooks/api/pkiSyncs/types/common.ts +++ b/frontend/src/hooks/api/pkiSyncs/types/common.ts @@ -57,6 +57,7 @@ export type TPkiSyncCertificate = { updatedAt: string; certificateSerialNumber?: string; certificateCommonName?: string; + certificateAltNames?: string; certificateStatus?: string; certificateNotBefore?: Date; certificateNotAfter?: Date; diff --git a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx index c33821ed9..7aeb5e1fd 100644 --- a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx +++ b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx @@ -52,9 +52,7 @@ export const PkiManagerLayout = () => { projectId: currentProject.id }} > - {({ isActive }) => ( - Certificate Management - )} + {({ isActive }) => Certificates} { return zodResolver(createSchema(shouldShowSubjectSection)); diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx index 405b5d7f1..85cb118db 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesTable.tsx @@ -2,6 +2,7 @@ import { useMemo, useState } from "react"; import { faBan, faCertificate, + faClockRotateLeft, faEllipsis, faEye, faFileExport, @@ -11,11 +12,14 @@ import { } 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 { + CertificateDisplayName, + getCertificateDisplayName +} from "@app/components/utilities/certificateDisplayUtils"; import { DropdownMenu, DropdownMenuContent, @@ -45,7 +49,6 @@ import { caSupportsCapability } from "@app/hooks/api/ca/constants"; import { CaCapability, CaType } from "@app/hooks/api/ca/enums"; import { useListCasByProjectId } from "@app/hooks/api/ca/queries"; import { CertStatus } from "@app/hooks/api/certificates/enums"; -import { TCertificate } from "@app/hooks/api/certificates/types"; import { useListWorkspaceCertificates } from "@app/hooks/api/projects"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -58,93 +61,6 @@ const isExpiringWithinOneDay = (notAfter: string): boolean => { return expiryDate <= oneDayFromNow; }; -const getAutoRenewalInfo = (certificate: TCertificate) => { - if (certificate.renewedByCertificateId) { - return { text: "Renewed", variant: "neutral" as const }; - } - - const isRevoked = certificate.status === CertStatus.REVOKED; - const isExpired = new Date(certificate.notAfter) < new Date(); - const hasNoProfile = !certificate.profileId; - const isExpiringWithinDay = isExpiringWithinOneDay(certificate.notAfter); - - if (isRevoked) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for revoked certificates" - }; - } - - if (isExpired) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for expired certificates" - }; - } - - if (hasNoProfile) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal requires a certificate profile" - }; - } - - if (certificate.hasPrivateKey === false) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for certificates with externally generated private keys" - }; - } - - if (isExpiringWithinDay) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Auto-renewal is not available for certificates expiring within 24 hours" - }; - } - - if (certificate.renewalError) { - return { - text: "Failed", - variant: "danger" as const, - tooltip: certificate.renewalError - }; - } - - if (!certificate.renewBeforeDays) { - return { text: "Auto-Renewal Disabled", variant: "warning" as const }; - } - - const notAfterDate = new Date(certificate.notAfter); - const renewalDate = new Date( - notAfterDate.getTime() - certificate.renewBeforeDays * 24 * 60 * 60 * 1000 - ); - const now = new Date(); - - if (renewalDate <= now) { - return { text: "Due Now", variant: "danger" as const }; - } - - const daysUntilRenewal = Math.floor( - (renewalDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) - ); - - if (daysUntilRenewal === 0) { - return { text: "Renews today", variant: "warning" as const }; - } - - if (daysUntilRenewal <= 7) { - return { text: `Renews in ${daysUntilRenewal}d`, variant: "warning" as const }; - } - - return { text: `Renews in ${daysUntilRenewal}d`, variant: "success" as const }; -}; - type Props = { handlePopUpOpen: ( popUpName: keyof UsePopUpState< @@ -236,20 +152,18 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { - - - - - - + + + + - {isPending && } + {isPending && } {!isPending && data?.certificates.map((certificate) => { const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter); - const autoRenewalInfo = getAutoRenewalInfo(certificate); const isRevoked = certificate.status === CertStatus.REVOKED; const isExpired = new Date(certificate.notAfter) < new Date(); @@ -258,9 +172,24 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const isAutoRenewalEnabled = Boolean( certificate.renewBeforeDays && certificate.renewBeforeDays > 0 ); + + const canShowAutoRenewalIcon = Boolean( + certificate.profileId && + certificate.hasPrivateKey !== false && + !certificate.renewedByCertificateId && + !isRevoked && + !isExpired && + !isExpiringWithinDay + ); + + // Still need originalDisplayName for other uses in the component + const { originalDisplayName } = getCertificateDisplayName(certificate, 64, "—"); + return ( - - + + - - - {subscriberId ? ( - - ) : ( - - )} - - - + ); + })} + +
Common NameStatusNot BeforeNot AfterRenewal Status + SAN / CNStatusNot BeforeNot After
{certificate.commonName}
+ + {certificate.status === CertStatus.REVOKED ? ( Revoked @@ -278,22 +207,64 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { ? format(new Date(certificate.notAfter), "yyyy-MM-dd") : "-"} - {autoRenewalInfo && - (autoRenewalInfo.tooltip ? ( -
- - - {autoRenewalInfo.text} - - - -
- ) : ( - {autoRenewalInfo.text} - ))} -
+ +
{ + if (!canShowAutoRenewalIcon) return ""; + if (isAutoRenewalEnabled) return "opacity-100"; + return "opacity-0 group-hover:opacity-100"; + })()}`} + > + {canShowAutoRenewalIcon && ( + { + if (hasFailed && certificate.renewalError) { + return `Auto-renewal failed: ${certificate.renewalError}`; + } + if (isAutoRenewalEnabled) { + const expiryDate = new Date(certificate.notAfter); + const now = new Date(); + const daysUntilExpiry = Math.ceil( + (expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) + ); + const daysUntilRenewal = Math.max( + 0, + daysUntilExpiry - (certificate.renewBeforeDays || 0) + ); + return `Auto-renews in ${daysUntilRenewal}d`; + } + return "Set auto renewal"; + })()} + > + + + )} +
@@ -370,20 +341,20 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { { const notAfterDate = new Date(certificate.notAfter); const notBeforeDate = certificate.notBefore ? new Date(certificate.notBefore) : new Date( - notAfterDate.getTime() - 365 * 24 * 60 * 60 * 1000 - ); + notAfterDate.getTime() - 365 * 24 * 60 * 60 * 1000 + ); const ttlDays = Math.max( 1, Math.ceil( (notAfterDate.getTime() - notBeforeDate.getTime()) / - (24 * 60 * 60 * 1000) + (24 * 60 * 60 * 1000) ) ); handlePopUpOpen("manageRenewal", { @@ -433,7 +404,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { { await handleDisableAutoRenewal( @@ -470,7 +441,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { { handlePopUpOpen("renewCertificate", { @@ -532,7 +503,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { handlePopUpOpen("revokeCertificate", { 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 1c5bff98c..0b2ce65c1 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 @@ -12,7 +12,6 @@ import { faToggleOff, faToggleOn, faTrash, - faTriangleExclamation, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -47,7 +46,6 @@ import { useToggle } from "@app/hooks"; import { PkiSyncStatus, TPkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs"; import { PkiSyncDestinationCol } from "./PkiSyncDestinationCol"; -import { PkiSyncTableCell } from "./PkiSyncTableCell"; type Props = { pkiSync: TPkiSync; @@ -163,23 +161,6 @@ export const PkiSyncRow = ({

{destinationDetails.name}

- -
- - - Source Deleted - -
-
-
diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx index ca4aa7473..178d07e0d 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx @@ -57,7 +57,6 @@ import { PkiSyncRow } from "./PkiSyncRow"; enum PkiSyncsOrderBy { Destination = "destination", - Source = "source", Name = "name", Status = "status" } @@ -160,14 +159,6 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => { const [syncOne, syncTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; switch (orderBy) { - case PkiSyncsOrderBy.Source: - return (syncOne.subscriber?.name ?? syncOne.subscriberId ?? "") - .toLowerCase() - .localeCompare( - syncTwo.subscriber?.name?.toLowerCase() ?? - syncTwo.subscriberId?.toLowerCase() ?? - "" - ); case PkiSyncsOrderBy.Destination: return getPkiSyncDestinationColValues(syncOne) .primaryText.toLowerCase() @@ -370,7 +361,7 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => {
- +
Name {
-
- Source - handleSort(PkiSyncsOrderBy.Source)} - > - - -
-
+
Destination {
+
Status { return (
-

Sync Logs

+

Sync Logs

{subscription.auditLogs && (

Displaying audit logs from the last {Math.min(auditLogsRetentionDays, 60)} days diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx index b9c243bc4..2fd6aad3a 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx @@ -1,12 +1,17 @@ import { useState } from "react"; import { subject } from "@casl/ability"; -import { faEdit, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faCertificate, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { CertificateManagementModal } from "@app/components/pki-syncs/CertificateManagementModal"; import { + CertificateDisplayName, + getCertificateDisplayName +} from "@app/components/utilities/certificateDisplayUtils"; +import { + DeleteActionModal, EmptyState, IconButton, Pagination, @@ -46,6 +51,11 @@ const getSyncStatusText = (status?: CertificateSyncStatus | null) => { export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => { const [isManageModalOpen, setIsManageModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [certificateToDelete, setCertificateToDelete] = useState<{ + id: string; + displayName: string; + } | null>(null); const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; @@ -74,6 +84,9 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => { text: "Certificate removed from sync", type: "success" }); + + setIsDeleteModalOpen(false); + setCertificateToDelete(null); } catch { createNotification({ text: "Failed to remove certificate from sync", @@ -82,13 +95,18 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => { } }; + const handleDeleteClick = (certificateId: string, displayName: string) => { + setCertificateToDelete({ id: certificateId, displayName }); + setIsDeleteModalOpen(true); + }; + const totalPages = Math.ceil(totalCount / pageSize); return (

-

Certificates ({totalCount})

+

Certificates

{(isAllowed) => ( {
- {syncCertificates.length === 0 ? ( - - No certificates are currently synced with this PKI destination. - - ) : ( -
- - - - - - - - - - - - - {syncCertificates.map((syncCert) => { - const isExpired = syncCert.certificateNotAfter - ? new Date(syncCert.certificateNotAfter) < new Date() - : false; +
+ +
Common NameSerial NumberStatusExpiresActions
+ + + + + + + + + + + {syncCertificates.map((syncCert) => { + const isExpired = syncCert.certificateNotAfter + ? new Date(syncCert.certificateNotAfter) < new Date() + : false; - return ( - - - - + + + + + - - - - ); - })} - -
SAN / CNSerial NumberStatusExpiresActions
-
- {syncCert.certificateCommonName || "Unknown"} -
-
-
- {syncCert.certificateSerialNumber || "Unknown"} -
-
- {syncCert.lastSyncMessage && - syncCert.syncStatus === CertificateSyncStatus.Failed ? ( - - Failed - - ) : ( - - {getSyncStatusText(syncCert.syncStatus)} - + const { originalDisplayName } = getCertificateDisplayName( + { + altNames: syncCert.certificateAltNames, + commonName: syncCert.certificateCommonName + }, + 34, + "Unknown" + ); + + return ( +
+ + +
+ {syncCert.certificateSerialNumber || "Unknown"} +
+
+ {syncCert.lastSyncMessage && + syncCert.syncStatus === CertificateSyncStatus.Failed ? ( + + Failed + + ) : ( + + {getSyncStatusText(syncCert.syncStatus)} + + )} + + + {syncCert.certificateNotAfter + ? new Date(syncCert.certificateNotAfter).toLocaleDateString() + : "Unknown"} + + + + {(isAllowed) => ( + + handleDeleteClick(syncCert.certificateId, originalDisplayName) + } + > + + )} - - - {syncCert.certificateNotAfter - ? new Date(syncCert.certificateNotAfter).toLocaleDateString() - : "Unknown"} - - - - {(isAllowed) => ( - handleRemoveCertificate(syncCert.certificateId)} - > - - - )} - -
-
- - {/* Pagination */} - {totalPages > 1 && ( -
- setCurrentPage(page)} - onChangePerPage={() => {}} - /> -
+ + +
+ {syncCertificates.length === 0 && ( + )} -
- )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ setCurrentPage(page)} + onChangePerPage={() => {}} + /> +
+ )} +
@@ -218,6 +248,23 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => { refetchSyncCertificates(); }} /> + + { + setIsDeleteModalOpen(false); + setCertificateToDelete(null); + }} + title="Remove Certificate from Sync" + subTitle={`Are you sure you want to remove "${certificateToDelete?.displayName}" from this PKI sync?`} + deleteKey="confirm" + onDeleteApproved={async () => { + if (certificateToDelete) { + await handleRemoveCertificate(certificateToDelete.id); + } + }} + buttonText="Remove Certificate" + /> ); }; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx index af16fbffd..1c6bb8a04 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx @@ -11,12 +11,15 @@ import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionC import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs"; -import { AzureKeyVaultPkiSyncDestinationSection } from "./PkiSyncDestinationSection/index"; +import { + AwsCertificateManagerPkiSyncDestinationSection, + AzureKeyVaultPkiSyncDestinationSection +} from "./PkiSyncDestinationSection/index"; const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( -
- -
{children}
+
+

{label}

+
{children}
); @@ -32,6 +35,9 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) let DestinationComponents: ReactNode; switch (destination) { + case PkiSync.AwsCertificateManager: + DestinationComponents = ; + break; case PkiSync.AzureKeyVault: DestinationComponents = ; break; @@ -47,7 +53,7 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) return (
-

Destination Configuration

+

Destination Configuration

{(isAllowed) => (
-
+
{pkiSync.appConnectionName || "Default Connection"} diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx new file mode 100644 index 000000000..88ffbbb93 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx @@ -0,0 +1,21 @@ +import { TPkiSync } from "@app/hooks/api/pkiSyncs"; + +const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( +
+

{label}

+
{children}
+
+); + +type Props = { + pkiSync: TPkiSync; +}; + +export const AwsCertificateManagerPkiSyncDestinationSection = ({ pkiSync }: Props) => { + const region = + pkiSync.destinationConfig && "region" in pkiSync.destinationConfig + ? pkiSync.destinationConfig.region + : undefined; + + return {region || "Not specified"}; +}; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx index 7c1cc8b14..b9bc7462c 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx @@ -2,9 +2,9 @@ import { TAzureKeyVaultPkiSync } from "@app/hooks/api/pkiSyncs/types/azure-key-vault-sync"; const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( -
- -
{children}
+
+

{label}

+
{children}
); diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts index 5c8823388..4a1728f42 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts @@ -1 +1,2 @@ +export { AwsCertificateManagerPkiSyncDestinationSection } from "./AwsCertificateManagerPkiSyncDestinationSection"; export { AzureKeyVaultPkiSyncDestinationSection } from "./AzureKeyVaultPkiSyncDestinationSection"; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx index 7514bab50..a3d95d5c3 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx @@ -21,9 +21,9 @@ const GenericFieldLabel = ({ children: React.ReactNode; labelClassName?: string; }) => ( -
- -
{children}
+
+

{label}

+
{children}
); @@ -57,7 +57,7 @@ export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => { return (
-

Details

+

Details

{(isAllowed) => ( { )}
-
-
- {name} - {description || "None"} - - {subscriber ? subscriber.name : "Subscriber deleted"} +
+ {name} + {description || "None"} + {subscriber && ( + {subscriber.name} + )} + {syncStatus && ( + + - {syncStatus && ( - - - - )} - {lastSyncedAt && ( - - {format(new Date(lastSyncedAt), "yyyy-MM-dd, h:mm aaa")} - - )} - {syncStatus === PkiSyncStatus.Failed && failureMessage && ( - -

- {failureMessage} -

-
- )} -
+ )} + {lastSyncedAt && ( + + {format(new Date(lastSyncedAt), "yyyy-MM-dd, h:mm aaa")} + + )} + {syncStatus === PkiSyncStatus.Failed && failureMessage && ( + +

{failureMessage}

+
+ )}
); 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 b6365f1cd..65ca5c397 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx @@ -3,13 +3,27 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; -import { GenericFieldLabel } from "@app/components/secret-syncs"; 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"; +const GenericFieldLabel = ({ + label, + children, + labelClassName +}: { + label: string; + children: React.ReactNode; + labelClassName?: string; +}) => ( +
+

{label}

+
{children}
+
+); + type Props = { pkiSync: TPkiSync; onEditOptions: VoidFunction; @@ -28,7 +42,7 @@ export const PkiSyncOptionsSection = ({ pkiSync, onEditOptions }: Props) => {
-

Sync Options

+

Sync Options

{(isAllowed) => ( { )}
-
-
- {/* Hidden for now - Import certificates functionality disabled +
+ {/* Hidden for now - Import certificates functionality disabled {canImportCertificates ? "Enabled" : "Disabled"} */} - - - {canRemoveCertificates ? "Enabled" : "Disabled"} - - -
+ + + {canRemoveCertificates ? "Enabled" : "Disabled"} + +
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx b/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx index 58f3c11e6..5b2988775 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx @@ -36,7 +36,7 @@ export const PoliciesPage = () => { { projectId: currentProject?.id || "", limit: 100, offset: 0, - includeConfigs: true, - includeMetrics: true + includeConfigs: true }); const profiles = data?.certificateProfiles || []; @@ -42,10 +41,9 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => { Name - Enrollment Type + Enrollment Method Issuing CA Certificate Template - Certificates @@ -67,10 +65,9 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => { Name - Enrollment Type + Enrollment Method Issuing CA Certificate Template - Certificates 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 ab7eb549e..e3bdea3c4 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx @@ -33,43 +33,6 @@ import { TCertificateProfile } from "@app/hooks/api/certificateProfiles"; import { useGetCertificateTemplateV2ById } from "@app/hooks/api/certificateTemplates/queries"; import { CertificateIssuanceModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal"; -const MetricsBadges = ({ - metrics -}: { - metrics?: { - totalCertificates: number; - activeCertificates: number; - expiringCertificates: number; - expiredCertificates: number; - revokedCertificates: number; - }; -}) => { - if (!metrics) { - return No metrics; - } - - if (metrics.totalCertificates === 0) { - return No certificates; - } - - return ( - <> - {metrics.activeCertificates > 0 && ( - {metrics.activeCertificates} active - )} - {metrics.expiringCertificates > 0 && ( - {metrics.expiringCertificates} expiring - )} - {metrics.expiredCertificates > 0 && ( - {metrics.expiredCertificates} expired - )} - {metrics.revokedCertificates > 0 && ( - {metrics.revokedCertificates} revoked - )} - - ); -}; - interface Props { profile: TCertificateProfile; onEditProfile: (profile: TCertificateProfile) => void; @@ -118,8 +81,8 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = const getEnrollmentTypeBadge = (enrollmentType: string) => { const config = { - api: { variant: "success" as const, label: "API" }, - est: { variant: "warning" as const, label: "EST" } + api: { variant: "ghost" as const, label: "API" }, + est: { variant: "ghost" as const, label: "EST" } } as const; const configKey = Object.keys(config).includes(enrollmentType) @@ -153,11 +116,6 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = {templateData?.name || profile.certificateTemplateId} - -
- -
-