mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: initial integration of cert template management
This commit is contained in:
@@ -476,7 +476,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
CertificateTemplatesSchema.pick({
|
||||
id: true,
|
||||
name: true
|
||||
})
|
||||
}).merge(
|
||||
z.object({
|
||||
caName: z.string()
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ export const certificateTemplateDALFactory = (db: TDbClient) => {
|
||||
`${TableName.CertificateTemplate}.caId`
|
||||
)
|
||||
.where(`${TableName.CertificateAuthority}.projectId`, "=", projectId)
|
||||
.select(selectAllTableCols(TableName.CertificateTemplate));
|
||||
.select(selectAllTableCols(TableName.CertificateTemplate))
|
||||
.select(db.ref("friendlyName").as("caName").withSchema(TableName.CertificateAuthority));
|
||||
|
||||
return certTemplates;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ export enum ProjectPermissionSub {
|
||||
Identity = "identity",
|
||||
CertificateAuthorities = "certificate-authorities",
|
||||
Certificates = "certificates",
|
||||
CertificateTemplates = "certificate-templates",
|
||||
PkiAlerts = "pki-alerts",
|
||||
PkiCollections = "pki-collections",
|
||||
Kms = "kms"
|
||||
@@ -59,6 +60,7 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
|
||||
|
||||
2
frontend/src/hooks/api/certificateTemplates/index.tsx
Normal file
2
frontend/src/hooks/api/certificateTemplates/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { useCreateCertTemplate, useDeleteCertTemplate, useUpdateCertTemplate } from "./mutations";
|
||||
export { useGetCertTemplate } from "./queries";
|
||||
63
frontend/src/hooks/api/certificateTemplates/mutations.tsx
Normal file
63
frontend/src/hooks/api/certificateTemplates/mutations.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import { certTemplateKeys } from "./queries";
|
||||
import {
|
||||
TCertificateTemplate,
|
||||
TCreateCertificateTemplateDTO,
|
||||
TDeleteCertificateTemplateDTO,
|
||||
TUpdateCertificateTemplateDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateCertTemplate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TCertificateTemplate, {}, TCreateCertificateTemplateDTO>({
|
||||
mutationFn: async (data) => {
|
||||
const {
|
||||
data: { certificateTemplate }
|
||||
} = await apiRequest.post<{ certificateTemplate: TCertificateTemplate }>(
|
||||
"/api/v1/pki/certificate-templates",
|
||||
data
|
||||
);
|
||||
return certificateTemplate;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificateTemplates(projectId));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateCertTemplate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<TCertificateTemplate, {}, TUpdateCertificateTemplateDTO>({
|
||||
mutationFn: async (data) => {
|
||||
const {
|
||||
data: { certificateTemplate }
|
||||
} = await apiRequest.patch<{ certificateTemplate: TCertificateTemplate }>(
|
||||
`/api/v1/pki/certificate-templates/${data.id}`,
|
||||
data
|
||||
);
|
||||
|
||||
return certificateTemplate;
|
||||
},
|
||||
onSuccess: (_, { projectId, id }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificateTemplates(projectId));
|
||||
queryClient.invalidateQueries(certTemplateKeys.getCertTemplateById(id));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteCertTemplate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<void, {}, TDeleteCertificateTemplateDTO>({
|
||||
mutationFn: async (data) => {
|
||||
return apiRequest.delete(`/api/v1/pki/certificate-templates/${data.id}`);
|
||||
},
|
||||
onSuccess: (_, { projectId, id }) => {
|
||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificateTemplates(projectId));
|
||||
queryClient.invalidateQueries(certTemplateKeys.getCertTemplateById(id));
|
||||
}
|
||||
});
|
||||
};
|
||||
24
frontend/src/hooks/api/certificateTemplates/queries.tsx
Normal file
24
frontend/src/hooks/api/certificateTemplates/queries.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TCertificateTemplate } from "./types";
|
||||
|
||||
export const certTemplateKeys = {
|
||||
getCertTemplateById: (id: string) => [{ id }, "cert-template"]
|
||||
};
|
||||
|
||||
export const useGetCertTemplate = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: certTemplateKeys.getCertTemplateById(id),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { certificateTemplate }
|
||||
} = await apiRequest.get<{ certificateTemplate: TCertificateTemplate }>(
|
||||
`/api/v1/pki/certificate-templates/${id}`
|
||||
);
|
||||
return certificateTemplate;
|
||||
},
|
||||
enabled: Boolean(id)
|
||||
});
|
||||
};
|
||||
35
frontend/src/hooks/api/certificateTemplates/types.ts
Normal file
35
frontend/src/hooks/api/certificateTemplates/types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export type TCertificateTemplateListEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
caName: string;
|
||||
};
|
||||
|
||||
export type TCertificateTemplate = {
|
||||
id: string;
|
||||
caId: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
ttl: string;
|
||||
};
|
||||
|
||||
export type TCreateCertificateTemplateDTO = {
|
||||
caId: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
ttl: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TUpdateCertificateTemplateDTO = {
|
||||
id: string;
|
||||
caId?: string;
|
||||
name?: string;
|
||||
commonName?: string;
|
||||
ttl?: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TDeleteCertificateTemplateDTO = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
};
|
||||
@@ -7,6 +7,7 @@ export * from "./auth";
|
||||
export * from "./bots";
|
||||
export * from "./ca";
|
||||
export * from "./certificates";
|
||||
export * from "./certificateTemplates";
|
||||
export * from "./dynamicSecret";
|
||||
export * from "./dynamicSecretLease";
|
||||
export * from "./groups";
|
||||
|
||||
@@ -25,6 +25,7 @@ export {
|
||||
useGetWorkspaceUsers,
|
||||
useListWorkspaceCas,
|
||||
useListWorkspaceCertificates,
|
||||
useListWorkspaceCertificateTemplates,
|
||||
useListWorkspaceGroups,
|
||||
useListWorkspacePkiAlerts,
|
||||
useListWorkspacePkiCollections,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { apiRequest } from "@app/config/request";
|
||||
import { CaStatus } from "../ca/enums";
|
||||
import { TCertificateAuthority } from "../ca/types";
|
||||
import { TCertificate } from "../certificates/types";
|
||||
import { TCertificateTemplateListEntry } from "../certificateTemplates/types";
|
||||
import { TGroupMembership } from "../groups/types";
|
||||
import { identitiesKeys } from "../identities/queries";
|
||||
import { IdentityMembership } from "../identities/types";
|
||||
@@ -68,7 +69,9 @@ export const workspaceKeys = {
|
||||
getWorkspacePkiAlerts: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-pki-alerts"] as const,
|
||||
getWorkspacePkiCollections: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-pki-collections"] as const
|
||||
[{ workspaceId }, "workspace-pki-collections"] as const,
|
||||
getWorkspaceCertificateTemplates: (workspaceId: string) =>
|
||||
[{ workspaceId }, "workspace-certificate-templates"] as const
|
||||
};
|
||||
|
||||
const fetchWorkspaceById = async (workspaceId: string) => {
|
||||
@@ -639,3 +642,19 @@ export const useListWorkspacePkiCollections = ({ workspaceId }: { workspaceId: s
|
||||
enabled: Boolean(workspaceId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useListWorkspaceCertificateTemplates = ({ workspaceId }: { workspaceId: string }) => {
|
||||
return useQuery({
|
||||
queryKey: workspaceKeys.getWorkspaceCertificateTemplates(workspaceId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { certificateTemplates }
|
||||
} = await apiRequest.get<{ certificateTemplates: TCertificateTemplateListEntry[] }>(
|
||||
`/api/v2/workspace/${workspaceId}/certificate-templates`
|
||||
);
|
||||
|
||||
return { certificateTemplates };
|
||||
},
|
||||
enabled: Boolean(workspaceId)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { CertificateTemplatesSection } from "./components/CertificateTemplatesSection";
|
||||
import { CertificatesSection } from "./components";
|
||||
|
||||
export const CertificatesTab = () => {
|
||||
@@ -11,6 +12,7 @@ export const CertificatesTab = () => {
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<CertificateTemplatesSection />
|
||||
<CertificatesSection />
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
CaStatus,
|
||||
useCreateCertTemplate,
|
||||
useGetCertTemplate,
|
||||
useListWorkspaceCas,
|
||||
useUpdateCertTemplate
|
||||
} from "@app/hooks/api";
|
||||
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z.object({
|
||||
caId: z.string(),
|
||||
name: z.string().min(1),
|
||||
commonName: z.string().trim().min(1),
|
||||
ttl: z.string().trim().min(1)
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["certificateTemplate"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["certificateTemplate"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data: certTemplate } = useGetCertTemplate(
|
||||
(popUp?.certificateTemplate?.data as { id: string })?.id || ""
|
||||
);
|
||||
|
||||
const { data: cas } = useListWorkspaceCas({
|
||||
projectSlug: currentWorkspace?.slug ?? "",
|
||||
status: CaStatus.ACTIVE
|
||||
});
|
||||
|
||||
const { mutateAsync: createCertTemplate } = useCreateCertTemplate();
|
||||
const { mutateAsync: updateCertTemplate } = useUpdateCertTemplate();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (certTemplate) {
|
||||
reset({
|
||||
caId: certTemplate.caId,
|
||||
name: certTemplate.name,
|
||||
commonName: certTemplate.commonName,
|
||||
ttl: certTemplate.ttl
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
caId: "",
|
||||
name: "",
|
||||
commonName: "",
|
||||
ttl: ""
|
||||
});
|
||||
}
|
||||
}, [certTemplate]);
|
||||
|
||||
const onFormSubmit = async ({ caId, name, commonName, ttl }: FormData) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (certTemplate) {
|
||||
await updateCertTemplate({
|
||||
id: certTemplate.id,
|
||||
projectId: currentWorkspace.id,
|
||||
caId,
|
||||
name,
|
||||
commonName,
|
||||
ttl
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated certificate template",
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
await createCertTemplate({
|
||||
projectId: currentWorkspace.id,
|
||||
caId,
|
||||
name,
|
||||
commonName,
|
||||
ttl
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created certificate template",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
reset();
|
||||
handlePopUpToggle("certificateTemplate", false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to save changes",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.certificateTemplate?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("certificateTemplate", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title={certTemplate ? "Certificate Template" : "Create Certificate Template"}>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Template Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="My Certificate Template" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="caId"
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Issuing CA"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="mt-4"
|
||||
isRequired
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{(cas || []).map(({ id, type, dn }) => (
|
||||
<SelectItem value={id} key={`ca-${id}`}>
|
||||
{`${caTypeToNameMap[type]}: ${dn}`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="commonName"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Common Name (CN)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="service.acme.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="ttl"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="TTL"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("certificateTemplate", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import { faPlus } 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, DeleteActionModal } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteCertTemplate } from "@app/hooks/api";
|
||||
|
||||
import { CertificateTemplateModal } from "./CertificateTemplateModal";
|
||||
import { CertificateTemplatesTable } from "./CertificateTemplatesTable";
|
||||
|
||||
export const CertificateTemplatesSection = () => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"certificateTemplate",
|
||||
"deleteCertificateTemplate"
|
||||
] as const);
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { mutateAsync: deleteCertTemplate } = useDeleteCertTemplate();
|
||||
|
||||
const onRemoveCertificateTemplateSubmit = async (id: string) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteCertTemplate({
|
||||
id,
|
||||
projectId: currentWorkspace.id
|
||||
});
|
||||
|
||||
await createNotification({
|
||||
text: "Successfully deleted certificate template",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteCertificateTemplate");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete certificate template",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Certificate Templates</p>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Certificates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("certificateTemplate")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create Certificate Template
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<CertificateTemplatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<CertificateTemplateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteCertificateTemplate.isOpen}
|
||||
title={`Are you sure want to delete the certificate template ${
|
||||
(popUp?.deleteCertificateTemplate?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteCertificateTemplate", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveCertificateTemplateSubmit(
|
||||
(popUp?.deleteCertificateTemplate?.data as { id: string })?.id
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import { faEllipsis, faGear, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { useListWorkspaceCertificateTemplates } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["certificateTemplate", "deleteCertificateTemplate"]>,
|
||||
data?: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const CertificateTemplatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isLoading } = useListWorkspaceCertificateTemplates({
|
||||
workspaceId: currentWorkspace?.id ?? ""
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Certificate Authority</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={2} innerKey="project-cas" />}
|
||||
{!isLoading &&
|
||||
data?.certificateTemplates.map((certificateTemplate) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`certificate-${certificateTemplate.id}`}>
|
||||
<Td>{certificateTemplate.name}</Td>
|
||||
<Td>{certificateTemplate.caName}</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">
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
handlePopUpOpen("certificateTemplate", {
|
||||
id: certificateTemplate.id
|
||||
})
|
||||
}
|
||||
icon={<FontAwesomeIcon icon={faGear} />}
|
||||
>
|
||||
Manage
|
||||
</DropdownMenuItem>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.CertificateTemplates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
disabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("deleteCertificateTemplate", {
|
||||
id: certificateTemplate.id,
|
||||
name: certificateTemplate.name
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete Template
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && !data?.certificateTemplates?.length && (
|
||||
<EmptyState title="No certificate templates have been created" icon={faGear} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user