import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, FormControl, Input, Modal, ModalContent, Select, SelectItem, TextArea, Tooltip } from "@app/components/v2"; import { envConfig } from "@app/config/env"; import { useProject } from "@app/context"; import { useListCasByProjectId } from "@app/hooks/api/ca/queries"; import { TCertificateProfileWithDetails, TCreateCertificateProfileDTO, TUpdateCertificateProfileDTO, useCreateCertificateProfile, useUpdateCertificateProfile } from "@app/hooks/api/certificateProfiles"; import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries"; const createSchema = z .object({ slug: z .string() .trim() .min(1, "Profile slug is required") .max(255, "Profile slug must be less than 255 characters") .regex( /^[a-zA-Z0-9-_]+$/, "Profile slug must contain only letters, numbers, hyphens, and underscores" ), description: z .string() .trim() .max(1000, "Description must be less than 1000 characters") .optional(), enrollmentType: z.enum(["api", "est", "acme"]), certificateAuthorityId: z.string().min(1, "Certificate Authority is required"), certificateTemplateId: z.string().min(1, "Certificate Template is required"), estConfig: z .object({ disableBootstrapCaValidation: z.boolean().optional(), passphrase: z.string().min(1, "EST passphrase is required"), caChain: z.string().min(1, "EST CA chain is required").optional() }) .refine( (data) => { if (!data.disableBootstrapCaValidation && !data.caChain) { return false; } return true; }, { message: "EST CA chain is required when bootstrap CA validation is enabled", path: ["caChain"] } ) .optional(), apiConfig: z .object({ autoRenew: z.boolean().optional(), renewBeforeDays: z.number().min(1).max(365).optional() }) .optional(), acmeConfig: z.object({}).optional() }) .refine( (data) => { if (data.enrollmentType === "est" && !data.estConfig) { return false; } if (data.enrollmentType === "api" && !data.apiConfig) { return false; } if (data.enrollmentType === "acme" && !data.acmeConfig) { return false; } return true; }, { message: "Configuration is required for selected enrollment type" } ); const editSchema = z .object({ slug: z .string() .trim() .min(1, "Profile slug is required") .max(255, "Profile slug must be less than 255 characters") .regex( /^[a-zA-Z0-9-_]+$/, "Profile slug must contain only letters, numbers, hyphens, and underscores" ), description: z .string() .trim() .max(1000, "Description must be less than 1000 characters") .optional(), enrollmentType: z.enum(["api", "est", "acme"]), certificateAuthorityId: z.string().optional(), certificateTemplateId: z.string().optional(), estConfig: z .object({ disableBootstrapCaValidation: z.boolean().optional(), passphrase: z.string().optional(), caChain: z.string().optional() }) .optional(), apiConfig: z .object({ autoRenew: z.boolean().optional(), renewBeforeDays: z.number().min(1).max(365).optional() }) .optional(), acmeConfig: z.object({}).optional() }) .refine( (data) => { if (data.enrollmentType === "est" && !data.estConfig) { return false; } if (data.enrollmentType === "api" && !data.apiConfig) { return false; } if (data.enrollmentType === "acme" && !data.acmeConfig) { return false; } return true; }, { message: "Configuration is required for selected enrollment type" } ); export type FormData = z.infer; interface Props { isOpen: boolean; onClose: () => void; profile?: TCertificateProfileWithDetails; mode?: "create" | "edit"; } export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }: Props) => { const { currentProject } = useProject(); const { data: caData } = useListCasByProjectId(currentProject?.id || ""); const { data: templateData } = useListCertificateTemplatesV2({ projectId: currentProject?.id || "", limit: 100, offset: 0 }); const createProfile = useCreateCertificateProfile(); const updateProfile = useUpdateCertificateProfile(); const isEdit = mode === "edit" && profile; const certificateAuthorities = caData || []; const certificateTemplates = templateData?.certificateTemplates || []; const { control, handleSubmit, reset, watch, setValue, formState } = useForm({ resolver: zodResolver(isEdit ? editSchema : createSchema), defaultValues: isEdit ? { slug: profile.slug, description: profile.description || "", enrollmentType: profile.enrollmentType, certificateAuthorityId: profile.caId, certificateTemplateId: profile.certificateTemplateId, estConfig: profile.enrollmentType === "est" ? { disableBootstrapCaValidation: profile.estConfig?.disableBootstrapCaValidation || false, passphrase: profile.estConfig?.passphrase || "", caChain: profile.estConfig?.caChain || "" } : undefined, apiConfig: profile.enrollmentType === "api" ? { autoRenew: profile.apiConfig?.autoRenew || false, renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30 } : undefined, acmeConfig: profile.enrollmentType === "acme" ? {} : undefined } : { slug: "", description: "", enrollmentType: "api", certificateAuthorityId: "", certificateTemplateId: "", apiConfig: { autoRenew: false, renewBeforeDays: 30 }, acmeConfig: {} } }); const watchedEnrollmentType = watch("enrollmentType"); const watchedDisableBootstrapValidation = watch("estConfig.disableBootstrapCaValidation"); const watchedAutoRenew = watch("apiConfig.autoRenew"); useEffect(() => { if (isEdit && profile) { reset({ slug: profile.slug, description: profile.description || "", enrollmentType: profile.enrollmentType, certificateAuthorityId: profile.caId, certificateTemplateId: profile.certificateTemplateId, estConfig: profile.enrollmentType === "est" ? { disableBootstrapCaValidation: profile.estConfig?.disableBootstrapCaValidation || false, passphrase: profile.estConfig?.passphrase || "", caChain: profile.estConfig?.caChain || "" } : undefined, apiConfig: profile.enrollmentType === "api" ? { autoRenew: profile.apiConfig?.autoRenew || false, renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30 } : undefined, acmeConfig: profile.enrollmentType === "acme" ? {} : undefined }); } }, [isEdit, profile, reset]); const onFormSubmit = async (data: FormData) => { if (!currentProject?.id && !isEdit) return; if (isEdit) { const updateData: TUpdateCertificateProfileDTO = { profileId: profile.id, slug: data.slug, description: data.description }; if (data.enrollmentType === "est" && data.estConfig) { updateData.estConfig = data.estConfig; } else if (data.enrollmentType === "api" && data.apiConfig) { updateData.apiConfig = data.apiConfig; } else if (data.enrollmentType === "acme" && data.acmeConfig) { updateData.acmeConfig = data.acmeConfig; } await updateProfile.mutateAsync(updateData); } else { if (!currentProject?.id) { throw new Error("Project ID is required for creating a profile"); } const createData: TCreateCertificateProfileDTO = { projectId: currentProject.id, slug: data.slug, description: data.description, enrollmentType: data.enrollmentType, caId: data.certificateAuthorityId, certificateTemplateId: data.certificateTemplateId }; if (data.enrollmentType === "est" && data.estConfig) { createData.estConfig = { passphrase: data.estConfig.passphrase, caChain: data.estConfig.caChain || undefined, disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation }; } else if (data.enrollmentType === "api" && data.apiConfig) { createData.apiConfig = data.apiConfig; } await createProfile.mutateAsync(createData); } createNotification({ text: `Certificate profile ${isEdit ? "updated" : "created"} successfully`, type: "success" }); reset(); onClose(); }; return ( { if (!open) { reset(); } onClose(); }} >
( )} /> (