diff --git a/ui/package-lock.json b/ui/package-lock.json index f705d7b5..fe5fe63c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13,7 +13,6 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "ahooks": "^3.8.4", @@ -3325,36 +3324,6 @@ } } }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", - "integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", - "dependencies": { - "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-select": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/@radix-ui/react-select/-/react-select-2.1.1.tgz", diff --git a/ui/package.json b/ui/package.json index 61ec5f26..e84e81ba 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,7 +15,6 @@ "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "ahooks": "^3.8.4", diff --git a/ui/src/components/access/AccessEditFormNameDotComConfig.tsx b/ui/src/components/access/AccessEditFormNameDotComConfig.tsx index 5b810ce8..163bb135 100644 --- a/ui/src/components/access/AccessEditFormNameDotComConfig.tsx +++ b/ui/src/components/access/AccessEditFormNameDotComConfig.tsx @@ -1,31 +1,29 @@ -import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { useDeepCompareEffect } from "ahooks"; import { Form, Input, type FormInstance } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import { useAntdForm } from "@/hooks"; import { type NameDotComAccessConfig } from "@/domain/access"; -type AccessEditFormNameDotComConfigModelType = Partial; +type AccessEditFormNameDotComConfigModelValues = Partial; export type AccessEditFormNameDotComConfigProps = { form: FormInstance; formName: string; disabled?: boolean; - loading?: boolean; - model?: AccessEditFormNameDotComConfigModelType; - onModelChange?: (model: AccessEditFormNameDotComConfigModelType) => void; + model?: AccessEditFormNameDotComConfigModelValues; + onModelChange?: (model: AccessEditFormNameDotComConfigModelValues) => void; }; -const initModel = () => { +const initFormModel = (): AccessEditFormNameDotComConfigModelValues => { return { username: "", apiToken: "", - } as AccessEditFormNameDotComConfigModelType; + }; }; -const AccessEditFormNameDotComConfig = ({ form, formName, disabled, loading, model, onModelChange }: AccessEditFormNameDotComConfigProps) => { +const AccessEditFormNameDotComConfig = ({ form, formName, disabled, model, onModelChange }: AccessEditFormNameDotComConfigProps) => { const { t } = useTranslation(); const formSchema = z.object({ @@ -41,18 +39,17 @@ const AccessEditFormNameDotComConfig = ({ form, formName, disabled, loading, mod .max(64, t("common.errmsg.string_max", { max: 64 })), }); const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm>({ + form: form, + initialValues: model ?? initFormModel(), + }); - const [initialValues, setInitialValues] = useState>>(model ?? initModel()); - useDeepCompareEffect(() => { - setInitialValues(model ?? initModel()); - }, [model]); - - const handleFormChange = (_: unknown, fields: AccessEditFormNameDotComConfigModelType) => { - onModelChange?.(fields); + const handleFormChange = (_: unknown, values: z.infer) => { + onModelChange?.(values as AccessEditFormNameDotComConfigModelValues); }; return ( -
+ , React.ComponentPropsWithoutRef>( - ({ className, children, ...props }, ref) => ( - - {children} - - - - ) -); -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; - -const ScrollBar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, orientation = "vertical", ...props }, ref) => ( - - - -)); -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; - -export { ScrollArea, ScrollBar }; diff --git a/ui/src/components/ui/sheet.tsx b/ui/src/components/ui/sheet.tsx deleted file mode 100644 index 145732c8..00000000 --- a/ui/src/components/ui/sheet.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; -import * as SheetPrimitive from "@radix-ui/react-dialog"; -import { X } from "lucide-react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "./utils"; - -const Sheet = SheetPrimitive.Root; - -const SheetTrigger = SheetPrimitive.Trigger; - -const SheetClose = SheetPrimitive.Close; - -const SheetPortal = SheetPrimitive.Portal; - -const SheetOverlay = React.forwardRef, React.ComponentPropsWithoutRef>( - ({ className, ...props }, ref) => ( - - ) -); -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; - -const sheetVariants = cva( - "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", - { - variants: { - side: { - top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", - bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", - left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", - right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", - }, - }, - defaultVariants: { - side: "right", - }, - } -); - -interface SheetContentProps extends React.ComponentPropsWithoutRef, VariantProps {} - -const SheetContent = React.forwardRef, SheetContentProps>( - ({ side = "right", className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - - ) -); -SheetContent.displayName = SheetPrimitive.Content.displayName; - -const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -SheetHeader.displayName = "SheetHeader"; - -const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -SheetFooter.displayName = "SheetFooter"; - -const SheetTitle = React.forwardRef, React.ComponentPropsWithoutRef>( - ({ className, ...props }, ref) => -); -SheetTitle.displayName = SheetPrimitive.Title.displayName; - -const SheetDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ); -SheetDescription.displayName = SheetPrimitive.Description.displayName; - -export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }; diff --git a/ui/src/components/workflow/Panel.tsx b/ui/src/components/workflow/Panel.tsx index c603e8c0..8f54bf64 100644 --- a/ui/src/components/workflow/Panel.tsx +++ b/ui/src/components/workflow/Panel.tsx @@ -1,6 +1,5 @@ -// components/AddNodePanel.tsx -import { ScrollArea } from "../ui/scroll-area"; -import { Sheet, SheetContent, SheetTitle } from "../ui/sheet"; +import { useEffect } from "react"; +import { Drawer } from "antd"; type AddNodePanelProps = { open: boolean; @@ -10,14 +9,14 @@ type AddNodePanelProps = { }; const Panel = ({ open, onOpenChange, children, name }: AddNodePanelProps) => { - return ( - - - {name} + useEffect(() => { + onOpenChange(open); + }, [open, onOpenChange]); - {children} - - + return ( + onOpenChange(false)}> + {children} + ); }; diff --git a/ui/src/components/workflow/node/ApplyForm.tsx b/ui/src/components/workflow/node/ApplyForm.tsx new file mode 100644 index 00000000..d5162cd1 --- /dev/null +++ b/ui/src/components/workflow/node/ApplyForm.tsx @@ -0,0 +1,356 @@ +import { memo, useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { Collapse, Divider, Switch, Tooltip, Typography } from "antd"; +import z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ChevronsUpDown as ChevronsUpDownIcon, Plus as PlusIcon, CircleHelp as CircleHelpIcon } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +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 AccessEditModal from "@/components/access/AccessEditModal"; +import EmailsEdit from "@/components/certimate/EmailsEdit"; +import StringList from "@/components/certimate/StringList"; +import { accessProvidersMap } from "@/domain/access"; +import { useZustandShallowSelector } from "@/hooks"; +import { useAccessStore } from "@/stores/access"; +import { useContactStore } from "@/stores/contact"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore } from "@/stores/workflow"; +import { usePanel } from "../PanelProvider"; + +type ApplyFormProps = { + data: WorkflowNode; +}; + +const ApplyForm = ({ data }: ApplyFormProps) => { + const { updateNode } = useWorkflowStore(useZustandShallowSelector(["updateNode"])); + + const { accesses } = useAccessStore(); + const { emails, fetchEmails } = useContactStore(); + + useEffect(() => { + fetchEmails(); + }, []); + + 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>({ + 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) => { + updateNode({ ...data, config, validated: true }); + hidePanel(); + }; + + return ( + <> + + + {/* 域名 */} + ( + + <> + { + form.setValue("domain", domain); + }} + /> + + + + )} + /> + + {/* 邮箱 */} + ( + + +
{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}
+ + + {t("common.button.add")} +
+ } + /> + + + + + + + + )} + /> + + {/* DNS 服务商授权 */} + ( + + +
{t("domain.application.form.access.label")}
+ + + {t("common.button.add")} +
+ } + /> + + + + + + + + )} + /> + + + + {t("domain.application.form.advanced_settings.label")}, + children: ( +
+ {/* 证书算法 */} + ( + + {t("domain.application.form.key_algorithm.label")} + + + )} + /> + + {/* DNS */} + ( + + { + form.setValue("nameservers", val); + }} + valueType="dns" + > + + + + )} + /> + + {/* DNS 超时时间 */} + ( + + {t("domain.application.form.timeout.label")} + + { + form.setValue("timeout", parseInt(e.target.value)); + }} + /> + + + + + )} + /> + + {/* 禁用 CNAME 跟随 */} + ( + + +
+ {t("domain.application.form.disable_follow_cname.label")} + + {t("domain.application.form.disable_follow_cname.tips")} + + {t("domain.application.form.disable_follow_cname.tips_link")} + +

+ } + > + +
+
+
+ +
+ { + form.setValue(field.name, value); + }} + /> +
+
+ +
+ )} + /> +
+ ), + extra: , + forceRender: true, + showArrow: false, + }, + ]} + /> + +
+ +
+ + + + ); +}; + +export default memo(ApplyForm); diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index 8ef90a5b..830609a9 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -16,7 +16,6 @@ import { useAntdForm, useZustandShallowSelector } from "@/hooks"; import { allNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { remove as removeWorkflow } from "@/repository/workflow"; -import { run as runWorkflow } from "@/api/workflow"; import { getErrMsg } from "@/utils/error"; const WorkflowDetail = () => { @@ -29,9 +28,7 @@ const WorkflowDetail = () => { const [notificationApi, NotificationContextHolder] = notification.useNotification(); const { id: workflowId } = useParams(); - const { workflow, init, setBaseInfo, switchEnable, save } = useWorkflowStore( - useZustandShallowSelector(["workflow", "init", "setBaseInfo", "switchEnable", "save"]) - ); + const { workflow, init, setBaseInfo, switchEnable } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "setBaseInfo", "switchEnable"])); useEffect(() => { init(workflowId); }, [workflowId, init]);