diff --git a/ui/src/components/MultipleSplitValueInput.tsx b/ui/src/components/MultipleSplitValueInput.tsx new file mode 100644 index 00000000..5f94ec7d --- /dev/null +++ b/ui/src/components/MultipleSplitValueInput.tsx @@ -0,0 +1,108 @@ +import { type ChangeEvent, useEffect } from "react"; +import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; +import { nanoid } from "@ant-design/pro-components"; +import { useControllableValue } from "ahooks"; +import { Button, Form, Input, type InputProps, Space } from "antd"; + +import { useAntdForm } from "@/hooks"; +import ModalForm from "./ModalForm"; +import MultipleInput from "./MultipleInput"; + +type SplitOptions = { + removeEmpty?: boolean; + trim?: boolean; +}; + +export type MultipleSplitValueInputProps = Omit & { + defaultValue?: string; + delimiter?: string; + maxCount?: number; + minCount?: number; + modalTitle?: string; + modalWidth?: number; + placeholderInModal?: string; + showSortButton?: boolean; + splitOptions?: SplitOptions; + value?: string[]; + onChange?: (value: string) => void; +}; + +const DEFAULT_DELIMITER = ";"; + +const MultipleSplitValueInput = ({ + className, + style, + delimiter = DEFAULT_DELIMITER, + disabled, + maxCount, + minCount, + modalTitle, + modalWidth = 480, + placeholder, + placeholderInModal, + showSortButton = true, + splitOptions = {}, + onClear, + ...props +}: MultipleSplitValueInputProps) => { + const [value, setValue] = useControllableValue(props, { + valuePropName: "value", + defaultValuePropName: "defaultValue", + trigger: "onChange", + }); + + const { form: formInst, formProps } = useAntdForm({ + name: "componentMultipleSplitValueInput_" + nanoid(), + initialValues: { value: value?.split(delimiter) }, + onSubmit: (values) => { + const temp = values.value ?? []; + if (splitOptions.trim) { + temp.map((e) => e.trim()); + } + if (splitOptions.removeEmpty) { + temp.filter((e) => !!e); + } + + setValue(temp.join(delimiter)); + }, + }); + + useEffect(() => { + formInst.setFieldValue("value", value?.split(delimiter)); + }, [delimiter, value]); + + const handleChange = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + const handleClear = () => { + setValue(""); + onClear?.(); + }; + + return ( + + + + + + } + validateTrigger="onSubmit" + width={modalWidth} + > + + + + + + ); +}; + +export default MultipleSplitValueInput; diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index 22cb57d5..7faa148e 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -1,12 +1,7 @@ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router"; -import { - FormOutlined as FormOutlinedIcon, - PlusOutlined as PlusOutlinedIcon, - QuestionCircleOutlined as QuestionCircleOutlinedIcon, - RightOutlined as RightOutlinedIcon, -} from "@ant-design/icons"; +import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon, RightOutlined as RightOutlinedIcon } from "@ant-design/icons"; import { useControllableValue } from "ahooks"; import { AutoComplete, @@ -19,7 +14,6 @@ import { Input, InputNumber, Select, - Space, Switch, Tooltip, Typography, @@ -29,8 +23,7 @@ import { z } from "zod"; import AccessEditModal from "@/components/access/AccessEditModal"; import AccessSelect from "@/components/access/AccessSelect"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; import ACMEDns01ProviderSelect from "@/components/provider/ACMEDns01ProviderSelect"; import CAProviderSelect from "@/components/provider/CAProviderSelect"; import Show from "@/components/Show"; @@ -152,11 +145,9 @@ const ApplyNodeConfigForm = forwardRef("domains", formInst); const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); const fieldProviderAccessId = Form.useWatch("providerAccessId", formInst); const fieldCAProvider = Form.useWatch("caProvider", formInst); - const fieldNameservers = Form.useWatch("nameservers", formInst); const [showProvider, setShowProvider] = useState(false); useEffect(() => { @@ -294,25 +285,17 @@ const ApplyNodeConfigForm = forwardRef
} > - - - - - - - - } - onChange={(v) => { - formInst.setFieldValue("domains", v); - }} - /> - + } > - - - { - formInst.setFieldValue("nameservers", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("nameservers", undefined); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("nameservers", value); - }} - /> - + void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - domains: z.array(z.string()).refine((v) => { - return v.every((e) => !e?.trim() || validDomainName(e.trim(), { allowWildcard: true })); - }, t("common.errmsg.domain_invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeApplyConfigFormDomainsModalInput", - initialValues: { domains: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.domains - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - -const NameserversModalInput = memo(({ trigger, value, onChange }: { trigger?: React.ReactNode; value?: string; onChange?: (value: 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: formInst, formProps } = useAntdForm({ - name: "workflowNodeApplyConfigFormNameserversModalInput", - initialValues: { nameservers: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.nameservers - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - export default memo(ApplyNodeConfigForm); diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASDeployConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASDeployConfig.tsx index 4e3db0db..980e89fe 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASDeployConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASDeployConfig.tsx @@ -1,13 +1,9 @@ -import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; -import { Alert, Button, Form, type FormInstance, Input, Space } from "antd"; +import { Alert, Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; -import { useAntdForm } from "@/hooks"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; type DeployNodeConfigFormAliyunCASDeployConfigFieldValues = Nullish<{ region: string; @@ -61,9 +57,6 @@ const DeployNodeConfigFormAliyunCASDeployConfig = ({ }); const formRule = createSchemaFieldRule(formSchema); - const fieldResourceIds = Form.useWatch("resourceIds", formInst); - const fieldContactIds = Form.useWatch("contactIds", formInst); - const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -87,69 +80,31 @@ const DeployNodeConfigFormAliyunCASDeployConfig = ({ } > - - - { - formInst.setFieldValue("resourceIds", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("resourceIds", ""); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("resourceIds", value); - }} - /> - + } > - - - { - formInst.setFieldValue("contactIds", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("contactIds", ""); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("contactIds", value); - }} - /> - + @@ -159,84 +114,4 @@ const DeployNodeConfigFormAliyunCASDeployConfig = ({ ); }; -const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - resourceIds: z.array(z.string()).refine((v) => { - return v.every((e) => !e?.trim() || /^[1-9]\d*$/.test(e)); - }, t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeDeployConfigFormAliyunCASResourceIdsModalInput", - initialValues: { resourceIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.resourceIds - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - -const ContactIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - contactIds: z.array(z.string()).refine((v) => { - return v.every((e) => !e?.trim() || /^[1-9]\d*$/.test(e)); - }, t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeDeployConfigFormAliyunCASDeploymentJobContactIdsModalInput", - initialValues: { contactIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.contactIds - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - export default DeployNodeConfigFormAliyunCASDeployConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx index acc9d187..54507cdb 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx @@ -1,14 +1,10 @@ -import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; -import { Button, Form, type FormInstance, Input, Select, Space } from "antd"; +import { Form, type FormInstance, Input, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; import Show from "@/components/Show"; -import { useAntdForm } from "@/hooks"; type DeployNodeConfigFormBaotaPanelSiteConfigFieldValues = Nullish<{ siteType: string; @@ -71,7 +67,6 @@ const DeployNodeConfigFormBaotaPanelSiteConfig = ({ const formRule = createSchemaFieldRule(formSchema); const fieldSiteType = Form.useWatch("siteType", formInst); - const fieldSiteNames = Form.useWatch("siteNames", formInst); const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); @@ -110,80 +105,21 @@ const DeployNodeConfigFormBaotaPanelSiteConfig = ({ } > - - - { - formInst.setFieldValue("siteNames", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("siteNames", ""); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("siteNames", value); - }} - /> - + ); }; -const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - siteNames: z.array(z.string()).refine((v) => { - return v.every((e) => !!e?.trim()); - }, t("workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeDeployConfigFormBaotaPanelSiteNamesModalInput", - initialValues: { siteNames: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.siteNames - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - export default DeployNodeConfigFormBaotaPanelSiteConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx index 2a4e7d12..4fcb4e76 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx @@ -1,13 +1,9 @@ -import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; -import { Alert, AutoComplete, Button, Form, type FormInstance, Input, Space } from "antd"; +import { Alert, AutoComplete, Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; -import { useAntdForm } from "@/hooks"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; type DeployNodeConfigFormTencentCloudSSLDeployConfigFieldValues = Nullish<{ region: string; @@ -56,8 +52,6 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({ }); const formRule = createSchemaFieldRule(formSchema); - const fieldResourceIds = Form.useWatch("resourceIds", formInst); - const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -94,36 +88,17 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({ } > - - - { - formInst.setFieldValue("resourceIds", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("resourceIds", ""); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("resourceIds", value); - }} - /> - + @@ -133,44 +108,4 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({ ); }; -const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - resourceIds: z.array(z.string()).refine((v) => { - return v.every((e) => !e?.trim() || /^[A-Za-z0-9*._-|]+$/.test(e)); - }, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeDeployConfigFormTencentCloudSSLDeployResourceIdsModalInput", - initialValues: { resourceIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.resourceIds - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - export default DeployNodeConfigFormTencentCloudSSLDeployConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx index 57d0d381..d64e6eba 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormWangsuCDNConfig.tsx @@ -1,13 +1,9 @@ -import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { FormOutlined as FormOutlinedIcon } from "@ant-design/icons"; -import { Button, Form, type FormInstance, Input, Space } from "antd"; +import { Form, type FormInstance } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; -import ModalForm from "@/components/ModalForm"; -import MultipleInput from "@/components/MultipleInput"; -import { useAntdForm } from "@/hooks"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; import { validDomainName } from "@/utils/validators"; type DeployNodeConfigFormWangsuCDNConfigFieldValues = Nullish<{ @@ -52,8 +48,6 @@ const DeployNodeConfigFormWangsuCDNConfig = ({ }); const formRule = createSchemaFieldRule(formSchema); - const fieldDomains = Form.useWatch("domains", formInst); - const handleFormChange = (_: unknown, values: z.infer) => { onValuesChange?.(values); }; @@ -68,79 +62,20 @@ const DeployNodeConfigFormWangsuCDNConfig = ({ onValuesChange={handleFormChange} > } > - - - { - formInst.setFieldValue("domains", e.target.value); - }} - onClear={() => { - formInst.setFieldValue("domains", ""); - }} - /> - - - - - } - onChange={(value) => { - formInst.setFieldValue("domains", value); - }} - /> - + ); }; -const SiteNamesModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { - const { t } = useTranslation(); - - const formSchema = z.object({ - domains: z.array(z.string()).refine((v) => { - return v.every((e) => validDomainName(e)); - }, t("workflow_node.deploy.form.wangsu_cdn_domains.errmsg.invalid")), - }); - const formRule = createSchemaFieldRule(formSchema); - const { form: formInst, formProps } = useAntdForm({ - name: "workflowNodeDeployConfigFormWangsuCDNNamesModalInput", - initialValues: { domains: value?.split(MULTIPLE_INPUT_DELIMITER) }, - onSubmit: (values) => { - onChange?.( - values.domains - .map((e) => e.trim()) - .filter((e) => !!e) - .join(MULTIPLE_INPUT_DELIMITER) - ); - }, - }); - - return ( - - - - - - ); -}); - export default DeployNodeConfigFormWangsuCDNConfig; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 80237287..ca60aedb 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -1,9 +1,11 @@ { "workflow_node.action.configure_node": "Configure node", "workflow_node.action.add_node": "Add node", + "workflow_node.action.duplicate_node": "Duplicate node", "workflow_node.action.rename_node": "Rename node", "workflow_node.action.remove_node": "Delete node", "workflow_node.action.add_branch": "Add branch", + "workflow_node.action.duplicate_branch": "Duplicate branch", "workflow_node.action.rename_branch": "Rename branch", "workflow_node.action.remove_branch": "Delete branch", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index faf40816..3da2c838 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -1,9 +1,11 @@ { "workflow_node.action.configure_node": "配置节点", "workflow_node.branch.add_node": "添加节点", + "workflow_node.action.duplicate_node": "复制节点", "workflow_node.action.rename_node": "重命名", "workflow_node.action.remove_node": "删除节点", "workflow_node.action.add_branch": "添加并行分支", + "workflow_node.action.duplicate_branch": "复制分支", "workflow_node.action.rename_branch": "重命名", "workflow_node.action.remove_branch": "删除分支",