import { memo, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons"; import { useControllableValue } from "ahooks"; import { AutoComplete, type AutoCompleteProps, Button, Divider, Form, type FormInstance, Input, Select, Space, Switch, Tooltip, Typography } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; import ModalForm from "@/components/ModalForm"; import MultipleInput from "@/components/MultipleInput"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; import { ACCESS_USAGES, accessProvidersMap } from "@/domain/provider"; import { type WorkflowNode, type WorkflowNodeConfigForApply } from "@/domain/workflow"; import { useContactEmailsStore } from "@/stores/contact"; import { validDomainName, validIPv4Address, validIPv6Address } from "@/utils/validators"; type ApplyNodeFormFieldValues = Partial; export type ApplyNodeFormProps = { form: FormInstance; formName?: string; disabled?: boolean; workflowNode: WorkflowNode; onValuesChange?: (values: ApplyNodeFormFieldValues) => void; }; const MULTIPLE_INPUT_DELIMITER = ";"; const initFormModel = (): ApplyNodeFormFieldValues => { return { keyAlgorithm: "RSA2048", propagationTimeout: 60, disableFollowCNAME: true, }; }; const ApplyNodeForm = ({ form, formName, disabled, workflowNode, onValuesChange }: ApplyNodeFormProps) => { const { t } = useTranslation(); const formSchema = z.object({ domains: z.string({ message: t("workflow_node.apply.form.domains.placeholder") }).refine((v) => { return String(v) .split(MULTIPLE_INPUT_DELIMITER) .every((e) => validDomainName(e, true)); }, t("common.errmsg.domain_invalid")), contactEmail: z.string({ message: t("workflow_node.apply.form.contact_email.placeholder") }).email(t("common.errmsg.email_invalid")), providerAccessId: z .string({ message: t("workflow_node.apply.form.provider_access.placeholder") }) .min(1, t("workflow_node.apply.form.provider_access.placeholder")), keyAlgorithm: z .string({ message: t("workflow_node.apply.form.key_algorithm.placeholder") }) .nonempty(t("workflow_node.apply.form.key_algorithm.placeholder")), nameservers: z .string() .nullish() .refine((v) => { if (!v) return true; return String(v) .split(MULTIPLE_INPUT_DELIMITER) .every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e)); }, t("common.errmsg.host_invalid")), propagationTimeout: z .union([ z.number().int().gte(1, t("workflow_node.apply.form.propagation_timeout.placeholder")), z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.propagation_timeout.placeholder")), ]) .nullish(), disableFollowCNAME: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); const initialValues: ApplyNodeFormFieldValues = (workflowNode.config as WorkflowNodeConfigForApply) ?? initFormModel(); const fieldDomains = Form.useWatch("domains", form); const fieldNameservers = Form.useWatch("nameservers", form); const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values as ApplyNodeFormFieldValues); }; return (
} > { form.setFieldValue("domains", e.target.value); }} /> } onFinish={(v) => { form.setFieldValue("domains", v); }} /> } > { const provider = accessProvidersMap.get(record.provider); return ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.APPLY === provider?.usage; }} /> {t("workflow_node.apply.form.advanced_config.label")} { form.setFieldValue("nameservers", e.target.value); }} /> } onFinish={(v) => { form.setFieldValue("nameservers", v); }} /> } > } >
); }; const FormFieldEmailSelect = ({ className, style, disabled, placeholder, ...props }: { className?: string; style?: React.CSSProperties; defaultValue?: string; disabled?: boolean; placeholder?: string; value?: string; onChange?: (value: string) => void; }) => { const { emails, fetchEmails } = useContactEmailsStore(); const emailsToOptions = useCallback(() => emails.map((email) => ({ label: email, value: email })), [emails]); useEffect(() => { fetchEmails(); }, [fetchEmails]); const [value, setValue] = useControllableValue(props, { valuePropName: "value", defaultValuePropName: "defaultValue", trigger: "onChange", }); const [options, setOptions] = useState([]); useEffect(() => { setOptions(emailsToOptions()); }, [emails, emailsToOptions]); const handleChange = (value: string) => { setValue(value); }; const handleSearch = (text: string) => { const temp = emailsToOptions(); if (text?.trim()) { if (temp.every((option) => option.label !== text)) { temp.unshift({ label: text, value: text }); } } setOptions(temp); }; return ( ); }; const FormFieldDomainsModalForm = ({ data, trigger, onFinish, }: { data?: string; disabled?: boolean; trigger?: React.ReactNode; onFinish?: (data: string) => void; }) => { const { t } = useTranslation(); const formSchema = z.object({ domains: z.array(z.string()).refine((v) => { return v.every((e) => !e?.trim() || validDomainName(e.trim(), true)); }, t("common.errmsg.domain_invalid")), }); const formRule = createSchemaFieldRule(formSchema); const [form] = Form.useForm>(); const [model, setModel] = useState>>({ domains: data?.split(MULTIPLE_INPUT_DELIMITER) }); useEffect(() => { setModel({ domains: data?.split(MULTIPLE_INPUT_DELIMITER) }); }, [data]); const handleFormFinish = (values: z.infer) => { onFinish?.( values.domains .map((e) => e.trim()) .filter((e) => !!e) .join(MULTIPLE_INPUT_DELIMITER) ); }; return ( ); }; const FormFieldNameserversModalForm = ({ data, trigger, onFinish }: { data?: string; trigger?: React.ReactNode; onFinish?: (data: string) => void }) => { const { t } = useTranslation(); const formSchema = z.object({ nameservers: z.array(z.string()).refine((v) => { return v.every((e) => !e?.trim() || validIPv4Address(e) || validIPv6Address(e) || validDomainName(e)); }, t("common.errmsg.domain_invalid")), }); const formRule = createSchemaFieldRule(formSchema); const [form] = Form.useForm>(); const [model, setModel] = useState>>({ nameservers: data?.split(MULTIPLE_INPUT_DELIMITER) }); useEffect(() => { setModel({ nameservers: data?.split(MULTIPLE_INPUT_DELIMITER) }); }, [data]); const handleFormFinish = (values: z.infer) => { onFinish?.( values.nameservers .map((e) => e.trim()) .filter((e) => !!e) .join(MULTIPLE_INPUT_DELIMITER) ); }; return ( ); }; export default memo(ApplyNodeForm);