mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 12:29:26 +00:00
306 lines
12 KiB
TypeScript
306 lines
12 KiB
TypeScript
import { useState } from "react";
|
|
import { Helmet } from "react-helmet";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
faCertificate,
|
|
faCog,
|
|
faEllipsis,
|
|
faPencil,
|
|
faPlus,
|
|
faTrash
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { format } from "date-fns";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
|
import { createNotification } from "@app/components/notifications";
|
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
|
import {
|
|
Button,
|
|
DeleteActionModal,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
EmptyState,
|
|
Modal,
|
|
ModalContent,
|
|
PageHeader,
|
|
Pagination,
|
|
Table,
|
|
TableContainer,
|
|
TableSkeleton,
|
|
Tag,
|
|
TBody,
|
|
Td,
|
|
Th,
|
|
THead,
|
|
Tooltip,
|
|
Tr
|
|
} from "@app/components/v2";
|
|
import {
|
|
ProjectPermissionPkiTemplateActions,
|
|
ProjectPermissionSub,
|
|
useProject,
|
|
useSubscription
|
|
} from "@app/context";
|
|
import { usePopUp } from "@app/hooks";
|
|
import { useDeleteCertTemplateV2 } from "@app/hooks/api";
|
|
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
|
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
|
|
|
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
|
|
import { PkiTemplateForm } from "./components/PkiTemplateForm";
|
|
|
|
const PER_PAGE_INIT = 25;
|
|
export const PkiTemplateListPage = () => {
|
|
const { t } = useTranslation();
|
|
const { currentProject } = useProject();
|
|
const [page, setPage] = useState(1);
|
|
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
|
|
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
|
"certificateTemplate",
|
|
"deleteTemplate",
|
|
"enrollmentOptions",
|
|
"estUpgradePlan"
|
|
] as const);
|
|
|
|
const { subscription } = useSubscription();
|
|
|
|
const { data, isPending } = useListCertificateTemplates({
|
|
projectId: currentProject.id,
|
|
offset: (page - 1) * perPage,
|
|
limit: perPage
|
|
});
|
|
|
|
const deleteCertTemplate = useDeleteCertTemplateV2();
|
|
|
|
const onRemovePkiSubscriberSubmit = async () => {
|
|
try {
|
|
const pkiTemplate = await deleteCertTemplate.mutateAsync({
|
|
projectId: currentProject.id,
|
|
templateName: popUp?.deleteTemplate?.data?.name
|
|
});
|
|
|
|
createNotification({
|
|
text: `Successfully deleted PKI template: ${pkiTemplate.name}`,
|
|
type: "success"
|
|
});
|
|
|
|
handlePopUpClose("deleteTemplate");
|
|
} catch (err) {
|
|
console.error(err);
|
|
createNotification({
|
|
text: "Failed to delete PKI subscriber",
|
|
type: "error"
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Helmet>
|
|
<title>{t("common.head-title", { title: "PKI Templates" })}</title>
|
|
</Helmet>
|
|
<div className="h-full bg-bunker-800">
|
|
<div className="mx-auto flex flex-col justify-between text-white">
|
|
<div className="mx-auto mb-6 w-full max-w-8xl">
|
|
<PageHeader
|
|
scope={ProjectType.CertificateManager}
|
|
title="Certificate Templates"
|
|
description="Manage certificate template to request and issue dynamic certificates following a strict format."
|
|
/>
|
|
</div>
|
|
<div className="container mx-auto mb-6 max-w-8xl rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
|
{subscription?.pkiLegacyTemplates && (
|
|
<div className="mb-4 flex justify-between">
|
|
<p className="text-xl font-medium text-mineshaft-100">Templates</p>
|
|
<div className="flex w-full justify-end">
|
|
<ProjectPermissionCan
|
|
I={ProjectPermissionPkiTemplateActions.Create}
|
|
a={ProjectPermissionSub.CertificateTemplates}
|
|
>
|
|
{(isAllowed) => (
|
|
<Button
|
|
colorSchema="primary"
|
|
type="submit"
|
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
onClick={() => handlePopUpOpen("certificateTemplate")}
|
|
isDisabled={!isAllowed}
|
|
className="ml-4"
|
|
>
|
|
Add Template
|
|
</Button>
|
|
)}
|
|
</ProjectPermissionCan>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<TableContainer>
|
|
<Table>
|
|
<THead>
|
|
<Tr>
|
|
<Th>Name</Th>
|
|
<Th>Issuing CA</Th>
|
|
<Th className="w-64">Last Updated At</Th>
|
|
<Th />
|
|
</Tr>
|
|
</THead>
|
|
<TBody>
|
|
{isPending && <TableSkeleton columns={4} innerKey="project-cert-templates" />}
|
|
{!isPending &&
|
|
data?.certificateTemplates?.map((template) => {
|
|
return (
|
|
<Tr className="h-10" key={`certificate-template-${template.id}`}>
|
|
<Td>{template.name}</Td>
|
|
<Td>
|
|
<Tag size="xs">{template.ca.name}</Tag>
|
|
</Td>
|
|
<Td>{format(new Date(template.updatedAt), "yyyy-MM-dd | HH:mm:ss")}</Td>
|
|
<Td className="text-right align-middle">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
|
<Tooltip content="More options">
|
|
<FontAwesomeIcon size="lg" icon={faEllipsis} />
|
|
</Tooltip>
|
|
</div>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start" className="p-1">
|
|
<ProjectPermissionCan
|
|
I={ProjectPermissionPkiTemplateActions.Edit}
|
|
a={ProjectPermissionSub.CertificateTemplates}
|
|
>
|
|
{(isAllowed) => (
|
|
<DropdownMenuItem
|
|
className={twMerge(
|
|
!isAllowed &&
|
|
"pointer-events-none cursor-not-allowed opacity-50"
|
|
)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handlePopUpOpen("certificateTemplate", template);
|
|
}}
|
|
disabled={!isAllowed}
|
|
icon={<FontAwesomeIcon icon={faPencil} />}
|
|
>
|
|
Edit Template
|
|
</DropdownMenuItem>
|
|
)}
|
|
</ProjectPermissionCan>
|
|
<ProjectPermissionCan
|
|
I={ProjectPermissionPkiTemplateActions.Edit}
|
|
a={ProjectPermissionSub.CertificateTemplates}
|
|
>
|
|
{(isAllowed) => (
|
|
<DropdownMenuItem
|
|
className={twMerge(
|
|
!isAllowed &&
|
|
"pointer-events-none cursor-not-allowed opacity-50"
|
|
)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (!subscription.pkiEst) {
|
|
handlePopUpOpen("estUpgradePlan", {
|
|
isEnterpriseFeature: true
|
|
});
|
|
return;
|
|
}
|
|
handlePopUpOpen("enrollmentOptions", {
|
|
id: template.id
|
|
});
|
|
}}
|
|
disabled={!isAllowed}
|
|
icon={<FontAwesomeIcon icon={faCog} />}
|
|
>
|
|
Manage Enrollment
|
|
</DropdownMenuItem>
|
|
)}
|
|
</ProjectPermissionCan>
|
|
<ProjectPermissionCan
|
|
I={ProjectPermissionPkiTemplateActions.Delete}
|
|
a={ProjectPermissionSub.CertificateTemplates}
|
|
>
|
|
{(isAllowed) => (
|
|
<DropdownMenuItem
|
|
className={twMerge(
|
|
!isAllowed &&
|
|
"pointer-events-none cursor-not-allowed opacity-50"
|
|
)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handlePopUpOpen("deleteTemplate", template);
|
|
}}
|
|
disabled={!isAllowed}
|
|
icon={<FontAwesomeIcon icon={faTrash} />}
|
|
>
|
|
Delete Template
|
|
</DropdownMenuItem>
|
|
)}
|
|
</ProjectPermissionCan>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</Td>
|
|
</Tr>
|
|
);
|
|
})}
|
|
{!isPending && !data?.certificateTemplates?.length && (
|
|
<Tr>
|
|
<Td colSpan={4}>
|
|
<EmptyState title="No certificate templates found" icon={faCertificate} />
|
|
</Td>
|
|
</Tr>
|
|
)}
|
|
</TBody>
|
|
</Table>
|
|
{!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
|
|
<Pagination
|
|
count={data.totalCount}
|
|
page={page}
|
|
perPage={perPage}
|
|
onChangePage={(newPage) => setPage(newPage)}
|
|
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
|
/>
|
|
)}
|
|
</TableContainer>
|
|
<DeleteActionModal
|
|
isOpen={popUp.deleteTemplate.isOpen}
|
|
title="Are you sure you want to remove the PKI Template?"
|
|
onChange={(isOpen) => handlePopUpToggle("deleteTemplate", isOpen)}
|
|
deleteKey="confirm"
|
|
onDeleteApproved={() => onRemovePkiSubscriberSubmit()}
|
|
/>
|
|
</div>
|
|
<div className="container mx-auto max-w-8xl" />
|
|
</div>
|
|
<Modal
|
|
isOpen={popUp?.certificateTemplate?.isOpen}
|
|
onOpenChange={(isOpen) => handlePopUpToggle("certificateTemplate", isOpen)}
|
|
>
|
|
<ModalContent
|
|
title={
|
|
popUp.certificateTemplate?.data
|
|
? "Certificate Template"
|
|
: "Create Certificate Template"
|
|
}
|
|
>
|
|
<PkiTemplateForm
|
|
certTemplate={popUp?.certificateTemplate?.data}
|
|
handlePopUpToggle={(isOpen) => handlePopUpToggle("certificateTemplate", isOpen)}
|
|
/>
|
|
</ModalContent>
|
|
</Modal>
|
|
<CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
|
</div>
|
|
<UpgradePlanModal
|
|
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}
|
|
/>
|
|
</>
|
|
);
|
|
};
|