import { memo } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import z from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { ChevronsUpDown, Plus, CircleHelp } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; import AccessEditDialog from "@/components/certimate/AccessEditDialog"; import EmailsEdit from "@/components/certimate/EmailsEdit"; import StringList from "@/components/certimate/StringList"; import { accessProvidersMap } from "@/domain/access"; import { EmailsSetting } from "@/domain/settings"; import { useConfigContext } from "@/providers/config"; import { Switch } from "@/components/ui/switch"; import { TooltipFast } from "@/components/ui/tooltip"; import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; import { useShallow } from "zustand/shallow"; import { usePanel } from "./PanelProvider"; type ApplyFormProps = { data: WorkflowNode; }; const selectState = (state: WorkflowState) => ({ updateNode: state.updateNode, }); const ApplyForm = ({ data }: ApplyFormProps) => { const { updateNode } = useWorkflowStore(useShallow(selectState)); const { config: { accesses, emails }, } = useConfigContext(); const { t } = useTranslation(); const { hidePanel } = usePanel(); const formSchema = z.object({ domain: z.string().min(1, { message: "common.errmsg.domain_invalid", }), email: z.string().email("common.errmsg.email_invalid").optional(), access: z.string().regex(/^[a-zA-Z0-9]+$/, { message: "domain.application.form.access.placeholder", }), keyAlgorithm: z.string().optional(), nameservers: z.string().optional(), timeout: z.number().optional(), disableFollowCNAME: z.boolean().optional(), }); let config: WorkflowNodeConfig = { domain: "", email: "", access: "", keyAlgorithm: "RSA2048", nameservers: "", timeout: 60, disableFollowCNAME: true, }; if (data) config = data.config ?? config; const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { domain: config.domain as string, email: config.email as string, access: config.access as string, keyAlgorithm: config.keyAlgorithm as string, nameservers: config.nameservers as string, timeout: config.timeout as number, disableFollowCNAME: config.disableFollowCNAME as boolean, }, }); const onSubmit = async (config: z.infer<typeof formSchema>) => { updateNode({ ...data, config, validated: true }); hidePanel(); }; return ( <> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 dark:text-stone-200"> {/* 域名 */} <FormField control={form.control} name="domain" render={({ field }) => ( <FormItem> <> <StringList value={field.value} valueType="domain" onValueChange={(domain: string) => { form.setValue("domain", domain); }} /> </> <FormMessage /> </FormItem> )} /> {/* 邮箱 */} <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel className="flex justify-between w-full"> <div>{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}</div> <EmailsEdit trigger={ <div className="flex items-center font-normal cursor-pointer text-primary hover:underline"> <Plus size={14} /> {t("common.add")} </div> } /> </FormLabel> <FormControl> <Select {...field} value={field.value} onValueChange={(value) => { form.setValue("email", value); }} > <SelectTrigger> <SelectValue placeholder={t("domain.application.form.email.placeholder")} /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>{t("domain.application.form.email.list")}</SelectLabel> {(emails.content as EmailsSetting).emails.map((item) => ( <SelectItem key={item} value={item}> <div>{item}</div> </SelectItem> ))} </SelectGroup> </SelectContent> </Select> </FormControl> <FormMessage /> </FormItem> )} /> {/* DNS 服务商授权 */} <FormField control={form.control} name="access" render={({ field }) => ( <FormItem> <FormLabel className="flex justify-between w-full"> <div>{t("domain.application.form.access.label")}</div> <AccessEditDialog trigger={ <div className="flex items-center font-normal cursor-pointer text-primary hover:underline"> <Plus size={14} /> {t("common.add")} </div> } op="add" /> </FormLabel> <FormControl> <Select {...field} value={field.value} onValueChange={(value) => { form.setValue("access", value); }} > <SelectTrigger> <SelectValue placeholder={t("domain.application.form.access.placeholder")} /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>{t("domain.application.form.access.list")}</SelectLabel> {accesses .filter((item) => item.usage != "deploy") .map((item) => ( <SelectItem key={item.id} value={item.id}> <div className="flex items-center space-x-2"> <img className="w-6" src={accessProvidersMap.get(item.configType)?.icon} /> <div>{item.name}</div> </div> </SelectItem> ))} </SelectGroup> </SelectContent> </Select> </FormControl> <FormMessage /> </FormItem> )} /> <div> <hr /> <Collapsible> <CollapsibleTrigger className="w-full my-4"> <div className="flex items-center justify-between space-x-4"> <span className="flex-1 text-sm text-left text-gray-600">{t("domain.application.form.advanced_settings.label")}</span> <ChevronsUpDown className="w-4 h-4" /> </div> </CollapsibleTrigger> <CollapsibleContent> <div className="flex flex-col space-y-8"> {/* 证书算法 */} <FormField control={form.control} name="keyAlgorithm" render={({ field }) => ( <FormItem> <FormLabel>{t("domain.application.form.key_algorithm.label")}</FormLabel> <Select {...field} value={field.value} onValueChange={(value) => { form.setValue("keyAlgorithm", value); }} > <SelectTrigger> <SelectValue placeholder={t("domain.application.form.key_algorithm.placeholder")} /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectItem value="RSA2048">RSA2048</SelectItem> <SelectItem value="RSA3072">RSA3072</SelectItem> <SelectItem value="RSA4096">RSA4096</SelectItem> <SelectItem value="RSA8192">RSA8192</SelectItem> <SelectItem value="EC256">EC256</SelectItem> <SelectItem value="EC384">EC384</SelectItem> </SelectGroup> </SelectContent> </Select> </FormItem> )} /> {/* DNS */} <FormField control={form.control} name="nameservers" render={({ field }) => ( <FormItem> <StringList value={field.value ?? ""} onValueChange={(val: string) => { form.setValue("nameservers", val); }} valueType="dns" ></StringList> <FormMessage /> </FormItem> )} /> {/* DNS 超时时间 */} <FormField control={form.control} name="timeout" render={({ field }) => ( <FormItem> <FormLabel>{t("domain.application.form.timeout.label")}</FormLabel> <FormControl> <Input type="number" placeholder={t("domain.application.form.timeout.placeholder")} {...field} value={field.value} onChange={(e) => { form.setValue("timeout", parseInt(e.target.value)); }} /> </FormControl> <FormMessage /> </FormItem> )} /> {/* 禁用 CNAME 跟随 */} <FormField control={form.control} name="disableFollowCNAME" render={({ field }) => ( <FormItem> <FormLabel> <div className="flex"> <span className="mr-1">{t("domain.application.form.disable_follow_cname.label")} </span> <TooltipFast className="max-w-[20rem]" contentView={ <p> {t("domain.application.form.disable_follow_cname.tips")} <a className="text-primary" target="_blank" href="https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname" > {t("domain.application.form.disable_follow_cname.tips_link")} </a> </p> } > <CircleHelp size={14} /> </TooltipFast> </div> </FormLabel> <FormControl> <div> <Switch defaultChecked={field.value} onCheckedChange={(value) => { form.setValue(field.name, value); }} /> </div> </FormControl> <FormMessage /> </FormItem> )} /> </div> </CollapsibleContent> </Collapsible> </div> <div className="flex justify-end"> <Button type="submit">{t("common.save")}</Button> </div> </form> </Form> </> ); }; export default memo(ApplyForm);