mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 01:38:05 +00:00
Complete preliminary docs for pki subscribers
This commit is contained in:
+20
-33
@@ -21,22 +21,25 @@ import {
|
||||
ProjectPermissionSub,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useDeletePkiSubscriber, useGetPkiSubscriberById } from "@app/hooks/api";
|
||||
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
|
||||
// import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from "./components";
|
||||
import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from "./components";
|
||||
|
||||
const Page = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const navigate = useNavigate();
|
||||
const projectId = currentWorkspace?.id || "";
|
||||
const subscriberId = useParams({
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace.id;
|
||||
const subscriberName = useParams({
|
||||
from: ROUTE_PATHS.CertManager.PkiSubscriberDetailsByIDPage.id,
|
||||
select: (el) => el.subscriberId
|
||||
select: (el) => el.subscriberName
|
||||
});
|
||||
const { data } = useGetPkiSubscriber({
|
||||
subscriberName,
|
||||
projectId
|
||||
});
|
||||
const { data } = useGetPkiSubscriberById(subscriberId);
|
||||
|
||||
const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber();
|
||||
|
||||
@@ -45,14 +48,14 @@ const Page = () => {
|
||||
"deletePkiSubscriber"
|
||||
] as const);
|
||||
|
||||
const onRemoveSubscriberSubmit = async (subscriberIdToDelete: string) => {
|
||||
const onRemoveSubscriberSubmit = async (subscriberNameToDelete: string) => {
|
||||
try {
|
||||
if (!projectId) return;
|
||||
|
||||
await deletePkiSubscriber({ subscriberId: subscriberIdToDelete });
|
||||
await deletePkiSubscriber({ subscriberName: subscriberNameToDelete, projectId });
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted PKI subscriber",
|
||||
text: "Successfully deleted subscriber",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
@@ -66,7 +69,7 @@ const Page = () => {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete PKI subscriber",
|
||||
text: "Failed to delete subscriber",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
@@ -114,15 +117,13 @@ const Page = () => {
|
||||
</PageHeader>
|
||||
<div className="flex">
|
||||
<div className="mr-4 w-96">
|
||||
TODO: PkiSubscriberDetailsSection
|
||||
{/* <PkiSubscriberDetailsSection
|
||||
subscriberId={subscriberId}
|
||||
<PkiSubscriberDetailsSection
|
||||
subscriberName={data.name}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/> */}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
TODO: PkiSubscriberCertificatesSection
|
||||
{/* <PkiSubscriberCertificatesSection subscriberId={subscriberId} /> */}
|
||||
<PkiSubscriberCertificatesSection subscriberName={data.name} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,28 +153,14 @@ export const PkiSubscriberDetailsByIDPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "PKI Subscriber" })}</title>
|
||||
</Helmet>
|
||||
{/* <ProjectPermissionCan
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionPkiSubscriberActions.Read}
|
||||
a={ProjectPermissionSub.PkiSubscribers}
|
||||
passThrough={false}
|
||||
renderGuardBanner
|
||||
>
|
||||
<Page />
|
||||
</ProjectPermissionCan> */}
|
||||
TESTTT
|
||||
</ProjectPermissionCan>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId"
|
||||
)({
|
||||
component: PkiSubscriberDetailsByIDPage
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { CertificateRevocationModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal";
|
||||
|
||||
import { PkiSubscriberCertificatesTable } from "./PkiSubscriberCertificatesTable";
|
||||
|
||||
type Props = {
|
||||
subscriberName: string;
|
||||
};
|
||||
|
||||
export const PkiSubscriberCertificatesSection = ({ subscriberName }: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeCertificate"] as const);
|
||||
|
||||
return (
|
||||
<div className="h-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">Certificates</h3>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<PkiSubscriberCertificatesTable
|
||||
subscriberName={subscriberName}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
</div>
|
||||
<CertificateRevocationModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
import { useState } from "react";
|
||||
import { faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Badge,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
Pagination,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSub,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useGetPkiSubscriberCertificates } from "@app/hooks/api";
|
||||
import { CertStatus } from "@app/hooks/api/certificates/enums";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
subscriberName: string;
|
||||
handlePopUpOpen?: (popUpName: keyof UsePopUpState<["revokeCertificate"]>, data?: object) => void;
|
||||
};
|
||||
|
||||
const PER_PAGE_INIT = 25;
|
||||
|
||||
export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace.id;
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
|
||||
|
||||
const { data, isPending } = useGetPkiSubscriberCertificates({
|
||||
subscriberName,
|
||||
projectId,
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
});
|
||||
|
||||
const getCertStatusBadge = (status: string, notAfter: string) => {
|
||||
if (status === CertStatus.REVOKED) {
|
||||
return <Badge variant="danger">Revoked</Badge>;
|
||||
}
|
||||
|
||||
const expiryDate = new Date(notAfter);
|
||||
const now = new Date();
|
||||
const daysUntilExpiry = Math.floor(
|
||||
(expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
|
||||
if (daysUntilExpiry < 0) {
|
||||
return <Badge variant="danger">Expired</Badge>;
|
||||
}
|
||||
|
||||
if (daysUntilExpiry < 30) {
|
||||
return <Badge variant="primary">Expiring Soon</Badge>;
|
||||
}
|
||||
|
||||
return <Badge variant="success">Valid</Badge>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Common Name</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Not Before</Th>
|
||||
<Th>Not After</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isPending && <TableSkeleton columns={5} innerKey="pki-subscriber-certificates" />}
|
||||
{!isPending &&
|
||||
data?.certificates?.map((certificate) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||
<Td>{certificate.commonName}</Td>
|
||||
<Td>{getCertStatusBadge(certificate.status, certificate.notAfter)}</Td>
|
||||
<Td>
|
||||
{certificate.notBefore
|
||||
? format(new Date(certificate.notBefore), "yyyy-MM-dd")
|
||||
: "-"}
|
||||
</Td>
|
||||
<Td>
|
||||
{certificate.notAfter
|
||||
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
|
||||
: "-"}
|
||||
</Td>
|
||||
<Td className="flex justify-end">
|
||||
<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={ProjectPermissionPkiSubscriberActions.Delete}
|
||||
a={ProjectPermissionSub.PkiSubscribers}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={() =>
|
||||
handlePopUpOpen &&
|
||||
handlePopUpOpen("revokeCertificate", {
|
||||
serialNumber: certificate.serialNumber
|
||||
})
|
||||
}
|
||||
disabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Revoke Certificate
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</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)}
|
||||
/>
|
||||
)}
|
||||
{!isPending && !data?.certificates?.length && (
|
||||
<EmptyState
|
||||
title="No certificates have been issued for this subscriber"
|
||||
icon={faCertificate}
|
||||
/>
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
import { useState } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faCheck, faCopy, faPencil } 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 { Button, IconButton, Modal, ModalContent, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { useGetPkiSubscriber, useIssuePkiSubscriberCert } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
|
||||
|
||||
type Props = {
|
||||
subscriberName: string;
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["pkiSubscriber"]>, data?: object) => void;
|
||||
};
|
||||
|
||||
type TCertificateDetails = {
|
||||
serialNumber: string;
|
||||
certificate: string;
|
||||
certificateChain: string;
|
||||
privateKey: string;
|
||||
};
|
||||
|
||||
export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace.id;
|
||||
const { permission } = useProjectPermission();
|
||||
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
|
||||
initialState: "Copy ID to clipboard"
|
||||
});
|
||||
|
||||
const { data: pkiSubscriber } = useGetPkiSubscriber({
|
||||
subscriberName,
|
||||
projectId
|
||||
});
|
||||
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
|
||||
useIssuePkiSubscriberCert();
|
||||
|
||||
const onIssuePkiSubscriberCert = async () => {
|
||||
try {
|
||||
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
|
||||
|
||||
setCertificateDetails({
|
||||
serialNumber: response.serialNumber,
|
||||
certificate: response.certificate,
|
||||
certificateChain: response.certificateChain,
|
||||
privateKey: response.privateKey
|
||||
});
|
||||
|
||||
setIsModalOpen(true);
|
||||
|
||||
createNotification({
|
||||
text: "Successfully issued certificate",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to issue certificate",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const canIssuePkiSubscriberCert = permission.can(
|
||||
ProjectPermissionPkiSubscriberActions.IssueCert,
|
||||
subject(ProjectPermissionSub.PkiSubscribers, {
|
||||
name: pkiSubscriber?.name ?? ""
|
||||
})
|
||||
);
|
||||
|
||||
return pkiSubscriber ? (
|
||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">PKI Subscriber Details</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionPkiSubscriberActions.Edit}
|
||||
a={ProjectPermissionSub.PkiSubscribers}
|
||||
>
|
||||
{(isAllowed) => {
|
||||
return (
|
||||
<Tooltip content="Edit PKI Subscriber">
|
||||
<IconButton
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="edit icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("pkiSubscriber", {
|
||||
subscriberName: pkiSubscriber.name
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">PKI Subscriber ID</p>
|
||||
<div className="group flex align-top">
|
||||
<p className="text-sm text-mineshaft-300">{pkiSubscriber.id}</p>
|
||||
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Tooltip content={copyTextId}>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative ml-2"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(pkiSubscriber.id);
|
||||
setCopyTextId("Copied");
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
|
||||
<p className="text-sm text-mineshaft-300">{pkiSubscriber.name}</p>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="text-sm font-semibold text-mineshaft-300">Common Name</p>
|
||||
<p className="text-sm text-mineshaft-300">{pkiSubscriber.commonName}</p>
|
||||
</div>
|
||||
{canIssuePkiSubscriberCert && (
|
||||
<Button
|
||||
isDisabled={!canIssuePkiSubscriberCert}
|
||||
className="mt-4 w-full"
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
isLoading={isIssuingCert}
|
||||
onClick={() => {
|
||||
onIssuePkiSubscriberCert();
|
||||
}}
|
||||
>
|
||||
Issue Certificate
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
setIsModalOpen(isOpen);
|
||||
if (!isOpen) {
|
||||
setCertificateDetails(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Certificate Details">
|
||||
{certificateDetails && (
|
||||
<CertificateContent
|
||||
serialNumber={certificateDetails.serialNumber}
|
||||
certificate={certificateDetails.certificate}
|
||||
certificateChain={certificateDetails.certificateChain}
|
||||
privateKey={certificateDetails.privateKey}
|
||||
/>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { PkiSubscriberCertificatesSection } from "./PkiSubscriberCertificatesSection";
|
||||
export { PkiSubscriberDetailsSection } from "./PkiSubscriberDetailsSection";
|
||||
@@ -1,16 +1,9 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div>
|
||||
Hello
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId"!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberId"
|
||||
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
|
||||
)({
|
||||
component: RouteComponent
|
||||
component: PkiSubscriberDetailsByIDPage
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user