Update expiry badge display for certs

This commit is contained in:
Tuan Dang
2024-08-17 19:44:47 -07:00
parent 6a6c084b8a
commit 7ec68ca9a1
7 changed files with 198 additions and 152 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = {
[CaStatus.PENDING_CERTIFICATE]: "Pending Certificate" [CaStatus.PENDING_CERTIFICATE]: "Pending Certificate"
}; };
export const getStatusBadgeVariant = (status: CaStatus) => { export const getCaStatusBadgeVariant = (status: CaStatus) => {
switch (status) { switch (status) {
case CaStatus.ACTIVE: case CaStatus.ACTIVE:
return "success"; return "success";
@@ -1,10 +1,21 @@
import { CertKeyAlgorithm, CertStatus,CrlReason } from "./enums"; import { CertKeyAlgorithm, CertStatus, CrlReason } from "./enums";
export const certStatusToNameMap: { [K in CertStatus]: string } = { export const certStatusToNameMap: { [K in CertStatus]: string } = {
[CertStatus.ACTIVE]: "Active", [CertStatus.ACTIVE]: "Active",
[CertStatus.REVOKED]: "Revoked" [CertStatus.REVOKED]: "Revoked"
}; };
export const getCertStatusBadgeVariant = (status: CertStatus) => {
switch (status) {
case CertStatus.ACTIVE:
return "success";
case CertStatus.REVOKED:
return "danger";
default:
return "primary";
}
};
export const certKeyAlgorithmToNameMap: { [K in CertKeyAlgorithm]: string } = { export const certKeyAlgorithmToNameMap: { [K in CertKeyAlgorithm]: string } = {
[CertKeyAlgorithm.RSA_2048]: "RSA 2048", [CertKeyAlgorithm.RSA_2048]: "RSA 2048",
[CertKeyAlgorithm.RSA_4096]: "RSA 4096", [CertKeyAlgorithm.RSA_4096]: "RSA 4096",
@@ -39,7 +39,7 @@ import { CaStatus, useListWorkspaceCas } from "@app/hooks/api";
import { import {
caStatusToNameMap, caStatusToNameMap,
caTypeToNameMap, caTypeToNameMap,
getStatusBadgeVariant getCaStatusBadgeVariant
} from "@app/hooks/api/ca/constants"; } from "@app/hooks/api/ca/constants";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -92,7 +92,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
> >
<Td>{ca.friendlyName}</Td> <Td>{ca.friendlyName}</Td>
<Td> <Td>
<Badge variant={getStatusBadgeVariant(ca.status)}> <Badge variant={getCaStatusBadgeVariant(ca.status)}>
{caStatusToNameMap[ca.status]} {caStatusToNameMap[ca.status]}
</Badge> </Badge>
</Td> </Td>
@@ -100,9 +100,6 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<Td> <Td>
<div className="flex items-center "> <div className="flex items-center ">
<p>{ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "-"}</p> <p>{ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "-"}</p>
{/* <Badge variant="danger" className="ml-4">
Expires Soon
</Badge> */}
</div> </div>
</Td> </Td>
<Td className="flex justify-end"> <Td className="flex justify-end">
@@ -13,6 +13,7 @@ import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Badge,
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
@@ -31,9 +32,10 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useListWorkspaceCertificates } from "@app/hooks/api"; import { useListWorkspaceCertificates } from "@app/hooks/api";
import { certStatusToNameMap } from "@app/hooks/api/certificates/constants";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { getCertValidUntilBadgeDetails } from "./CertificatesTable.utils";
type Props = { type Props = {
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState< popUpName: keyof UsePopUpState<
@@ -60,146 +62,155 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
}); });
return ( return (
<div> <TableContainer>
<TableContainer> <Table>
<Table> <THead>
<THead> <Tr>
<Tr> <Th>Friendly Name</Th>
<Th>Friendly Name</Th> <Th>Status</Th>
<Th>Status</Th> <Th>Not Before</Th>
<Th>Valid Until</Th> <Th>Not After</Th>
<Th /> <Th />
</Tr> </Tr>
</THead> </THead>
<TBody> <TBody>
{isLoading && <TableSkeleton columns={3} innerKey="project-cas" />} {isLoading && <TableSkeleton columns={3} innerKey="project-cas" />}
{!isLoading && {!isLoading &&
data?.certificates.map((certificate) => { data?.certificates.map((certificate) => {
return ( const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter);
<Tr className="h-10" key={`certificate-${certificate.id}`}> return (
<Td>{certificate.friendlyName}</Td> <Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certStatusToNameMap[certificate.status]}</Td> <Td>{certificate.friendlyName}</Td>
<Td> <Td>
{certificate.notAfter <Badge className="" variant={variant}>
? format(new Date(certificate.notAfter), "yyyy-MM-dd") {label}
: "-"} </Badge>
</Td> </Td>
<Td className="flex justify-end"> <Td>
<DropdownMenu> {certificate.notBefore
<DropdownMenuTrigger asChild className="rounded-lg"> ? format(new Date(certificate.notBefore), "yyyy-MM-dd")
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> : "-"}
<Tooltip content="More options"> </Td>
<FontAwesomeIcon size="lg" icon={faEllipsis} /> <Td>
</Tooltip> {certificate.notAfter
</div> ? format(new Date(certificate.notAfter), "yyyy-MM-dd")
</DropdownMenuTrigger> : "-"}
<DropdownMenuContent align="start" className="p-1"> </Td>
<ProjectPermissionCan <Td className="flex justify-end">
I={ProjectPermissionActions.Read} <DropdownMenu>
a={ProjectPermissionSub.Certificates} <DropdownMenuTrigger asChild className="rounded-lg">
> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
{(isAllowed) => ( <Tooltip content="More options">
<DropdownMenuItem <FontAwesomeIcon size="lg" icon={faEllipsis} />
className={twMerge( </Tooltip>
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" </div>
)} </DropdownMenuTrigger>
onClick={async () => <DropdownMenuContent align="start" className="p-1">
handlePopUpOpen("certificateCert", { <ProjectPermissionCan
serialNumber: certificate.serialNumber I={ProjectPermissionActions.Read}
}) a={ProjectPermissionSub.Certificates}
} >
disabled={!isAllowed} {(isAllowed) => (
icon={<FontAwesomeIcon icon={faFileExport} />} <DropdownMenuItem
> className={twMerge(
Export Certificate !isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
</DropdownMenuItem> )}
)} onClick={async () =>
</ProjectPermissionCan> handlePopUpOpen("certificateCert", {
<ProjectPermissionCan serialNumber: certificate.serialNumber
I={ProjectPermissionActions.Read} })
a={ProjectPermissionSub.Certificates} }
> disabled={!isAllowed}
{(isAllowed) => ( icon={<FontAwesomeIcon icon={faFileExport} />}
<DropdownMenuItem >
className={twMerge( Export Certificate
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" </DropdownMenuItem>
)} )}
onClick={async () => </ProjectPermissionCan>
handlePopUpOpen("certificate", { <ProjectPermissionCan
serialNumber: certificate.serialNumber I={ProjectPermissionActions.Read}
}) a={ProjectPermissionSub.Certificates}
} >
disabled={!isAllowed} {(isAllowed) => (
icon={<FontAwesomeIcon icon={faEye} />} <DropdownMenuItem
> className={twMerge(
View Details !isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
</DropdownMenuItem> )}
)} onClick={async () =>
</ProjectPermissionCan> handlePopUpOpen("certificate", {
<ProjectPermissionCan serialNumber: certificate.serialNumber
I={ProjectPermissionActions.Delete} })
a={ProjectPermissionSub.Certificates} }
> disabled={!isAllowed}
{(isAllowed) => ( icon={<FontAwesomeIcon icon={faEye} />}
<DropdownMenuItem >
className={twMerge( View Details
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" </DropdownMenuItem>
)} )}
onClick={async () => </ProjectPermissionCan>
handlePopUpOpen("revokeCertificate", { <ProjectPermissionCan
serialNumber: certificate.serialNumber I={ProjectPermissionActions.Delete}
}) a={ProjectPermissionSub.Certificates}
} >
disabled={!isAllowed} {(isAllowed) => (
icon={<FontAwesomeIcon icon={faBan} />} <DropdownMenuItem
> className={twMerge(
Revoke Certificate !isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
</DropdownMenuItem> )}
)} onClick={async () =>
</ProjectPermissionCan> handlePopUpOpen("revokeCertificate", {
<ProjectPermissionCan serialNumber: certificate.serialNumber
I={ProjectPermissionActions.Delete} })
a={ProjectPermissionSub.Certificates} }
> disabled={!isAllowed}
{(isAllowed) => ( icon={<FontAwesomeIcon icon={faBan} />}
<DropdownMenuItem >
className={twMerge( Revoke Certificate
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50" </DropdownMenuItem>
)} )}
onClick={async () => </ProjectPermissionCan>
handlePopUpOpen("deleteCertificate", { <ProjectPermissionCan
serialNumber: certificate.serialNumber, I={ProjectPermissionActions.Delete}
commonName: certificate.commonName a={ProjectPermissionSub.Certificates}
}) >
} {(isAllowed) => (
disabled={!isAllowed} <DropdownMenuItem
icon={<FontAwesomeIcon icon={faTrash} />} className={twMerge(
> !isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
Delete Certificate )}
</DropdownMenuItem> onClick={async () =>
)} handlePopUpOpen("deleteCertificate", {
</ProjectPermissionCan> serialNumber: certificate.serialNumber,
</DropdownMenuContent> commonName: certificate.commonName
</DropdownMenu> })
</Td> }
</Tr> disabled={!isAllowed}
); icon={<FontAwesomeIcon icon={faTrash} />}
})} >
</TBody> Delete Certificate
</Table> </DropdownMenuItem>
{!isLoading && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && ( )}
<Pagination </ProjectPermissionCan>
count={data.totalCount} </DropdownMenuContent>
page={page} </DropdownMenu>
perPage={perPage} </Td>
onChangePage={(newPage) => setPage(newPage)} </Tr>
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} );
/> })}
)} </TBody>
{!isLoading && !data?.certificates?.length && ( </Table>
<EmptyState title="No certificates have been created" icon={faCertificate} /> {!isLoading && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
)} <Pagination
</TableContainer> count={data.totalCount}
</div> page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isLoading && !data?.certificates?.length && (
<EmptyState title="No certificates have been created" icon={faCertificate} />
)}
</TableContainer>
); );
}; };
@@ -0,0 +1,28 @@
import ms from "ms";
export const getCertValidUntilBadgeDetails = (notAfter: string) => {
const currentDate = new Date().getTime();
const notAfterDate = new Date(notAfter).getTime();
const diffInMs = notAfterDate - currentDate;
let variant: "success" | "primary" | "danger" = "success";
let label = "Healthy";
if (diffInMs > ms("60d")) {
variant = "success";
} else if (diffInMs > ms("30d")) {
variant = "primary";
} else {
variant = "danger";
}
if (diffInMs > ms("60d")) {
label = "Healthy";
} else if (diffInMs > ms("0d")) {
label = `Expires in ${ms(diffInMs)}`;
} else {
label = "Expired";
}
return { variant, label };
};
@@ -1,6 +1,6 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { PkiAlertsSection, PkiCollectionSection } from "./components"; import { PkiAlertsSection } from "./components";
export const PkiAlertsTab = () => { export const PkiAlertsTab = () => {
return ( return (
@@ -11,7 +11,6 @@ export const PkiAlertsTab = () => {
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<PkiCollectionSection />
<PkiAlertsSection /> <PkiAlertsSection />
</motion.div> </motion.div>
); );
@@ -63,7 +63,7 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
} }
> >
<Td>{pkiCollection.name}</Td> <Td>{pkiCollection.name}</Td>
<Td>{pkiCollection.description}</Td> <Td>{pkiCollection.description ? pkiCollection.description : "-"}</Td>
<Td> <Td>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">