import { useMemo, useState } from "react"; import { faBan, faCertificate, faClockRotateLeft, faEllipsis, faEye, faFileExport, faLink, faRedo, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; 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, DropdownMenuItem, DropdownMenuTrigger, EmptyState, Pagination, Table, TableContainer, TableSkeleton, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2"; import { Badge } from "@app/components/v3"; import { ProjectPermissionCertificateActions, ProjectPermissionSub, useProject } from "@app/context"; import { useUpdateRenewalConfig } from "@app/hooks/api"; 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 { useListWorkspaceCertificates } from "@app/hooks/api/projects"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { getCertValidUntilBadgeDetails } from "./CertificatesTable.utils"; const isExpiringWithinOneDay = (notAfter: string): boolean => { const expiryDate = new Date(notAfter); const now = new Date(); const oneDayFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000); return expiryDate <= oneDayFromNow; }; type Props = { handlePopUpOpen: ( popUpName: keyof UsePopUpState< [ "certificate", "deleteCertificate", "revokeCertificate", "certificateCert", "manageRenewal", "renewCertificate", "managePkiSyncs" ] >, data?: { serialNumber?: string; commonName?: string; certificateId?: string; profileId?: string; renewBeforeDays?: number; ttlDays?: number; notAfter?: string; renewalError?: string; renewedFromCertificateId?: string; renewedByCertificateId?: string; } ) => void; }; const PER_PAGE_INIT = 25; export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); const { currentProject } = useProject(); const { data, isPending } = useListWorkspaceCertificates({ projectId: currentProject?.id ?? "", offset: (page - 1) * perPage, limit: perPage }); const { mutateAsync: updateRenewalConfig } = useUpdateRenewalConfig(); // TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation const isLegacyTemplatesEnabled = true; const { data: caData } = useListCasByProjectId(currentProject?.id ?? ""); const caCapabilityMap = useMemo(() => { if (!caData) return {}; const map: Record = {}; caData.forEach((ca) => { map[ca.id] = ca.type; }); return map; }, [caData]); const handleDisableAutoRenewal = async (certificateId: string, commonName: string) => { if (!currentProject?.slug) { createNotification({ text: "Unable to disable auto-renewal: Project not found. Please refresh the page and try again.", type: "error" }); return; } await updateRenewalConfig({ certificateId, projectSlug: currentProject.slug, enableAutoRenewal: false }); createNotification({ text: `Auto-renewal disabled for ${commonName}`, type: "success" }); }; return ( {isPending && } {!isPending && data?.certificates.map((certificate) => { const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter); const isRevoked = certificate.status === CertStatus.REVOKED; const isExpired = new Date(certificate.notAfter) < new Date(); const isExpiringWithinDay = isExpiringWithinOneDay(certificate.notAfter); const hasFailed = Boolean(certificate.renewalError); 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 ( ); })}
SAN / CN Status Not Before Not After
{certificate.status === CertStatus.REVOKED ? ( Revoked ) : ( {label} )} {certificate.notBefore ? format(new Date(certificate.notBefore), "yyyy-MM-dd") : "-"} {certificate.notAfter ? format(new Date(certificate.notAfter), "yyyy-MM-dd") : "-"}
{ 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"; })()} > )}
{(isAllowed) => ( handlePopUpOpen("certificateCert", { serialNumber: certificate.serialNumber }) } disabled={!isAllowed} icon={} > Export Certificate )} {isLegacyTemplatesEnabled && ( {(isAllowed) => ( handlePopUpOpen("certificate", { serialNumber: certificate.serialNumber }) } disabled={!isAllowed} icon={} > View Details )} )} {/* Manage auto renewal option - not shown for failed renewals */} {(() => { const canManageRenewal = certificate.profileId && certificate.hasPrivateKey !== false && !certificate.renewedByCertificateId && !isRevoked && !isExpired && !hasFailed && !isExpiringWithinDay; if (!canManageRenewal) return null; return ( {(isAllowed) => { return ( { const notAfterDate = new Date(certificate.notAfter); const notBeforeDate = certificate.notBefore ? new Date(certificate.notBefore) : new Date( notAfterDate.getTime() - 365 * 24 * 60 * 60 * 1000 ); const ttlDays = Math.max( 1, Math.ceil( (notAfterDate.getTime() - notBeforeDate.getTime()) / (24 * 60 * 60 * 1000) ) ); handlePopUpOpen("manageRenewal", { certificateId: certificate.id, commonName: certificate.commonName, profileId: certificate.profileId, renewBeforeDays: certificate.renewBeforeDays, ttlDays, notAfter: certificate.notAfter, renewalError: certificate.renewalError, renewedFromCertificateId: certificate.renewedFromCertificateId, renewedByCertificateId: certificate.renewedByCertificateId }); }} disabled={!isAllowed} icon={} > {isAutoRenewalEnabled ? "Manage auto renewal" : "Enable auto renewal"} ); }} ); })()} {/* Disable auto renewal option - only shown when auto renewal is active */} {(() => { const canDisableRenewal = certificate.profileId && certificate.hasPrivateKey !== false && !certificate.renewedByCertificateId && !isRevoked && !isExpired && !isExpiringWithinDay && isAutoRenewalEnabled; if (!canDisableRenewal) return null; return ( {(isAllowed) => ( { await handleDisableAutoRenewal( certificate.id, certificate.commonName ); }} disabled={!isAllowed} icon={} > Disable auto renewal )} ); })()} {/* Manual renewal action for profile-issued certificates that are not revoked/expired (including failed ones) */} {(() => { const canRenew = certificate.profileId && certificate.hasPrivateKey !== false && !certificate.renewedByCertificateId && !isRevoked && !isExpired; if (!canRenew) return null; return ( {(isAllowed) => ( { handlePopUpOpen("renewCertificate", { certificateId: certificate.id, commonName: certificate.commonName }); }} disabled={!isAllowed} icon={} > Renew Now )} ); })()} {/* PKI Sync management - only for active certificates that are not renewed */} {certificate.status === CertStatus.ACTIVE && !certificate.renewedByCertificateId && ( {(isAllowed) => ( handlePopUpOpen("managePkiSyncs", { certificateId: certificate.id, commonName: certificate.commonName }) } disabled={!isAllowed} icon={} > Manage PKI Syncs )} )} {/* Only show revoke button if CA supports revocation */} {(() => { const caType = caCapabilityMap[certificate.caId]; const supportsRevocation = !caType || caSupportsCapability(caType, CaCapability.REVOKE_CERTIFICATES); if (!supportsRevocation) { return null; } return ( {(isAllowed) => ( handlePopUpOpen("revokeCertificate", { serialNumber: certificate.serialNumber }) } disabled={!isAllowed} icon={} > Revoke Certificate )} ); })()} {(isAllowed) => ( handlePopUpOpen("deleteCertificate", { serialNumber: certificate.serialNumber, commonName: certificate.commonName }) } disabled={!isAllowed} icon={} > Delete Certificate )}
{!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && ( setPage(newPage)} onChangePerPage={(newPerPage) => setPerPage(newPerPage)} /> )} {!isPending && !data?.certificates?.length && ( )}
); };