From 3a0e2bf88b3b4759aee70d6b5e4154164bed7272 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 30 May 2025 14:41:01 +0530 Subject: [PATCH] feat: completed frontend changes for new pki templates --- .../ProjectPermissionContext/index.tsx | 1 + .../context/ProjectPermissionContext/types.ts | 22 +- frontend/src/context/index.tsx | 1 + .../hooks/api/certificateTemplates/index.tsx | 5 +- .../api/certificateTemplates/mutations.tsx | 55 +++ .../api/certificateTemplates/queries.tsx | 35 +- .../hooks/api/certificateTemplates/types.ts | 54 +++ .../layouts/ProjectLayout/ProjectLayout.tsx | 14 + .../CertificateTemplatesSection.tsx | 8 +- .../components/CertificateTemplatesTable.tsx | 10 +- .../PkiTemplateListPage.tsx | 267 ++++++++++++ .../components/PkiTemplateForm.tsx | 407 ++++++++++++++++++ .../PkiTemplateListPage/route.tsx | 9 + .../PkiTemplatePermissionConditions.tsx | 173 ++++++++ .../ProjectRoleModifySection.utils.tsx | 60 ++- .../components/RolePermissionsSection.tsx | 5 + frontend/src/routeTree.gen.ts | 76 ++++ frontend/src/routes.ts | 1 + 18 files changed, 1188 insertions(+), 15 deletions(-) create mode 100644 frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx create mode 100644 frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx create mode 100644 frontend/src/pages/cert-manager/PkiTemplateListPage/route.tsx create mode 100644 frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx diff --git a/frontend/src/context/ProjectPermissionContext/index.tsx b/frontend/src/context/ProjectPermissionContext/index.tsx index d7b7334ea..f564ac74e 100644 --- a/frontend/src/context/ProjectPermissionContext/index.tsx +++ b/frontend/src/context/ProjectPermissionContext/index.tsx @@ -10,6 +10,7 @@ export { ProjectPermissionKmipActions, ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, + ProjectPermissionPkiTemplateActions, ProjectPermissionSshHostActions, ProjectPermissionSub } from "./types"; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index a640f6eaa..169338039 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -104,6 +104,15 @@ export enum ProjectPermissionPkiSubscriberActions { ListCerts = "list-certs" } +export enum ProjectPermissionPkiTemplateActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete", + IssueCert = "issue-cert", + ListCerts = "list-certs" +} + export enum ProjectPermissionSecretRotationActions { Read = "read", ReadGeneratedCredentials = "read-generated-credentials", @@ -238,6 +247,11 @@ export type PkiSubscriberSubjectFields = { name: string; }; +export type PkiTemplateSubjectFields = { + name: string; + // (dangtony98): consider adding [commonName] as a subject field in the future +}; + export type ProjectPermissionSet = | [ ProjectPermissionSecretActions, @@ -295,7 +309,13 @@ export type ProjectPermissionSet = ] | [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities] | [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates] - | [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates] + | [ + ProjectPermissionPkiTemplateActions, + ( + | ProjectPermissionSub.CertificateTemplates + | (ForcedSubject & PkiTemplateSubjectFields) + ) + ] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index 3641e8a3c..833956d77 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -19,6 +19,7 @@ export { ProjectPermissionKmipActions, ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, + ProjectPermissionPkiTemplateActions, ProjectPermissionSshHostActions, ProjectPermissionSub, useProjectPermission diff --git a/frontend/src/hooks/api/certificateTemplates/index.tsx b/frontend/src/hooks/api/certificateTemplates/index.tsx index 61dfb35a2..a48248a6b 100644 --- a/frontend/src/hooks/api/certificateTemplates/index.tsx +++ b/frontend/src/hooks/api/certificateTemplates/index.tsx @@ -1,8 +1,11 @@ export { useCreateCertTemplate, + useCreateCertTemplateV2, useCreateEstConfig, useDeleteCertTemplate, + useDeleteCertTemplateV2, useUpdateCertTemplate, + useUpdateCertTemplateV2, useUpdateEstConfig } from "./mutations"; -export { useGetCertTemplate, useGetEstConfig } from "./queries"; +export { useGetCertTemplate, useGetEstConfig, useListCertificateTemplates } from "./queries"; diff --git a/frontend/src/hooks/api/certificateTemplates/mutations.tsx b/frontend/src/hooks/api/certificateTemplates/mutations.tsx index 55bbcd417..d1069e7e6 100644 --- a/frontend/src/hooks/api/certificateTemplates/mutations.tsx +++ b/frontend/src/hooks/api/certificateTemplates/mutations.tsx @@ -8,9 +8,12 @@ import { certTemplateKeys } from "./queries"; import { TCertificateTemplate, TCreateCertificateTemplateDTO, + TCreateCertificateTemplateV2DTO, TCreateEstConfigDTO, TDeleteCertificateTemplateDTO, + TDeleteCertificateTemplateV2DTO, TUpdateCertificateTemplateDTO, + TUpdateCertificateTemplateV2DTO, TUpdateEstConfigDTO } from "./types"; @@ -73,6 +76,58 @@ export const useDeleteCertTemplate = () => { }); }; +export const useCreateCertTemplateV2 = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (dto) => { + const { data } = await apiRequest.post<{ + certificateTemplate: TCertificateTemplate; + }>("/api/v2/pki/certificate-templates", dto); + return data.certificateTemplate; + }, + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + } + }); +}; + +export const useUpdateCertTemplateV2 = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (dto) => { + const { data } = await apiRequest.patch<{ certificateTemplate: TCertificateTemplate }>( + `/api/v2/pki/certificate-templates/${dto.templateName}`, + dto + ); + + return data.certificateTemplate; + }, + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + } + }); +}; + +export const useDeleteCertTemplateV2 = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (dto) => { + const { data } = await apiRequest.delete<{ certificateTemplate: TCertificateTemplate }>( + `/api/v2/pki/certificate-templates/${dto.templateName}`, + { + data: { + projectId: dto.projectId + } + } + ); + return data.certificateTemplate; + }, + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + } + }); +}; + export const useCreateEstConfig = () => { const queryClient = useQueryClient(); return useMutation({ diff --git a/frontend/src/hooks/api/certificateTemplates/queries.tsx b/frontend/src/hooks/api/certificateTemplates/queries.tsx index 7ee5bbd30..435345ad9 100644 --- a/frontend/src/hooks/api/certificateTemplates/queries.tsx +++ b/frontend/src/hooks/api/certificateTemplates/queries.tsx @@ -2,10 +2,20 @@ import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { TCertificateTemplate, TEstConfig } from "./types"; +import { + TCertificateTemplate, + TCertificateTemplateV2, + TEstConfig, + TListCertificateTemplatesDTO +} from "./types"; export const certTemplateKeys = { getCertTemplateById: (id: string) => [{ id }, "cert-template"], + listTemplates: ({ projectId, ...el }: { limit?: number; offset?: number; projectId: string }) => [ + "list-template", + projectId, + el + ], getEstConfig: (id: string) => [{ id }, "cert-template-est-config"] }; @@ -22,6 +32,29 @@ export const useGetCertTemplate = (id: string) => { }); }; +export const useListCertificateTemplates = ({ + limit = 100, + offset = 0, + projectId +}: TListCertificateTemplatesDTO) => { + return useQuery({ + queryKey: certTemplateKeys.listTemplates({ limit, offset, projectId }), + queryFn: async () => { + const { data } = await apiRequest.get<{ + certificateTemplates: TCertificateTemplateV2[]; + totalCount?: number; + }>("/api/v2/pki/certificate-templates", { + params: { + limit, + offset, + projectId + } + }); + return data; + } + }); +}; + export const useGetEstConfig = (certificateTemplateId: string) => { return useQuery({ queryKey: certTemplateKeys.getEstConfig(certificateTemplateId), diff --git a/frontend/src/hooks/api/certificateTemplates/types.ts b/frontend/src/hooks/api/certificateTemplates/types.ts index 14367a280..3e3e728f8 100644 --- a/frontend/src/hooks/api/certificateTemplates/types.ts +++ b/frontend/src/hooks/api/certificateTemplates/types.ts @@ -14,6 +14,26 @@ export type TCertificateTemplate = { extendedKeyUsages: CertExtendedKeyUsage[]; }; +export type TCertificateTemplateV2 = { + id: string; + caId: string; + caName: string; + projectId: string; + pkiCollectionId?: string; + name: string; + commonName: string; + subjectAlternativeName: string; + ttl: string; + keyUsages: CertKeyUsage[]; + extendedKeyUsages: CertExtendedKeyUsage[]; + updatedAt: string; + createdAt: string; + ca: { + name: string; + id: string; + }; +}; + export type TCreateCertificateTemplateDTO = { caId: string; pkiCollectionId?: string; @@ -44,6 +64,34 @@ export type TDeleteCertificateTemplateDTO = { projectId: string; }; +export type TCreateCertificateTemplateV2DTO = { + caId: string; + name: string; + commonName: string; + subjectAlternativeName: string; + ttl: string; + projectId: string; + keyUsages: CertKeyUsage[]; + extendedKeyUsages: CertExtendedKeyUsage[]; +}; + +export type TUpdateCertificateTemplateV2DTO = { + templateName: string; + caId?: string; + name?: string; + commonName?: string; + subjectAlternativeName?: string; + ttl?: string; + projectId: string; + keyUsages?: CertKeyUsage[]; + extendedKeyUsages?: CertExtendedKeyUsage[]; +}; + +export type TDeleteCertificateTemplateV2DTO = { + templateName: string; + projectId: string; +}; + export type TCreateEstConfigDTO = { certificateTemplateId: string; caChain?: string; @@ -67,3 +115,9 @@ export type TEstConfig = { isEnabled: boolean; disableBootstrapCertValidation: boolean; }; + +export type TListCertificateTemplatesDTO = { + limit?: number; + offset?: number; + projectId: string; +}; diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 35534d4a2..ea331f866 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -117,6 +117,20 @@ export const ProjectLayout = () => { )} + + {({ isActive }) => ( + + Certificate Templates + + )} + {

Certificate Templates

{(isAllowed) => ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx index 64b829844..e7d137807 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesTable.tsx @@ -19,7 +19,11 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context"; +import { + ProjectPermissionPkiTemplateActions, + ProjectPermissionSub, + useSubscription +} from "@app/context"; import { useGetCaCertTemplates } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -79,7 +83,7 @@ export const CertificateTemplatesTable = ({ handlePopUpOpen, caId }: Props) => { Manage Policies {(isAllowed) => ( @@ -105,7 +109,7 @@ export const CertificateTemplatesTable = ({ handlePopUpOpen, caId }: Props) => { )} {(isAllowed) => ( diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx new file mode 100644 index 000000000..7bbb08008 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx @@ -0,0 +1,267 @@ +import { useState } from "react"; +import { Helmet } from "react-helmet"; +import { useTranslation } from "react-i18next"; +import { + faArrowUpRightFromSquare, + faCertificate, + 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 { 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, + useWorkspace +} from "@app/context"; +import { usePopUp } from "@app/hooks"; +import { useDeleteCertTemplateV2 } from "@app/hooks/api"; +import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries"; + +import { PkiTemplateForm } from "./components/PkiTemplateForm"; + +const PER_PAGE_INIT = 25; +export const PkiTemplateListPage = () => { + const { t } = useTranslation(); + const { currentWorkspace } = useWorkspace(); + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(PER_PAGE_INIT); + const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + "certificateTemplate", + "deleteTemplate" + ] as const); + + const { data, isPending } = useListCertificateTemplates({ + projectId: currentWorkspace.id, + offset: (page - 1) * perPage, + limit: perPage + }); + + const deleteCertTemplate = useDeleteCertTemplateV2(); + + const onRemovePkiSubscriberSubmit = async () => { + try { + const pkiTemplate = await deleteCertTemplate.mutateAsync({ + projectId: currentWorkspace.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 ( + <> + + {t("common.head-title", { title: "PKI Subscribers" })} + +
+
+
+ +
+
+
+

Templates

+
+ + + Documentation{" "} + + + + + {(isAllowed) => ( + + )} + +
+
+ + + + + + + + + + + {isPending && } + {!isPending && + data?.certificateTemplates?.map((template) => { + return ( + + + + + + + ); + })} + {!isPending && !data?.certificateTemplates?.length && ( + + + + )} + +
NameCALast Updated At +
{template.name} + {template.ca.name} + {format(new Date(template.updatedAt), "yyyy-MM-dd | HH:mm:ss")} + + +
+ + + +
+
+ + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("certificateTemplate", template); + }} + disabled={!isAllowed} + icon={} + > + Edit Template + + )} + + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("deleteTemplate", template); + }} + disabled={!isAllowed} + icon={} + > + Delete Template + + )} + + +
+
+ +
+ {!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && ( + setPage(newPage)} + onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + /> + )} +
+ handlePopUpToggle("deleteTemplate", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => onRemovePkiSubscriberSubmit()} + /> +
+
+
+ handlePopUpToggle("certificateTemplate", isOpen)} + > + + handlePopUpToggle("certificateTemplate", isOpen)} + /> + + +
+ + ); +}; diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx new file mode 100644 index 000000000..0a78ddac2 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx @@ -0,0 +1,407 @@ +import { Controller, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + Checkbox, + FilterableSelect, + FormControl, + FormLabel, + Input, + Tooltip +} from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { + useCreateCertTemplateV2, + useListCasByProjectId, + useUpdateCertTemplateV2 +} from "@app/hooks/api"; +import { + EXTENDED_KEY_USAGES_OPTIONS, + KEY_USAGES_OPTIONS +} from "@app/hooks/api/certificates/constants"; +import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums"; +import { TCertificateTemplateV2 } from "@app/hooks/api/certificateTemplates/types"; +import { slugSchema } from "@app/lib/schemas"; + +const validateTemplateRegexField = z.string().trim().min(1).max(100); + +const schema = z.object({ + ca: z.object({ + name: z.string(), + id: z.string() + }), + name: slugSchema(), + commonName: validateTemplateRegexField, + subjectAlternativeName: validateTemplateRegexField, + ttl: z.string().trim().min(1), + keyUsages: z.object({ + [CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(), + [CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(), + [CertKeyUsage.NON_REPUDIATION]: z.boolean().optional(), + [CertKeyUsage.DATA_ENCIPHERMENT]: z.boolean().optional(), + [CertKeyUsage.KEY_AGREEMENT]: z.boolean().optional(), + [CertKeyUsage.KEY_CERT_SIGN]: z.boolean().optional(), + [CertKeyUsage.CRL_SIGN]: z.boolean().optional(), + [CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(), + [CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional() + }), + extendedKeyUsages: z.object({ + [CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(), + [CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(), + [CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(), + [CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(), + [CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(), + [CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional() + }) +}); + +export type FormData = z.infer; + +type Props = { + certTemplate?: TCertificateTemplateV2; + handlePopUpToggle: (state?: boolean) => void; +}; + +export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { + const { currentWorkspace } = useWorkspace(); + + const { data: cas, isPending: isCaLoading } = useListCasByProjectId(currentWorkspace.id); + + const { mutateAsync: createCertTemplate } = useCreateCertTemplateV2(); + const { mutateAsync: updateCertTemplate } = useUpdateCertTemplateV2(); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: async () => { + if (certTemplate) { + return { + ca: certTemplate.ca, + name: certTemplate.name, + commonName: certTemplate.commonName, + subjectAlternativeName: certTemplate.subjectAlternativeName, + ttl: certTemplate.ttl, + keyUsages: Object.fromEntries(certTemplate.keyUsages.map((name) => [name, true]) ?? []), + extendedKeyUsages: Object.fromEntries( + certTemplate.extendedKeyUsages.map((name) => [name, true]) ?? [] + ) + }; + } + return { + ca: { name: "", id: "" }, + name: "", + subjectAlternativeName: "", + commonName: "", + ttl: "", + keyUsages: { + [CertKeyUsage.DIGITAL_SIGNATURE]: true, + [CertKeyUsage.KEY_ENCIPHERMENT]: true + }, + extendedKeyUsages: {} + }; + } + }); + + const onFormSubmit = async ({ + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages, + extendedKeyUsages, + ca + }: FormData) => { + if (!currentWorkspace?.id) { + return; + } + + try { + if (certTemplate) { + await updateCertTemplate({ + templateName: certTemplate.name, + projectId: currentWorkspace.id, + caId: ca.id, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertKeyUsage), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertExtendedKeyUsage) + }); + + createNotification({ + text: "Successfully updated certificate template", + type: "success" + }); + } else { + await createCertTemplate({ + projectId: currentWorkspace.id, + caId: ca.id, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertKeyUsage), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertExtendedKeyUsage) + }); + + createNotification({ + text: "Successfully created certificate template", + type: "success" + }); + } + + reset(); + handlePopUpToggle(false); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to save changes", + type: "error" + }); + } + }; + + return ( +
+ {certTemplate && ( + + + + )} + ( + + + + )} + /> + ( + + option.id} + getOptionLabel={(option) => option.name} + /> + + )} + control={control} + name="ca" + /> + ( + + + This field accepts limited regular expressions: spaces, *, ., @, -, \ (for + escaping), and alphanumeric characters only + + } + > + + + } + /> +
+ } + isError={Boolean(error)} + errorText={error?.message} + isRequired + > + + + )} + /> + ( + + + This field accepts limited regular expressions: spaces, *, ., @, -, \ (for + escaping), and alphanumeric characters only + + } + > + + + } + /> +
+ } + isError={Boolean(error)} + errorText={error?.message} + isRequired + > + + + )} + /> + ( + + + + )} + /> + + + +
Key Usage
+
+ + { + return ( + +
+ {KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => { + return ( + { + onChange({ + ...value, + [optionValue]: state + }); + }} + > + {label} + + ); + })} +
+
+ ); + }} + /> + { + return ( + +
+ {EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => { + return ( + { + onChange({ + ...value, + [optionValue]: state + }); + }} + > + {label} + + ); + })} +
+
+ ); + }} + /> +
+
+
+
+ + +
+ + ); +}; diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/route.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/route.tsx new file mode 100644 index 000000000..5647c45f5 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/route.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { PkiTemplateListPage } from "./PkiTemplateListPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/" +)({ + component: PkiTemplateListPage +}); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx new file mode 100644 index 000000000..b581b7bea --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PkiTemplatePermissionConditions.tsx @@ -0,0 +1,173 @@ +import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; +import { + PermissionConditionOperators, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; + +import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers"; +import { TFormSchema } from "./ProjectRoleModifySection.utils"; + +type Props = { + position?: number; + isDisabled?: boolean; +}; + +export const PkiTemplatePermissionConditions = ({ position = 0, isDisabled }: Props) => { + const { + control, + watch, + formState: { errors } + } = useFormContext(); + + const permissionSubject = ProjectPermissionSub.CertificateTemplates; + const items = useFieldArray({ + control, + name: `permissions.${permissionSubject}.${position}.conditions` + }); + + return ( +
+

Conditions

+

+ Conditions determine when a policy will be applied (always if no conditions are present). +

+

+ All conditions must evaluate to true for the policy to take effect. +

+
+ {items.fields.map((el, index) => { + const condition = + (watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as { + lhs: string; + rhs: string; + operator: string; + }) || {}; + + return ( +
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> + + + +
+
+ ( + + + + )} + /> +
+
+ items.remove(index)} + > + + +
+
+ ); + })} +
+ {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && ( +
+ + {errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message} +
+ )} +
+ +
+
+ ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index a54c844ad..09a2638cc 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -18,6 +18,7 @@ import { ProjectPermissionKmipActions, ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, + ProjectPermissionPkiTemplateActions, ProjectPermissionSecretActions, ProjectPermissionSecretRotationActions, ProjectPermissionSecretSyncActions, @@ -148,6 +149,15 @@ const PkiSubscriberPolicyActionSchema = z.object({ [ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional() }); +const PkiTemplatePolicyActionSchema = z.object({ + [ProjectPermissionPkiTemplateActions.Read]: z.boolean().optional(), + [ProjectPermissionPkiTemplateActions.Create]: z.boolean().optional(), + [ProjectPermissionPkiTemplateActions.Edit]: z.boolean().optional(), + [ProjectPermissionPkiTemplateActions.Delete]: z.boolean().optional(), + [ProjectPermissionPkiTemplateActions.IssueCert]: z.boolean().optional(), + [ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional() +}); + const SecretRollbackPolicyActionSchema = z.object({ read: z.boolean().optional(), create: z.boolean().optional() @@ -255,7 +265,12 @@ export const projectRoleFormSchema = z.object({ .default([]), [ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]), [ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]), - [ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]), + [ProjectPermissionSub.CertificateTemplates]: PkiTemplatePolicyActionSchema.extend({ + inverted: z.boolean().optional(), + conditions: ConditionSchema + }) + .array() + .default([]), [ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default( [] ), @@ -295,6 +310,7 @@ type TConditionalFields = | ProjectPermissionSub.SecretImports | ProjectPermissionSub.DynamicSecrets | ProjectPermissionSub.PkiSubscribers + | ProjectPermissionSub.CertificateTemplates | ProjectPermissionSub.SshHosts | ProjectPermissionSub.SecretRotation | ProjectPermissionSub.Identity; @@ -309,7 +325,8 @@ export const isConditionalSubjects = ( subject === ProjectPermissionSub.Identity || subject === ProjectPermissionSub.SshHosts || subject === ProjectPermissionSub.SecretRotation || - subject === ProjectPermissionSub.PkiSubscribers; + subject === ProjectPermissionSub.PkiSubscribers || + subject === ProjectPermissionSub.CertificateTemplates; const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => { const formConditions: z.infer = []; @@ -408,7 +425,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { ProjectPermissionSub.CertificateAuthorities, ProjectPermissionSub.PkiAlerts, ProjectPermissionSub.PkiCollections, - ProjectPermissionSub.CertificateTemplates, ProjectPermissionSub.Tags, ProjectPermissionSub.SecretRotation, ProjectPermissionSub.Kms, @@ -781,6 +797,34 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], inverted }); + return; + } + + if (subject === ProjectPermissionSub.CertificateTemplates) { + if (!formVal[subject]) formVal[subject] = []; + + formVal[subject]!.push({ + [ProjectPermissionPkiTemplateActions.Edit]: action.includes( + ProjectPermissionPkiTemplateActions.Edit + ), + [ProjectPermissionPkiTemplateActions.Delete]: action.includes( + ProjectPermissionPkiTemplateActions.Delete + ), + [ProjectPermissionPkiTemplateActions.Create]: action.includes( + ProjectPermissionPkiTemplateActions.Create + ), + [ProjectPermissionPkiTemplateActions.Read]: action.includes( + ProjectPermissionPkiTemplateActions.Read + ), + [ProjectPermissionPkiTemplateActions.IssueCert]: action.includes( + ProjectPermissionPkiTemplateActions.IssueCert + ), + [ProjectPermissionPkiTemplateActions.ListCerts]: action.includes( + ProjectPermissionPkiTemplateActions.ListCerts + ), + conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], + inverted + }); } }); @@ -1119,10 +1163,12 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { [ProjectPermissionSub.CertificateTemplates]: { title: "Certificate Templates", actions: [ - { label: "Read", value: "read" }, - { label: "Create", value: "create" }, - { label: "Modify", value: "edit" }, - { label: "Remove", value: "delete" } + { label: "Read", value: ProjectPermissionPkiTemplateActions.Read }, + { label: "Create", value: ProjectPermissionPkiTemplateActions.Create }, + { label: "Modify", value: ProjectPermissionPkiTemplateActions.Edit }, + { label: "Remove", value: ProjectPermissionPkiTemplateActions.Delete }, + { label: "Issue Certificates", value: ProjectPermissionPkiTemplateActions.IssueCert }, + { label: "List Certificates", value: ProjectPermissionPkiTemplateActions.ListCerts } ] }, [ProjectPermissionSub.SshCertificateAuthorities]: { diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index 593b40e93..a1bbe3d54 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -23,6 +23,7 @@ import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies"; import { IdentityManagementPermissionConditions } from "./IdentityManagementPermissionConditions"; import { PermissionEmptyState } from "./PermissionEmptyState"; import { PkiSubscriberPermissionConditions } from "./PkiSubscriberPermissionConditions"; +import { PkiTemplatePermissionConditions } from "./PkiTemplatePermissionConditions"; import { formRolePermission2API, isConditionalSubjects, @@ -63,6 +64,10 @@ export const renderConditionalComponents = ( return ; } + if (subject === ProjectPermissionSub.CertificateTemplates) { + return ; + } + return ; } diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index bc19deb6c..72b28878f 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -123,6 +123,7 @@ import { Route as certManagerPkiSubscriberDetailsByIDPageRouteImport } from './p import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route' import { Route as secretManagerIntegrationsListPageRouteImport } from './pages/secret-manager/IntegrationsListPage/route' import { Route as certManagerPkiSubscribersPageRouteImport } from './pages/cert-manager/PkiSubscribersPage/route' +import { Route as certManagerPkiTemplateListPageRouteImport } from './pages/cert-manager/PkiTemplateListPage/route' import { Route as secretManagerIntegrationsWindmillConfigurePageRouteImport } from './pages/secret-manager/integrations/WindmillConfigurePage/route' import { Route as secretManagerIntegrationsWindmillAuthorizePageRouteImport } from './pages/secret-manager/integrations/WindmillAuthorizePage/route' import { Route as secretManagerIntegrationsVercelConfigurePageRouteImport } from './pages/secret-manager/integrations/VercelConfigurePage/route' @@ -257,6 +258,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayout createFileRoute( '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers', )() +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesImport = + createFileRoute( + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates', + )() // Create/Update Routes @@ -870,6 +875,15 @@ const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayout } as any, ) +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute = + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesImport.update( + { + id: '/certificate-templates', + path: '/certificate-templates', + getParentRoute: () => certManagerLayoutRoute, + } as any, + ) + const projectAccessControlPageRouteCertManagerRoute = projectAccessControlPageRouteCertManagerImport.update({ id: '/access-management', @@ -1156,6 +1170,14 @@ const certManagerPkiSubscribersPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute, } as any) +const certManagerPkiTemplateListPageRouteRoute = + certManagerPkiTemplateListPageRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute, + } as any) + const secretManagerIntegrationsWindmillConfigurePageRouteRoute = secretManagerIntegrationsWindmillConfigurePageRouteImport.update({ id: '/windmill/create', @@ -2391,6 +2413,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport parentRoute: typeof certManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates': { + id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates' + path: '/certificate-templates' + fullPath: '/cert-manager/$projectId/certificate-templates' + preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesImport + parentRoute: typeof certManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': { id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers' path: '/subscribers' @@ -2489,6 +2518,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSshImport parentRoute: typeof sshLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/': { + id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/' + path: '/' + fullPath: '/cert-manager/$projectId/certificate-templates/' + preLoaderRoute: typeof certManagerPkiTemplateListPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesImport + } '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/': { id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/' path: '/' @@ -3360,6 +3396,21 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren, ) +interface AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteChildren { + certManagerPkiTemplateListPageRouteRoute: typeof certManagerPkiTemplateListPageRouteRoute +} + +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteChildren = + { + certManagerPkiTemplateListPageRouteRoute: + certManagerPkiTemplateListPageRouteRoute, + } + +const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren = + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute._addFileChildren( + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteChildren, + ) + interface AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren { certManagerPkiSubscribersPageRouteRoute: typeof certManagerPkiSubscribersPageRouteRoute certManagerPkiSubscriberDetailsByIDPageRouteRoute: typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute @@ -3384,6 +3435,7 @@ interface certManagerLayoutRouteChildren { certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute @@ -3400,6 +3452,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, projectAccessControlPageRouteCertManagerRoute: projectAccessControlPageRouteCertManagerRoute, + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRoute: + AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren, certManagerCertAuthDetailsByIDPageRouteRoute: @@ -4089,6 +4143,7 @@ export interface FileRoutesByFullPath { '/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/cert-manager/$projectId/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/cert-manager/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren '/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute '/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute @@ -4103,6 +4158,7 @@ export interface FileRoutesByFullPath { '/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/secret-manager/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/cert-manager/$projectId/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute '/cert-manager/$projectId/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/secret-manager/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute '/cert-manager/$projectId/ca/$caName': typeof certManagerCertAuthDetailsByIDPageRouteRoute @@ -4288,6 +4344,7 @@ export interface FileRoutesByTo { '/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute '/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/cert-manager/$projectId/certificate-templates': typeof certManagerPkiTemplateListPageRouteRoute '/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute '/secret-manager/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute '/cert-manager/$projectId/ca/$caName': typeof certManagerCertAuthDetailsByIDPageRouteRoute @@ -4479,6 +4536,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute '/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute @@ -4493,6 +4551,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute + '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName': typeof certManagerCertAuthDetailsByIDPageRouteRoute @@ -4676,6 +4735,7 @@ export interface FileRouteTypes { | '/ssh/$projectId/overview' | '/ssh/$projectId/settings' | '/cert-manager/$projectId/access-management' + | '/cert-manager/$projectId/certificate-templates' | '/cert-manager/$projectId/subscribers' | '/integrations/azure-app-configuration/oauth2/callback' | '/integrations/azure-key-vault/oauth2/callback' @@ -4690,6 +4750,7 @@ export interface FileRouteTypes { | '/secret-manager/$projectId/access-management' | '/secret-manager/$projectId/integrations' | '/ssh/$projectId/access-management' + | '/cert-manager/$projectId/certificate-templates/' | '/cert-manager/$projectId/subscribers/' | '/secret-manager/$projectId/integrations/' | '/cert-manager/$projectId/ca/$caName' @@ -4874,6 +4935,7 @@ export interface FileRouteTypes { | '/kms/$projectId/access-management' | '/secret-manager/$projectId/access-management' | '/ssh/$projectId/access-management' + | '/cert-manager/$projectId/certificate-templates' | '/cert-manager/$projectId/subscribers' | '/secret-manager/$projectId/integrations' | '/cert-manager/$projectId/ca/$caName' @@ -5063,6 +5125,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers' | '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback' | '/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback' @@ -5077,6 +5140,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName' @@ -5605,6 +5669,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caName", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId", @@ -5731,6 +5796,13 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates": { + "filePath": "", + "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout", + "children": [ + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/" + ] + }, "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout", @@ -5871,6 +5943,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-ssh.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout" }, + "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates/": { + "filePath": "cert-manager/PkiTemplateListPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-templates" + }, "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/": { "filePath": "cert-manager/PkiSubscribersPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 7f2a250e7..8f7353e44 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -293,6 +293,7 @@ const certManagerRoutes = route("/cert-manager/$projectId", [ index("cert-manager/PkiSubscribersPage/route.tsx"), route("/$subscriberName", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx") ]), + route("/certificate-templates", [index("cert-manager/PkiTemplateListPage/route.tsx")]), route("/certificates", "cert-manager/CertificatesPage/route.tsx"), route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"), route("/alerting", "cert-manager/AlertingPage/route.tsx"),