-
-
- {profile.name}
- {profile.description && (
-
-
-
- )}
-
- {profile.slug}
+
+ {profile.slug}
+ {profile.description && (
+
+
+
+ )}
|
{getEnrollmentTypeBadge(profile.enrollmentType)} |
@@ -84,64 +77,47 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
- {templateData?.name || profile.certificateTemplateId}
+ {templateData?.slug || profile.certificateTemplateId}
|
-
+
{profile.metrics ? (
profile.metrics.totalCertificates === 0 ? (
- No certificates attached
+
+ No certificates
+
) : (
<>
{profile.metrics.activeCertificates > 0 && (
-
+
{profile.metrics.activeCertificates} active
-
+
)}
{profile.metrics.expiringCertificates > 0 && (
- <>
- {profile.metrics.activeCertificates > 0 && (
- •
- )}
-
- {profile.metrics.expiringCertificates} expiring
-
- >
+
+ {profile.metrics.expiringCertificates} expiring
+
)}
{profile.metrics.expiredCertificates > 0 && (
- <>
- {(profile.metrics.activeCertificates > 0 ||
- profile.metrics.expiringCertificates > 0) && (
- •
- )}
-
- {profile.metrics.expiredCertificates} expired
-
- >
+
+ {profile.metrics.expiredCertificates} expired
+
)}
{profile.metrics.revokedCertificates > 0 && (
- <>
- {(profile.metrics.activeCertificates > 0 ||
- profile.metrics.expiringCertificates > 0 ||
- profile.metrics.expiredCertificates > 0) && (
- •
- )}
-
- {profile.metrics.revokedCertificates} revoked
-
- >
+
+ {profile.metrics.revokedCertificates} revoked
+
)}
>
)
) : (
- No metrics available
+
+ No metrics
+
)}
|
-
- {formatDate(profile.createdAt)}
- |
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/index.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/index.ts
index f07d54d30..f3054ef1d 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/index.ts
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/index.ts
@@ -1,4 +1,3 @@
export { CertificateProfilesTab } from "./CertificateProfilesTab";
export { CreateProfileModal } from "./CreateProfileModal";
-export { EditProfileModal } from "./EditProfileModal";
export { ProfileList } from "./ProfileList";
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx
index cc680b1da..9fda6af2c 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx
@@ -12,7 +12,6 @@ import { useDeleteCertificateTemplateV2New } from "@app/hooks/api/certificateTem
import { TCertificateTemplateV2New } from "@app/hooks/api/certificateTemplates/types";
import { CreateTemplateModal } from "./CreateTemplateModal";
-import { EditTemplateModal } from "./EditTemplateModal";
import { TemplateList } from "./TemplateList";
export const CertificateTemplatesV2Tab = () => {
@@ -87,23 +86,24 @@ export const CertificateTemplatesV2Tab = () => {
{selectedTemplate && (
<>
- {
setIsEditModalOpen(false);
setSelectedTemplate(null);
}}
template={selectedTemplate}
+ mode="edit"
/>
{
setIsDeleteModalOpen(isOpen);
if (!isOpen) setSelectedTemplate(null);
}}
- deleteKey={selectedTemplate.name}
+ deleteKey={selectedTemplate.slug}
onDeleteApproved={handleDeleteConfirm}
/>
>
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
index 1ad7e43ce..787bacdcf 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
@@ -1,4 +1,3 @@
-import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -6,8 +5,14 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { createNotification } from "@app/components/notifications";
import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
Button,
+ Checkbox,
FormControl,
+ IconButton,
Input,
Modal,
ModalContent,
@@ -16,8 +21,13 @@ import {
TextArea
} from "@app/components/v2";
import { useProject } from "@app/context";
-import { useCreateCertificateTemplateV2New } from "@app/hooks/api/certificateTemplates/mutations";
+import {
+ useCreateCertificateTemplateV2New,
+ useUpdateCertificateTemplateV2New
+} from "@app/hooks/api/certificateTemplates/mutations";
+import { TCertificateTemplateV2New } from "@app/hooks/api/certificateTemplates/types";
+import { INCLUDE_OPTIONS, SAN_TYPES, SUBJECT_ATTRIBUTE_TYPES } from "./shared/utils";
import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared";
export type FormData = TemplateFormData;
@@ -25,22 +35,34 @@ export type FormData = TemplateFormData;
interface Props {
isOpen: boolean;
onClose: () => void;
+ template?: TCertificateTemplateV2New;
+ mode?: "create" | "edit";
}
-const ATTRIBUTE_TYPES = [{ value: "common_name", label: "Common Name (CN)" }];
+const ATTRIBUTE_TYPE_LABELS: Record<(typeof SUBJECT_ATTRIBUTE_TYPES)[number], string> = {
+ common_name: "Common Name (CN)",
+ organization_name: "Organization (O)",
+ organization_unit: "Organizational Unit (OU)",
+ locality: "Locality (L)",
+ state: "State/Province (ST)",
+ country: "Country (C)",
+ email: "Email Address",
+ street_address: "Street Address",
+ postal_code: "Postal Code"
+};
-const SAN_TYPES = [
- { value: "dns_name", label: "DNS Name" },
- { value: "ip_address", label: "IP Address" },
- { value: "email", label: "Email" },
- { value: "uri", label: "URI" }
-];
+const SAN_TYPE_LABELS: Record<(typeof SAN_TYPES)[number], string> = {
+ dns_name: "DNS Name",
+ ip_address: "IP Address",
+ email: "Email",
+ uri: "URI"
+};
-const INCLUDE_TYPES = [
- { value: "mandatory", label: "Mandatory", color: "red" },
- { value: "optional", label: "Optional", color: "blue" },
- { value: "prohibit", label: "Prohibited", color: "gray" }
-];
+const INCLUDE_TYPE_LABELS: Record<(typeof INCLUDE_OPTIONS)[number], string> = {
+ mandatory: "Mandatory",
+ optional: "Optional",
+ prohibit: "Prohibited"
+};
const SIGNATURE_ALGORITHMS = [
"SHA256-RSA",
@@ -60,32 +82,58 @@ const KEY_ALGORITHMS = [
"ECDSA-P521"
];
-export const CreateTemplateModal = ({ isOpen, onClose }: Props) => {
+export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => {
const { currentProject } = useProject();
const createTemplate = useCreateCertificateTemplateV2New();
- const [activeTab, setActiveTab] = useState("basic");
+ const updateTemplate = useUpdateCertificateTemplateV2New();
+
+ const isEdit = mode === "edit" && template;
const { control, handleSubmit, reset, watch, setValue } = useForm({
resolver: zodResolver(templateSchema),
- defaultValues: {
- name: "",
- description: "",
- attributes: [],
- keyUsages: { requiredUsages: [], optionalUsages: [] },
- extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
- subjectAlternativeNames: [],
- validity: {
- maxDuration: { value: 365, unit: "days" }
- },
- signatureAlgorithm: {
- allowedAlgorithms: ["SHA256-RSA"],
- defaultAlgorithm: "SHA256-RSA"
- },
- keyAlgorithm: {
- allowedKeyTypes: ["RSA-2048"],
- defaultKeyType: "RSA-2048"
- }
- }
+ defaultValues: isEdit
+ ? {
+ slug: template.slug,
+ description: template.description || "",
+ attributes: template.attributes || [],
+ keyUsages: {
+ requiredUsages: template.keyUsages?.requiredUsages?.all || [],
+ optionalUsages: template.keyUsages?.optionalUsages?.all || []
+ },
+ extendedKeyUsages: {
+ requiredUsages: template.extendedKeyUsages?.requiredUsages?.all || [],
+ optionalUsages: template.extendedKeyUsages?.optionalUsages?.all || []
+ },
+ subjectAlternativeNames: template.subjectAlternativeNames || [],
+ validity: template.validity || { maxDuration: { value: 365, unit: "days" } },
+ signatureAlgorithm: template.signatureAlgorithm || {
+ allowedAlgorithms: ["SHA256-RSA"],
+ defaultAlgorithm: "SHA256-RSA"
+ },
+ keyAlgorithm: template.keyAlgorithm || {
+ allowedKeyTypes: ["RSA-2048"],
+ defaultKeyType: "RSA-2048"
+ }
+ }
+ : {
+ slug: "",
+ description: "",
+ attributes: [],
+ keyUsages: { requiredUsages: [], optionalUsages: [] },
+ extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
+ subjectAlternativeNames: [],
+ validity: {
+ maxDuration: { value: 365, unit: "days" }
+ },
+ signatureAlgorithm: {
+ allowedAlgorithms: ["SHA256-RSA"],
+ defaultAlgorithm: "SHA256-RSA"
+ },
+ keyAlgorithm: {
+ allowedKeyTypes: ["RSA-2048"],
+ defaultKeyType: "RSA-2048"
+ }
+ }
});
const watchedAttributes = watch("attributes") || [];
@@ -95,48 +143,77 @@ export const CreateTemplateModal = ({ isOpen, onClose }: Props) => {
const onFormSubmit = async (data: FormData) => {
try {
- if (!currentProject?.id) return;
+ if (!currentProject?.id && !isEdit) return;
- const templateData = {
- projectId: currentProject.id,
- name: data.name,
- description: data.description,
- attributes: data.attributes || [],
- keyUsages: {
- requiredUsages: { all: data.keyUsages?.requiredUsages || [] },
- optionalUsages: { all: data.keyUsages?.optionalUsages || [] }
- },
- extendedKeyUsages: {
- requiredUsages: { all: data.extendedKeyUsages?.requiredUsages || [] },
- optionalUsages: { all: data.extendedKeyUsages?.optionalUsages || [] }
- },
- subjectAlternativeNames: data.subjectAlternativeNames || [],
- validity: {
- maxDuration: data.validity?.maxDuration || { value: 365, unit: "days" as const }
- },
- signatureAlgorithm: {
- allowedAlgorithms: data.signatureAlgorithm?.allowedAlgorithms || ["SHA256-RSA"],
- defaultAlgorithm: data.signatureAlgorithm?.defaultAlgorithm || "SHA256-RSA"
- },
- keyAlgorithm: {
- allowedKeyTypes: data.keyAlgorithm?.allowedKeyTypes || ["RSA-2048"],
- defaultKeyType: data.keyAlgorithm?.defaultKeyType || "RSA-2048"
- }
- };
-
- await createTemplate.mutateAsync(templateData);
+ if (isEdit) {
+ const updateData = {
+ templateId: template.id,
+ name: data.slug,
+ description: data.description,
+ attributes: data.attributes || [],
+ keyUsages: {
+ requiredUsages: { all: data.keyUsages?.requiredUsages || [] },
+ optionalUsages: { all: data.keyUsages?.optionalUsages || [] }
+ },
+ extendedKeyUsages: {
+ requiredUsages: { all: data.extendedKeyUsages?.requiredUsages || [] },
+ optionalUsages: { all: data.extendedKeyUsages?.optionalUsages || [] }
+ },
+ subjectAlternativeNames: data.subjectAlternativeNames || [],
+ validity: {
+ maxDuration: data.validity?.maxDuration || { value: 365, unit: "days" as const }
+ },
+ signatureAlgorithm: {
+ allowedAlgorithms: data.signatureAlgorithm?.allowedAlgorithms || ["SHA256-RSA"],
+ defaultAlgorithm: data.signatureAlgorithm?.defaultAlgorithm || "SHA256-RSA"
+ },
+ keyAlgorithm: {
+ allowedKeyTypes: data.keyAlgorithm?.allowedKeyTypes || ["RSA-2048"],
+ defaultKeyType: data.keyAlgorithm?.defaultKeyType || "RSA-2048"
+ }
+ };
+ await updateTemplate.mutateAsync(updateData);
+ } else {
+ const createData = {
+ projectId: currentProject!.id,
+ slug: data.slug,
+ description: data.description,
+ attributes: data.attributes || [],
+ keyUsages: {
+ requiredUsages: { all: data.keyUsages?.requiredUsages || [] },
+ optionalUsages: { all: data.keyUsages?.optionalUsages || [] }
+ },
+ extendedKeyUsages: {
+ requiredUsages: { all: data.extendedKeyUsages?.requiredUsages || [] },
+ optionalUsages: { all: data.extendedKeyUsages?.optionalUsages || [] }
+ },
+ subjectAlternativeNames: data.subjectAlternativeNames || [],
+ validity: {
+ maxDuration: data.validity?.maxDuration || { value: 365, unit: "days" as const }
+ },
+ signatureAlgorithm: {
+ allowedAlgorithms: data.signatureAlgorithm?.allowedAlgorithms || ["SHA256-RSA"],
+ defaultAlgorithm: data.signatureAlgorithm?.defaultAlgorithm || "SHA256-RSA"
+ },
+ keyAlgorithm: {
+ allowedKeyTypes: data.keyAlgorithm?.allowedKeyTypes || ["RSA-2048"],
+ defaultKeyType: data.keyAlgorithm?.defaultKeyType || "RSA-2048"
+ }
+ };
+ await createTemplate.mutateAsync(createData);
+ }
createNotification({
- text: "Certificate template created successfully",
+ text: `Certificate template ${isEdit ? "updated" : "created"} successfully`,
type: "success"
});
reset();
onClose();
} catch (error) {
- console.error("Error creating template:", error);
+ console.error(`Error ${isEdit ? "updating" : "creating"} template:`, error);
createNotification({
- text: "Failed to create certificate template",
+ text: `Failed to ${isEdit ? "update" : "create"} certificate template`,
type: "error"
});
}
@@ -144,8 +221,8 @@ export const CreateTemplateModal = ({ isOpen, onClose }: Props) => {
const addAttribute = () => {
const newAttribute = {
- type: "common_name" as const,
- include: "optional" as const,
+ type: SUBJECT_ATTRIBUTE_TYPES[0],
+ include: INCLUDE_OPTIONS[1],
value: []
};
setValue("attributes", [...watchedAttributes, newAttribute]);
@@ -158,8 +235,8 @@ export const CreateTemplateModal = ({ isOpen, onClose }: Props) => {
const addSan = () => {
const newSan = {
- type: "dns_name" as const,
- include: "optional" as const,
+ type: SAN_TYPES[0],
+ include: INCLUDE_OPTIONS[1],
value: []
};
setValue("subjectAlternativeNames", [...watchedSans, newSan]);
@@ -208,14 +285,6 @@ export const CreateTemplateModal = ({ isOpen, onClose }: Props) => {
} as any);
};
- const tabs = [
- { id: "basic", label: "Basic Info" },
- { id: "attributes", label: "Subject Attributes" },
- { id: "san", label: "Subject Alternative Names" },
- { id: "usages", label: "Key Usages" },
- { id: "constraints", label: "Constraints" }
- ];
-
return (
{
>
|