mirror of
https://github.com/usual2970/certimate.git
synced 2025-08-15 22:01:45 +00:00
feat(ui): new DeployNodeForm using antd
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select";
|
||||
import { accessProvidersMap } from "@/domain/access";
|
||||
import { accessProvidersMap, deployProvidersMap } from "@/domain/provider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAccessStore } from "@/stores/access";
|
||||
import { deployTargetsMap } from "@/domain/domain";
|
||||
|
||||
type AccessSelectProps = {
|
||||
providerType: string;
|
||||
@@ -24,8 +23,7 @@ const AccessSelect = ({ value, onValueChange, providerType }: AccessSelectProps)
|
||||
}, [value]);
|
||||
|
||||
const targetAccesses = accesses.filter((item) => {
|
||||
console.log(item, providerType);
|
||||
return item.configType === deployTargetsMap.get(providerType)?.provider;
|
||||
return item.configType === deployProvidersMap.get(providerType)?.provider;
|
||||
});
|
||||
|
||||
return (
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Dropdown } from "antd";
|
||||
import { Plus as PlusIcon } from "lucide-react";
|
||||
import { PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons";
|
||||
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { newWorkflowNode, workflowNodeDropdownList, WorkflowNodeType } from "@/domain/workflow";
|
||||
@@ -22,7 +22,7 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="before:content-[''] before:w-[2px] before:bg-stone-200 before:absolute before:h-full before:left-[50%] before:-translate-x-[50%] before:top-0 pt-6 pb-9 relative flex flex-col items-center">
|
||||
<div className="before:content-[''] before:w-[2px] before:bg-stone-200 before:absolute before:h-full before:left-[50%] before:-translate-x-[50%] before:top-0 py-6 relative">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: workflowNodeDropdownList.map((item) => {
|
||||
@@ -56,8 +56,8 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => {
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<div className="bg-stone-400 hover:bg-stone-500 rounded-full z-10 relative outline-none">
|
||||
<PlusIcon className="text-white" size={18} />
|
||||
<div className="bg-stone-400 hover:bg-stone-500 rounded-full size-5 z-[1] relative flex items-center justify-center cursor-pointer">
|
||||
<PlusOutlinedIcon className="text-white" />
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
@@ -32,7 +32,7 @@ const BranchNode = memo(({ data }: BrandNodeProps) => {
|
||||
}}
|
||||
size={"sm"}
|
||||
variant={"outline"}
|
||||
className="text-xs px-2 h-6 rounded-full absolute -top-3 left-[50%] -translate-x-1/2 z-10 dark:text-stone-200"
|
||||
className="text-xs px-2 h-6 rounded-full absolute -top-3 left-[50%] -translate-x-1/2 z-[1] dark:text-stone-200"
|
||||
>
|
||||
{t("workflow.node.addBranch.label")}
|
||||
</Button>
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
import AddNode from "./AddNode";
|
||||
import { NodeProps } from "./types";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { Dropdown } from "antd";
|
||||
import { Ellipsis, Trash2 } from "lucide-react";
|
||||
import { DeleteOutlined as DeleteOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons";
|
||||
|
||||
import AddNode from "./AddNode";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
import { type NodeProps } from "./types";
|
||||
|
||||
const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => {
|
||||
const { updateNode, removeBranch } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeBranch"]));
|
||||
@@ -12,14 +13,14 @@ const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-md shadow-md w-[261px] mt-10 relative z-10">
|
||||
<div className="rounded-md shadow-md w-[261px] mt-10 relative z-[1]">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "delete",
|
||||
label: "删除分支",
|
||||
icon: <Trash2 size={16} />,
|
||||
icon: <DeleteOutlinedIcon />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
removeBranch(branchId ?? "", branchIndex ?? 0);
|
||||
@@ -30,7 +31,7 @@ const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => {
|
||||
trigger={["click"]}
|
||||
>
|
||||
<div className="absolute right-2 top-1 cursor-pointer">
|
||||
<Ellipsis size={17} className="text-stone-600" />
|
||||
<EllipsisOutlinedIcon size={17} className="text-stone-600" />
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
|
@@ -1,31 +1,33 @@
|
||||
import { WorkflowNode } from "@/domain/workflow";
|
||||
import { memo } from "react";
|
||||
import DeployToAliyunOSS from "./DeployToAliyunOss";
|
||||
|
||||
import { type WorkflowNode } from "@/domain/workflow";
|
||||
import DeployToAliyunALB from "./DeployToAliyunALB";
|
||||
import DeployToAliyunCDN from "./DeployToAliyunCDN";
|
||||
import DeployToAliyunCLB from "./DeployToAliyunCLB";
|
||||
import DeployToAliyunNLB from "./DeployToAliyunNLB";
|
||||
import DeployToAliyunOSS from "./DeployToAliyunOss";
|
||||
import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN";
|
||||
import DeployToBytePlusCDN from "./DeployToByteplusCDN";
|
||||
import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN";
|
||||
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
|
||||
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
|
||||
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
|
||||
import DeployToLocal from "./DeployToLocal";
|
||||
import DeployToQiniuCDN from "./DeployToQiniuCDN";
|
||||
import DeployToWebhook from "./DeployToWebhook";
|
||||
import DeployToSSH from "./DeployToSSH";
|
||||
import DeployToTencentCDN from "./DeployToTencentCDN";
|
||||
import DeployToTencentCLB from "./DeployToTencentCLB";
|
||||
import DeployToTencentCOS from "./DeployToTencentCOS";
|
||||
import DeployToTencentTEO from "./DeployToTencentTEO";
|
||||
import DeployToSSH from "./DeployToSSH";
|
||||
import DeployToLocal from "./DeployToLocal";
|
||||
import DeployToByteplusCDN from "./DeployToByteplusCDN";
|
||||
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
|
||||
import DeployToVolcengineLive from "./DeployToVolcengineLive";
|
||||
import DeployToTencentEO from "./DeployToTencentTEO";
|
||||
import DeployToVolcEngineCDN from "./DeployToVolcengineCDN";
|
||||
import DeployToVolcEngineLive from "./DeployToVolcengineLive";
|
||||
import DeployToWebhook from "./DeployToWebhook";
|
||||
|
||||
export type DeployFormProps = {
|
||||
data: WorkflowNode;
|
||||
defaultProivder?: string;
|
||||
};
|
||||
|
||||
const DeployForm = ({ data, defaultProivder }: DeployFormProps) => {
|
||||
return <div className="dark:text-stone-200">{getForm(data, defaultProivder)}</div>;
|
||||
};
|
||||
@@ -68,17 +70,17 @@ const getForm = (data: WorkflowNode, defaultProivder?: string) => {
|
||||
case "tencentcloud-cos":
|
||||
return <DeployToTencentCOS data={data} />;
|
||||
case "tencentcloud-eo":
|
||||
return <DeployToTencentTEO data={data} />;
|
||||
return <DeployToTencentEO data={data} />;
|
||||
case "ssh":
|
||||
return <DeployToSSH data={data} />;
|
||||
case "local":
|
||||
return <DeployToLocal data={data} />;
|
||||
case "byteplus-cdn":
|
||||
return <DeployToByteplusCDN data={data} />;
|
||||
return <DeployToBytePlusCDN data={data} />;
|
||||
case "volcengine-cdn":
|
||||
return <DeployToVolcengineCDN data={data} />;
|
||||
return <DeployToVolcEngineCDN data={data} />;
|
||||
case "volcengine-live":
|
||||
return <DeployToVolcengineLive data={data} />;
|
||||
return <DeployToVolcEngineLive data={data} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import { WorkflowNode } from "@/domain/workflow";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Show from "../Show";
|
||||
import DeployForm from "./DeployForm";
|
||||
import { DeployTarget, deployTargets } from "@/domain/domain";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import DeployNodeForm from "./node/DeployNodeForm";
|
||||
import { deployProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNode } from "@/domain/workflow";
|
||||
|
||||
type DeployPanelBodyProps = {
|
||||
data: WorkflowNode;
|
||||
};
|
||||
|
||||
const DeployPanelBody = ({ data }: DeployPanelBodyProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -21,33 +23,22 @@ const DeployPanelBody = ({ data }: DeployPanelBodyProps) => {
|
||||
return (
|
||||
<>
|
||||
{/* 默认展示服务商列表 */}
|
||||
<Show when={!providerType} fallback={<DeployForm data={data} defaultProivder={providerType} />}>
|
||||
<Show when={!providerType} fallback={<DeployNodeForm data={data} defaultProivderType={providerType} />}>
|
||||
<div className="text-lg font-semibold text-gray-700">选择服务商</div>
|
||||
{deployTargets
|
||||
.reduce((acc: DeployTarget[][], provider, index) => {
|
||||
if (index % 2 === 0) {
|
||||
acc.push([provider]);
|
||||
} else {
|
||||
acc[acc.length - 1].push(provider);
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
.map((providerRow, rowIndex) => (
|
||||
<div key={rowIndex} className="flex space-x-5">
|
||||
{providerRow.map((provider, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex space-x-2 w-[47%] items-center cursor-pointer hover:bg-slate-100 p-2 rounded-sm"
|
||||
onClick={() => {
|
||||
setProviderType(provider.type);
|
||||
}}
|
||||
>
|
||||
<img src={provider.icon} alt={provider.type} className="w-8 h-8" />
|
||||
<div className="text-muted-foreground text-sm">{t(provider.name)}</div>
|
||||
</div>
|
||||
))}
|
||||
{Array.from(deployProvidersMap.values()).map((provider, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex space-x-2 w-[47%] items-center cursor-pointer hover:bg-slate-100 p-2 rounded-sm"
|
||||
onClick={() => {
|
||||
setProviderType(provider.type);
|
||||
}}
|
||||
>
|
||||
<img src={provider.icon} alt={provider.type} className="w-8 h-8" />
|
||||
<div className="text-muted-foreground text-sm">{t(provider.name)}</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import { WorkflowNode, WorkflowNodeType } from "@/domain/workflow";
|
||||
import AddNode from "./AddNode";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Dropdown } from "antd";
|
||||
import { Ellipsis, Trash2 } from "lucide-react";
|
||||
import { DeleteOutlined as DeleteOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import AddNode from "./AddNode";
|
||||
import { usePanel } from "./PanelProvider";
|
||||
import PanelBody from "./PanelBody";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Show from "../Show";
|
||||
import { deployTargetsMap } from "@/domain/domain";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { deployProvidersMap } from "@/domain/provider";
|
||||
import { notifyChannelsMap } from "@/domain/settings";
|
||||
import { type WorkflowNode, WorkflowNodeType } from "@/domain/workflow";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
|
||||
type NodeProps = {
|
||||
data: WorkflowNode;
|
||||
@@ -56,7 +57,7 @@ const Node = ({ data }: NodeProps) => {
|
||||
case WorkflowNodeType.Apply:
|
||||
return <div className="text-muted-foreground truncate">{data.config?.domain as string}</div>;
|
||||
case WorkflowNodeType.Deploy: {
|
||||
const provider = deployTargetsMap.get(data.config?.providerType as string);
|
||||
const provider = deployProvidersMap.get(data.config?.providerType as string);
|
||||
return (
|
||||
<div className="flex space-x-2 items-center text-muted-foreground">
|
||||
<img src={provider?.icon} className="w-6 h-6" />
|
||||
@@ -90,7 +91,7 @@ const Node = ({ data }: NodeProps) => {
|
||||
{
|
||||
key: "delete",
|
||||
label: t(`${i18nPrefix}.delete.label`),
|
||||
icon: <Trash2 size={16} />,
|
||||
icon: <DeleteOutlinedIcon />,
|
||||
danger: true,
|
||||
onClick: () => {
|
||||
removeNode(data.id);
|
||||
@@ -101,7 +102,7 @@ const Node = ({ data }: NodeProps) => {
|
||||
trigger={["click"]}
|
||||
>
|
||||
<div className="absolute right-2 top-1 cursor-pointer">
|
||||
<Ellipsis className="text-white" size={17} />
|
||||
<EllipsisOutlinedIcon className="text-white" size={17} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
</>
|
||||
|
@@ -4,6 +4,7 @@ import { useControllableValue } from "ahooks";
|
||||
import { AutoComplete, Button, Divider, Form, Input, Select, Space, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||
import { produce } from "immer";
|
||||
import z from "zod";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
@@ -12,7 +13,8 @@ import ModalForm from "@/components/core/ModalForm";
|
||||
import MultipleInput from "@/components/core/MultipleInput";
|
||||
import { usePanel } from "../PanelProvider";
|
||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
||||
import { ACCESS_USAGES, accessProvidersMap } from "@/domain/access";
|
||||
import { ACCESS_USAGES } from "@/domain/access";
|
||||
import { accessProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNode, type WorkflowNodeConfig } from "@/domain/workflow";
|
||||
import { useContactStore } from "@/stores/contact";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
@@ -42,33 +44,27 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
const { hidePanel } = usePanel();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z.string({ message: t("workflow.nodes.apply.form.domains.placeholder") }).refine(
|
||||
(v) => {
|
||||
return String(v)
|
||||
.split(MULTIPLE_INPUT_DELIMITER)
|
||||
.every((e) => validDomainName(e, true));
|
||||
},
|
||||
{ message: t("common.errmsg.domain_invalid") }
|
||||
),
|
||||
email: z.string({ message: t("workflow.nodes.apply.form.email.placeholder") }).email("common.errmsg.email_invalid"),
|
||||
access: z.string({ message: t("workflow.nodes.apply.form.access.placeholder") }).min(1, t("workflow.nodes.apply.form.access.placeholder")),
|
||||
domain: 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")),
|
||||
email: z.string({ message: t("workflow_node.apply.form.email.placeholder") }).email("common.errmsg.email_invalid"),
|
||||
access: z.string({ message: t("workflow_node.apply.form.access.placeholder") }).min(1, t("workflow_node.apply.form.access.placeholder")),
|
||||
keyAlgorithm: z.string().nullish(),
|
||||
nameservers: z
|
||||
.string()
|
||||
.refine(
|
||||
(v) => {
|
||||
if (!v) return true;
|
||||
return String(v)
|
||||
.split(MULTIPLE_INPUT_DELIMITER)
|
||||
.every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e));
|
||||
},
|
||||
{ message: t("common.errmsg.host_invalid") }
|
||||
)
|
||||
.nullish(),
|
||||
.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.nodes.apply.form.propagation_timeout.placeholder")),
|
||||
z.string().refine((v) => !v || (parseInt(v) === +v && +v > 0), { message: t("workflow.nodes.apply.form.propagation_timeout.placeholder") }),
|
||||
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(),
|
||||
@@ -81,8 +77,14 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
} = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: data?.config ?? initFormModel(),
|
||||
onSubmit: async (values) => {
|
||||
await updateNode({ ...data, config: { ...values }, validated: true });
|
||||
await formInst.validateFields();
|
||||
await addEmail(values.email);
|
||||
await updateNode(
|
||||
produce(data, (draft) => {
|
||||
draft.config = { ...values };
|
||||
draft.validated = true;
|
||||
})
|
||||
);
|
||||
hidePanel();
|
||||
},
|
||||
});
|
||||
@@ -106,15 +108,15 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow.nodes.apply.form.domains.label")}
|
||||
label={t("workflow_node.apply.form.domains.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.domains.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.domains.tooltip") }}></span>}
|
||||
>
|
||||
<Space.Compact style={{ width: "100%" }}>
|
||||
<Input
|
||||
disabled={formPending}
|
||||
value={fieldDomains}
|
||||
placeholder={t("workflow.nodes.apply.form.domains.placeholder")}
|
||||
placeholder={t("workflow_node.apply.form.domains.placeholder")}
|
||||
onChange={handleFieldDomainsChange}
|
||||
/>
|
||||
<FormFieldDomainsModalForm
|
||||
@@ -134,19 +136,19 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={t("workflow.nodes.apply.form.email.label")}
|
||||
label={t("workflow_node.apply.form.email.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.email.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.email.tooltip") }}></span>}
|
||||
>
|
||||
<FormFieldEmailSelect placeholder={t("workflow.nodes.apply.form.email.placeholder")} />
|
||||
<FormFieldEmailSelect placeholder={t("workflow_node.apply.form.email.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Form.Item className="mb-0">
|
||||
<label className="block mb-1">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex-grow max-w-full truncate">
|
||||
<span>{t("workflow.nodes.apply.form.access.label")}</span>
|
||||
<Tooltip title={t("workflow.nodes.apply.form.access.tooltip")}>
|
||||
<span>{t("workflow_node.apply.form.access.label")}</span>
|
||||
<Tooltip title={t("workflow_node.apply.form.access.tooltip")}>
|
||||
<Typography.Text className="ms-1" type="secondary">
|
||||
<QuestionCircleOutlinedIcon />
|
||||
</Typography.Text>
|
||||
@@ -158,7 +160,7 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
<PlusOutlinedIcon />
|
||||
{t("workflow.nodes.apply.form.access.button")}
|
||||
{t("workflow_node.apply.form.access.button")}
|
||||
</Button>
|
||||
}
|
||||
onSubmit={(record) => {
|
||||
@@ -173,7 +175,7 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
</label>
|
||||
<Form.Item name="access" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
placeholder={t("workflow.nodes.apply.form.access.placeholder")}
|
||||
placeholder={t("workflow_node.apply.form.access.placeholder")}
|
||||
filter={(record) => {
|
||||
const provider = accessProvidersMap.get(record.configType);
|
||||
return ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.APPLY === provider?.usage;
|
||||
@@ -183,33 +185,33 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
</Form.Item>
|
||||
|
||||
<Divider className="my-1">
|
||||
<Typography.Text className="text-xs" type="secondary">
|
||||
{t("workflow.nodes.apply.form.advanced_settings.label")}
|
||||
<Typography.Text className="font-normal text-xs" type="secondary">
|
||||
{t("workflow_node.apply.form.advanced_config.label")}
|
||||
</Typography.Text>
|
||||
</Divider>
|
||||
|
||||
<Form.Item name="keyAlgorithm" label={t("workflow.nodes.apply.form.key_algorithm.label")} rules={[formRule]}>
|
||||
<Form.Item name="keyAlgorithm" label={t("workflow_node.apply.form.key_algorithm.label")} rules={[formRule]}>
|
||||
<Select
|
||||
options={["RSA2048", "RSA3072", "RSA4096", "RSA8192", "EC256", "EC384"].map((e) => ({
|
||||
label: e,
|
||||
value: e,
|
||||
}))}
|
||||
placeholder={t("workflow.nodes.apply.form.key_algorithm.placeholder")}
|
||||
placeholder={t("workflow_node.apply.form.key_algorithm.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="nameservers"
|
||||
label={t("workflow.nodes.apply.form.nameservers.label")}
|
||||
label={t("workflow_node.apply.form.nameservers.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.nameservers.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.nameservers.tooltip") }}></span>}
|
||||
>
|
||||
<Space.Compact style={{ width: "100%" }}>
|
||||
<Input
|
||||
allowClear
|
||||
disabled={formPending}
|
||||
value={fieldNameservers}
|
||||
placeholder={t("workflow.nodes.apply.form.nameservers.placeholder")}
|
||||
placeholder={t("workflow_node.apply.form.nameservers.placeholder")}
|
||||
onChange={handleFieldNameserversChange}
|
||||
/>
|
||||
<FormFieldNameserversModalForm
|
||||
@@ -229,25 +231,25 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
||||
|
||||
<Form.Item
|
||||
name="propagationTimeout"
|
||||
label={t("workflow.nodes.apply.form.propagation_timeout.label")}
|
||||
label={t("workflow_node.apply.form.propagation_timeout.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.propagation_timeout.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.propagation_timeout.tooltip") }}></span>}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
allowClear
|
||||
min={0}
|
||||
max={3600}
|
||||
placeholder={t("workflow.nodes.apply.form.propagation_timeout.placeholder")}
|
||||
addonAfter={t("workflow.nodes.apply.form.propagation_timeout.suffix")}
|
||||
placeholder={t("workflow_node.apply.form.propagation_timeout.placeholder")}
|
||||
addonAfter={t("workflow_node.apply.form.propagation_timeout.suffix")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="disableFollowCNAME"
|
||||
label={t("workflow.nodes.apply.form.disable_follow_cname.label")}
|
||||
label={t("workflow_node.apply.form.disable_follow_cname.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.disable_follow_cname.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.disable_follow_cname.tooltip") }}></span>}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
@@ -339,12 +341,9 @@ const FormFieldDomainsModalForm = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domains: z.array(z.string()).refine(
|
||||
(v) => {
|
||||
return v.every((e) => !e?.trim() || validDomainName(e.trim(), true));
|
||||
},
|
||||
{ message: t("common.errmsg.domain_invalid") }
|
||||
),
|
||||
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 [formInst] = Form.useForm<z.infer<typeof formSchema>>();
|
||||
@@ -372,14 +371,14 @@ const FormFieldDomainsModalForm = ({
|
||||
form={formInst}
|
||||
initialValues={model}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
title={t("workflow.nodes.apply.form.domains.multiple_input_modal.title")}
|
||||
title={t("workflow_node.apply.form.domains.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
width={480}
|
||||
onFinish={handleFinish}
|
||||
>
|
||||
<Form.Item name="domains" rules={[formRule]}>
|
||||
<MultipleInput placeholder={t("workflow.nodes.apply.form.domains.multiple_input_modal.placeholder")} />
|
||||
<MultipleInput placeholder={t("workflow_node.apply.form.domains.multiple_input_modal.placeholder")} />
|
||||
</Form.Item>
|
||||
</ModalForm>
|
||||
);
|
||||
@@ -389,12 +388,9 @@ const FormFieldNameserversModalForm = ({ data, trigger, onFinish }: { data: stri
|
||||
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));
|
||||
},
|
||||
{ message: t("common.errmsg.domain_invalid") }
|
||||
),
|
||||
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 [formInst] = Form.useForm<z.infer<typeof formSchema>>();
|
||||
@@ -422,14 +418,14 @@ const FormFieldNameserversModalForm = ({ data, trigger, onFinish }: { data: stri
|
||||
form={formInst}
|
||||
initialValues={model}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
title={t("workflow.nodes.apply.form.nameservers.multiple_input_modal.title")}
|
||||
title={t("workflow_node.apply.form.nameservers.multiple_input_modal.title")}
|
||||
trigger={trigger}
|
||||
validateTrigger="onSubmit"
|
||||
width={480}
|
||||
onFinish={handleFinish}
|
||||
>
|
||||
<Form.Item name="nameservers" rules={[formRule]}>
|
||||
<MultipleInput placeholder={t("workflow.nodes.apply.form.nameservers.multiple_input_modal.placeholder")} />
|
||||
<MultipleInput placeholder={t("workflow_node.apply.form.nameservers.multiple_input_modal.placeholder")} />
|
||||
</Form.Item>
|
||||
</ModalForm>
|
||||
);
|
||||
|
282
ui/src/components/workflow/node/DeployNodeForm.tsx
Normal file
282
ui/src/components/workflow/node/DeployNodeForm.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Button, Divider, Form, Select, Space, Tooltip, Typography } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||
import { produce } from "immer";
|
||||
import z from "zod";
|
||||
|
||||
import AccessEditModal from "@/components/access/AccessEditModal";
|
||||
import AccessSelect from "@/components/access/AccessSelect";
|
||||
import { usePanel } from "../PanelProvider";
|
||||
import DeployNodeFormAliyunALBFields from "./DeployNodeFormAliyunALBFields";
|
||||
import DeployNodeFormAliyunCLBFields from "./DeployNodeFormAliyunCLBFields";
|
||||
import DeployNodeFormAliyunCDNFields from "./DeployNodeFormAliyunCDNFields";
|
||||
import DeployNodeFormAliyunDCDNFields from "./DeployNodeFormAliyunDCDNFields";
|
||||
import DeployNodeFormAliyunNLBFields from "./DeployNodeFormAliyunNLBFields";
|
||||
import DeployNodeFormAliyunOSSFields from "./DeployNodeFormAliyunOSSFields";
|
||||
import DeployNodeFormBaiduCloudCDNFields from "./DeployNodeFormBaiduCloudCDNFields";
|
||||
import DeployNodeFormBytePlusCDNFields from "./DeployNodeFormBytePlusCDNFields";
|
||||
import DeployNodeFormDogeCloudCDNFields from "./DeployNodeFormDogeCloudCDNFields";
|
||||
import DeployNodeFormHuaweiCloudCDNFields from "./DeployNodeFormHuaweiCloudCDNFields";
|
||||
import DeployNodeFormHuaweiCloudELBFields from "./DeployNodeFormHuaweiCloudELBFields";
|
||||
import DeployNodeFormKubernetesSecretFields from "./DeployNodeFormKubernetesSecretFields";
|
||||
import DeployNodeFormLocalFields from "./DeployNodeFormLocalFields";
|
||||
import DeployNodeFormQiniuCDNFields from "./DeployNodeFormQiniuCDNFields";
|
||||
import DeployNodeFormSSHFields from "./DeployNodeFormSSHFields";
|
||||
import DeployNodeFormTencentCloudCDNFields from "./DeployNodeFormTencentCloudCDNFields";
|
||||
import DeployNodeFormTencentCloudCLBFields from "./DeployNodeFormTencentCloudCLBFields";
|
||||
import DeployNodeFormTencentCloudCOSFields from "./DeployNodeFormTencentCloudCOSFields";
|
||||
import DeployNodeFormTencentCloudECDNFields from "./DeployNodeFormTencentCloudECDNFields";
|
||||
import DeployNodeFormTencentCloudEOFields from "./DeployNodeFormTencentCloudEOFields";
|
||||
import DeployNodeFormVolcEngineCDNFields from "./DeployNodeFormVolcEngineCDNFields";
|
||||
import DeployNodeFormVolcEngineLiveFields from "./DeployNodeFormVolcEngineLiveFields";
|
||||
import DeployNodeFormWebhookFields from "./DeployNodeFormWebhookFields";
|
||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
||||
import { ACCESS_USAGES } from "@/domain/access";
|
||||
import { accessProvidersMap, deployProvidersMap } from "@/domain/provider";
|
||||
import { type WorkflowNode, type WorkflowNodeConfig } from "@/domain/workflow";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
|
||||
export type DeployFormProps = {
|
||||
data: WorkflowNode;
|
||||
defaultProivderType?: string;
|
||||
};
|
||||
|
||||
const initFormModel = (): WorkflowNodeConfig => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const DeployNodeForm = ({ data, defaultProivderType }: DeployFormProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"]));
|
||||
const { hidePanel } = usePanel();
|
||||
|
||||
const formSchema = z.object({
|
||||
providerType: z
|
||||
.string({ message: t("workflow_node.deploy.form.provider_type.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.provider_type.placeholder")),
|
||||
access: z
|
||||
.string({ message: t("workflow_node.deploy.form.provider_access.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.provider_access.placeholder")),
|
||||
certificate: z.string({ message: t("workflow_node.deploy.form.certificate.placeholder") }).nonempty(t("workflow_node.deploy.form.certificate.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const {
|
||||
form: formInst,
|
||||
formPending,
|
||||
formProps,
|
||||
} = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: data?.config ?? initFormModel(),
|
||||
onSubmit: async (values) => {
|
||||
await formInst.validateFields();
|
||||
await updateNode(
|
||||
produce(data, (draft) => {
|
||||
draft.config = { ...values };
|
||||
draft.validated = true;
|
||||
})
|
||||
);
|
||||
hidePanel();
|
||||
},
|
||||
});
|
||||
|
||||
const [previousOutput, setPreviousOutput] = useState<WorkflowNode[]>([]);
|
||||
useEffect(() => {
|
||||
const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
|
||||
setPreviousOutput(rs);
|
||||
}, [data, getWorkflowOuptutBeforeId]);
|
||||
|
||||
const fieldProviderType = Form.useWatch("providerType", formInst);
|
||||
// const fieldAccess = Form.useWatch("access", formInst);
|
||||
|
||||
const formFieldsComponent = useMemo(() => {
|
||||
/*
|
||||
注意:如果追加新的子组件,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new child component, please keep ASCII order.
|
||||
*/
|
||||
switch (fieldProviderType) {
|
||||
case "aliyun-alb":
|
||||
return <DeployNodeFormAliyunALBFields />;
|
||||
case "aliyun-clb":
|
||||
return <DeployNodeFormAliyunCLBFields />;
|
||||
case "aliyun-cdn":
|
||||
return <DeployNodeFormAliyunCDNFields />;
|
||||
case "aliyun-dcdn":
|
||||
return <DeployNodeFormAliyunDCDNFields />;
|
||||
case "aliyun-nlb":
|
||||
return <DeployNodeFormAliyunNLBFields />;
|
||||
case "aliyun-oss":
|
||||
return <DeployNodeFormAliyunOSSFields />;
|
||||
case "baiducloud-cdn":
|
||||
return <DeployNodeFormBaiduCloudCDNFields />;
|
||||
case "byteplus-cdn":
|
||||
return <DeployNodeFormBytePlusCDNFields />;
|
||||
case "dogecloud-cdn":
|
||||
return <DeployNodeFormDogeCloudCDNFields />;
|
||||
case "huaweicloud-cdn":
|
||||
return <DeployNodeFormHuaweiCloudCDNFields />;
|
||||
case "huaweicloud-elb":
|
||||
return <DeployNodeFormHuaweiCloudELBFields />;
|
||||
case "k8s-secret":
|
||||
return <DeployNodeFormKubernetesSecretFields />;
|
||||
case "local":
|
||||
return <DeployNodeFormLocalFields />;
|
||||
case "qiniu-cdn":
|
||||
return <DeployNodeFormQiniuCDNFields />;
|
||||
case "ssh":
|
||||
return <DeployNodeFormSSHFields />;
|
||||
case "tencentcloud-cdn":
|
||||
return <DeployNodeFormTencentCloudCDNFields />;
|
||||
case "tencentcloud-clb":
|
||||
return <DeployNodeFormTencentCloudCLBFields />;
|
||||
case "tencentcloud-cos":
|
||||
return <DeployNodeFormTencentCloudCOSFields />;
|
||||
case "tencentcloud-ecdn":
|
||||
return <DeployNodeFormTencentCloudECDNFields />;
|
||||
case "tencentcloud-eo":
|
||||
return <DeployNodeFormTencentCloudEOFields />;
|
||||
case "volcengine-cdn":
|
||||
return <DeployNodeFormVolcEngineCDNFields />;
|
||||
case "volcengine-live":
|
||||
return <DeployNodeFormVolcEngineLiveFields />;
|
||||
case "webhook":
|
||||
return <DeployNodeFormWebhookFields />;
|
||||
}
|
||||
}, [fieldProviderType]);
|
||||
|
||||
const handleProviderTypeSelect = (value: string) => {
|
||||
if (fieldProviderType === value) return;
|
||||
|
||||
// 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标
|
||||
if (data.config?.providerType === value) {
|
||||
formInst.resetFields();
|
||||
} else {
|
||||
const oldValues = formInst.getFieldsValue();
|
||||
const newValues: Record<string, unknown> = {};
|
||||
for (const key in oldValues) {
|
||||
if (key === "providerType" || key === "access" || key === "certificate") {
|
||||
newValues[key] = oldValues[key];
|
||||
} else {
|
||||
newValues[key] = undefined;
|
||||
}
|
||||
}
|
||||
formInst.setFieldsValue(newValues);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
||||
<Form.Item name="providerType" label={t("workflow_node.deploy.form.provider_type.label")} rules={[formRule]} initialValue={defaultProivderType}>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder={t("workflow_node.deploy.form.provider_type.placeholder")}
|
||||
filterOption={(searchValue, option) => {
|
||||
const type = String(option?.value ?? "");
|
||||
const target = deployProvidersMap.get(type);
|
||||
const filter = (v?: string) => v?.toLowerCase()?.includes(searchValue.toLowerCase()) ?? false;
|
||||
return filter(type) || filter(t(target?.name ?? ""));
|
||||
}}
|
||||
onSelect={handleProviderTypeSelect}
|
||||
>
|
||||
{Array.from(deployProvidersMap.values()).map((item) => {
|
||||
return (
|
||||
<Select.Option key={item.type} label={t(item.name)} value={item.type} title={t(item.name)}>
|
||||
<Space className="flex-grow max-w-full truncate" size={4}>
|
||||
<Avatar src={item.icon} size="small" />
|
||||
<Typography.Text className="leading-loose" ellipsis>
|
||||
{t(item.name)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<label className="block mb-1">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex-grow max-w-full truncate">
|
||||
<span>{t("workflow_node.deploy.form.provider_access.label")}</span>
|
||||
<Tooltip title={t("workflow_node.deploy.form.provider_access.tooltip")}>
|
||||
<Typography.Text className="ms-1" type="secondary">
|
||||
<QuestionCircleOutlinedIcon />
|
||||
</Typography.Text>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<AccessEditModal
|
||||
data={{ configType: deployProvidersMap.get(defaultProivderType!)?.provider }}
|
||||
preset="add"
|
||||
trigger={
|
||||
<Button size="small" type="link">
|
||||
<PlusOutlinedIcon />
|
||||
{t("workflow_node.deploy.form.provider_access.button")}
|
||||
</Button>
|
||||
}
|
||||
onSubmit={(record) => {
|
||||
const provider = accessProvidersMap.get(record.configType);
|
||||
if (ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.DEPLOY === provider?.usage) {
|
||||
formInst.setFieldValue("access", record.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="access" rules={[formRule]}>
|
||||
<AccessSelect
|
||||
placeholder={t("workflow_node.deploy.form.provider_access.placeholder")}
|
||||
filter={(record) => {
|
||||
if (defaultProivderType) {
|
||||
return deployProvidersMap.get(defaultProivderType)?.provider === record.configType;
|
||||
}
|
||||
|
||||
const provider = accessProvidersMap.get(record.configType);
|
||||
return ACCESS_USAGES.ALL === provider?.usage || ACCESS_USAGES.APPLY === provider?.usage;
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="certificate"
|
||||
label={t("workflow_node.deploy.form.certificate.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.certificate.tooltip") }}></span>}
|
||||
>
|
||||
<Select
|
||||
options={previousOutput.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
options: item.output?.map((output) => {
|
||||
return {
|
||||
label: `${item.name} - ${output.label}`,
|
||||
value: `${item.id}#${output.name}`,
|
||||
};
|
||||
}),
|
||||
};
|
||||
})}
|
||||
placeholder={t("workflow_node.deploy.form.certificate.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider className="my-1">
|
||||
<Typography.Text className="font-normal text-xs" type="secondary">
|
||||
{t("workflow_node.deploy.form.params_config.label")}
|
||||
</Typography.Text>
|
||||
</Divider>
|
||||
|
||||
{formFieldsComponent}
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={formPending}>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DeployNodeForm);
|
@@ -0,0 +1,87 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const;
|
||||
const RESOURCE_TYPE_LISTENER = "listener" as const;
|
||||
|
||||
const DeployNodeFormAliyunALBFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union([z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER)], {
|
||||
message: t("workflow_node.deploy.form.aliyun_alb_resource_type.placeholder"),
|
||||
}),
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_alb_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.aliyun_alb_region.placeholder"))
|
||||
.trim(),
|
||||
loadbalancerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_LOADBALANCER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_alb_loadbalancer_id.placeholder")),
|
||||
listenerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_alb_listener_id.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldResourceType = Form.useWatch("resourceType", formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="resourceType" label={t("workflow_node.deploy.form.aliyun_alb_resource_type.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.aliyun_alb_resource_type.placeholder")}>
|
||||
<Select.Option key={RESOURCE_TYPE_LOADBALANCER} value={RESOURCE_TYPE_LOADBALANCER}>
|
||||
{t("workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LISTENER} value={RESOURCE_TYPE_LISTENER}>
|
||||
{t("workflow_node.deploy.form.aliyun_alb_resource_type.option.listener.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.aliyun_alb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_alb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_alb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
|
||||
<Form.Item
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.aliyun_alb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_alb_loadbalancer_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_alb_loadbalancer_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LISTENER}>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
label={t("workflow_node.deploy.form.aliyun_alb_listener_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_alb_listener_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_alb_listener_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunALBFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormAliyunCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.aliyun_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunCDNFields;
|
@@ -0,0 +1,96 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { validPortNumber } from "@/utils/validators";
|
||||
|
||||
const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const;
|
||||
const RESOURCE_TYPE_LISTENER = "listener" as const;
|
||||
|
||||
const DeployNodeFormAliyunCLBFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union([z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER)], {
|
||||
message: t("workflow_node.deploy.form.aliyun_clb_resource_type.placeholder"),
|
||||
}),
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_clb_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.aliyun_clb_region.placeholder"))
|
||||
.trim(),
|
||||
loadbalancerId: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder") })
|
||||
.min(1, t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
listenerPort: z
|
||||
.union([
|
||||
z
|
||||
.number()
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v),
|
||||
t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder")
|
||||
),
|
||||
z
|
||||
.string()
|
||||
.refine(
|
||||
(v) => fieldResourceType === RESOURCE_TYPE_LISTENER && validPortNumber(v),
|
||||
t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder")
|
||||
),
|
||||
])
|
||||
.nullish(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldResourceType = Form.useWatch("resourceType", formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="resourceType" label={t("workflow_node.deploy.form.aliyun_clb_resource_type.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.aliyun_clb_resource_type.placeholder")}>
|
||||
<Select.Option key={RESOURCE_TYPE_LOADBALANCER} value={RESOURCE_TYPE_LOADBALANCER}>
|
||||
{t("workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LISTENER} value={RESOURCE_TYPE_LISTENER}>
|
||||
{t("workflow_node.deploy.form.aliyun_clb_resource_type.option.listener.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.aliyun_clb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_clb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_clb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LISTENER}>
|
||||
<Form.Item
|
||||
name="listenerPort"
|
||||
label={t("workflow_node.deploy.form.aliyun_clb_listener_port.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_clb_listener_port.tooltip") }}></span>}
|
||||
initialValue={443}
|
||||
>
|
||||
<Input type="number" min={1} max={65535} placeholder={t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunCLBFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormAliyunDCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_dcdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.aliyun_dcdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_dcdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_dcdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunDCDNFields;
|
@@ -0,0 +1,87 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const;
|
||||
const RESOURCE_TYPE_LISTENER = "listener" as const;
|
||||
|
||||
const DeployNodeFormAliyunNLBFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union([z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER)], {
|
||||
message: t("workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder"),
|
||||
}),
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_nlb_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.aliyun_nlb_region.placeholder"))
|
||||
.trim(),
|
||||
loadbalancerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_LOADBALANCER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.placeholder")),
|
||||
listenerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.aliyun_nlb_listener_id.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldResourceType = Form.useWatch("resourceType", formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="resourceType" label={t("workflow_node.deploy.form.aliyun_nlb_resource_type.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder")}>
|
||||
<Select.Option key={RESOURCE_TYPE_LOADBALANCER} value={RESOURCE_TYPE_LOADBALANCER}>
|
||||
{t("workflow_node.deploy.form.aliyun_nlb_resource_type.option.loadbalancer.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LISTENER} value={RESOURCE_TYPE_LISTENER}>
|
||||
{t("workflow_node.deploy.form.aliyun_nlb_resource_type.option.listener.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.aliyun_nlb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_nlb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_nlb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
|
||||
<Form.Item
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LISTENER}>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
label={t("workflow_node.deploy.form.aliyun_nlb_listener_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_nlb_listener_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_nlb_listener_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunNLBFields;
|
@@ -0,0 +1,58 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormAliyunOSSFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
endpoint: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_endpoint.placeholder") })
|
||||
.url(t("common.errmsg.url_invalid"))
|
||||
.trim(),
|
||||
bucket: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_bucket.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.aliyun_oss_bucket.placeholder"))
|
||||
.trim(),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.aliyun_oss_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="endpoint"
|
||||
label={t("workflow_node.deploy.form.aliyun_oss_endpoint.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_oss_endpoint.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_oss_endpoint.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="bucket"
|
||||
label={t("workflow_node.deploy.form.aliyun_oss_bucket.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_oss_bucket.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_oss_bucket.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.aliyun_oss_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_oss_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.aliyun_oss_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormAliyunOSSFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormBaiduCloudCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.baiducloud_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.baiducloud_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.baiducloud_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.baiducloud_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormBaiduCloudCDNFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormBytePlusCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.byteplus_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.byteplus_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.byteplus_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.byteplus_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormBytePlusCDNFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormDogeCloudCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.dogecloud_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.dogecloud_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.dogecloud_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.dogecloud_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormDogeCloudCDNFields;
|
@@ -0,0 +1,45 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormHuaweiCloudCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder"))
|
||||
.trim(),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_cdn_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_cdn_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormHuaweiCloudCDNFields;
|
@@ -0,0 +1,111 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
|
||||
const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const;
|
||||
const RESOURCE_TYPE_LISTENER = "listener" as const;
|
||||
|
||||
const DeployNodeFormHuaweiCloudELBFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union([z.literal(RESOURCE_TYPE_CERTIFICATE), z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER)], {
|
||||
message: t("workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder"),
|
||||
}),
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.huaweicloud_elb_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.huaweicloud_elb_region.placeholder"))
|
||||
.trim(),
|
||||
certificateId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_CERTIFICATE || !!v?.trim(), t("workflow_node.deploy.form.huaweicloud_elb_certificate_id.placeholder")),
|
||||
loadbalancerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine(
|
||||
(v) => fieldResourceType !== RESOURCE_TYPE_LOADBALANCER || !!v?.trim(),
|
||||
t("workflow_node.deploy.form.huaweicloud_elb_loadbalancer_id.placeholder")
|
||||
),
|
||||
listenerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldResourceType !== RESOURCE_TYPE_LISTENER || !!v?.trim(), t("workflow_node.deploy.form.huaweicloud_elb_listener_id.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldResourceType = Form.useWatch("resourceType", formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="resourceType" label={t("workflow_node.deploy.form.huaweicloud_elb_resource_type.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder")}>
|
||||
<Select.Option key={RESOURCE_TYPE_CERTIFICATE} value={RESOURCE_TYPE_CERTIFICATE}>
|
||||
{t("workflow_node.deploy.form.huaweicloud_elb_resource_type.option.certificate.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LOADBALANCER} value={RESOURCE_TYPE_LOADBALANCER}>
|
||||
{t("workflow_node.deploy.form.huaweicloud_elb_resource_type.option.loadbalancer.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LISTENER} value={RESOURCE_TYPE_LISTENER}>
|
||||
{t("workflow_node.deploy.form.huaweicloud_elb_resource_type.option.listener.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_elb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_elb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_elb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_CERTIFICATE}>
|
||||
<Form.Item
|
||||
name="certificateId"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_elb_certificate_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_elb_certificate_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_elb_certificate_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LOADBALANCER}>
|
||||
<Form.Item
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_elb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_elb_loadbalancer_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_elb_loadbalancer_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_LISTENER}>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
label={t("workflow_node.deploy.form.huaweicloud_elb_listener_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_elb_listener_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_elb_listener_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormHuaweiCloudELBFields;
|
@@ -0,0 +1,77 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const DeployNodeFormKubernetesSecretFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
namespace: z
|
||||
.string({ message: t("workflow_node.deploy.form.k8s_namespace.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.k8s_namespace.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
secretName: z
|
||||
.string({ message: t("workflow_node.deploy.form.k8s_secret_name.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.k8s_secret_name.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
secretDataKeyForCrt: z
|
||||
.string({ message: t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
secretDataKeyForKey: z
|
||||
.string({ message: t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="namespace"
|
||||
label={t("workflow_node.deploy.form.k8s_namespace.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_namespace.tooltip") }}></span>}
|
||||
initialValue="default"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.k8s_namespace.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="secretName"
|
||||
label={t("workflow_node.deploy.form.k8s_secret_name.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_secret_name.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.k8s_secret_name.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="secretDataKeyForCrt"
|
||||
label={t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.tooltip") }}></span>}
|
||||
initialValue="tls.crt"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="secretDataKeyForKey"
|
||||
label={t("workflow_node.deploy.form.k8s_secret_data_key_for_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip") }}></span>}
|
||||
initialValue="tls.key"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormKubernetesSecretFields;
|
305
ui/src/components/workflow/node/DeployNodeFormLocalFields.tsx
Normal file
305
ui/src/components/workflow/node/DeployNodeFormLocalFields.tsx
Normal file
@@ -0,0 +1,305 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Dropdown, Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const FORMAT_PEM = "pem" as const;
|
||||
const FORMAT_PFX = "pfx" as const;
|
||||
const FORMAT_JKS = "jks" as const;
|
||||
|
||||
const SHELLENV_SH = "sh" as const;
|
||||
const SHELLENV_CMD = "cmd" as const;
|
||||
const SHELLENV_POWERSHELL = "powershell" as const;
|
||||
|
||||
const DeployNodeFormLocalFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
format: z.union([z.literal(FORMAT_PEM), z.literal(FORMAT_PFX), z.literal(FORMAT_JKS)], {
|
||||
message: t("domain.deployment.form.local_format.placeholder"),
|
||||
}),
|
||||
certPath: z
|
||||
.string()
|
||||
.min(1, t("workflow_node.deploy.form.local_cert_path.tooltip"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
keyPath: z
|
||||
.string()
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_PEM || !!v?.trim(), { message: t("workflow_node.deploy.form.local_key_path.tooltip") }),
|
||||
pfxPassword: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_PFX || !!v?.trim(), { message: t("workflow_node.deploy.form.local_pfx_password.tooltip") }),
|
||||
jksAlias: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.local_jks_alias.tooltip") }),
|
||||
jksKeypass: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.local_jks_keypass.tooltip") }),
|
||||
jksStorepass: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.local_jks_storepass.tooltip") }),
|
||||
shellEnv: z.union([z.literal(SHELLENV_SH), z.literal(SHELLENV_CMD), z.literal(SHELLENV_POWERSHELL)], {
|
||||
message: t("domain.deployment.form.shell.placeholder"),
|
||||
}),
|
||||
preCommand: z
|
||||
.string()
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
||||
.nullish(),
|
||||
postCommand: z
|
||||
.string()
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
||||
.nullish(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldFormat = Form.useWatch("format", formInst);
|
||||
const fieldCertPath = Form.useWatch("certPath", formInst);
|
||||
|
||||
const handleFormatSelect = (value: string) => {
|
||||
if (fieldFormat === value) return;
|
||||
|
||||
switch (value) {
|
||||
case FORMAT_PEM:
|
||||
{
|
||||
if (/(.pfx|.jks)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.pfx|.jks)$/, ".crt"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FORMAT_PFX:
|
||||
{
|
||||
if (/(.crt|.jks)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.jks)$/, ".pfx"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FORMAT_JKS:
|
||||
{
|
||||
if (/(.crt|.pfx)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.pfx)$/, ".jks"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePresetScriptClick = (key: string) => {
|
||||
switch (key) {
|
||||
case "reload_nginx":
|
||||
{
|
||||
formInst.setFieldValue("shellEnv", "sh");
|
||||
formInst.setFieldValue("postCommand", "sudo service nginx reload");
|
||||
}
|
||||
break;
|
||||
|
||||
case "binding_iis":
|
||||
{
|
||||
formInst.setFieldValue("shellEnv", "powershell");
|
||||
formInst.setFieldValue(
|
||||
"postCommand",
|
||||
`# 请将以下变量替换为实际值
|
||||
$pfxPath = "<your-pfx-path>" # PFX 文件路径
|
||||
$pfxPassword = "<your-pfx-password>" # PFX 密码
|
||||
$siteName = "<your-site-name>" # IIS 网站名称
|
||||
$domain = "<your-domain-name>" # 域名
|
||||
$ipaddr = "<your-binding-ip>" # 绑定 IP,“*”表示所有 IP 绑定
|
||||
$port = "<your-binding-port>" # 绑定端口
|
||||
|
||||
|
||||
# 导入证书到本地计算机的个人存储区
|
||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
||||
# 获取 Thumbprint
|
||||
$thumbprint = $cert.Thumbprint
|
||||
# 导入 WebAdministration 模块
|
||||
Import-Module WebAdministration
|
||||
# 检查是否已存在 HTTPS 绑定
|
||||
$existingBinding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -HostHeader "$domain" -ErrorAction SilentlyContinue
|
||||
if (!$existingBinding) {
|
||||
# 添加新的 HTTPS 绑定
|
||||
New-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
|
||||
}
|
||||
# 获取绑定对象
|
||||
$binding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
|
||||
# 绑定 SSL 证书
|
||||
$binding.AddSslCertificate($thumbprint, "My")
|
||||
# 删除目录下的证书文件
|
||||
Remove-Item -Path "$pfxPath" -Force
|
||||
`.trim()
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "binding_netsh":
|
||||
{
|
||||
formInst.setFieldValue("shellEnv", "powershell");
|
||||
formInst.setFieldValue(
|
||||
"postCommand",
|
||||
`# 请将以下变量替换为实际值
|
||||
$pfxPath = "<your-pfx-path>" # PFX 文件路径
|
||||
$pfxPassword = "<your-pfx-password>" # PFX 密码
|
||||
$ipaddr = "<your-binding-ip>" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名。
|
||||
$port = "<your-binding-port>" # 绑定端口
|
||||
|
||||
$addr = $ipaddr + ":" + $port
|
||||
|
||||
# 导入证书到本地计算机的个人存储区
|
||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
||||
# 获取 Thumbprint
|
||||
$thumbprint = $cert.Thumbprint
|
||||
# 检测端口是否绑定证书,如绑定则删除绑定
|
||||
$isExist = netsh http show sslcert ipport=$addr
|
||||
if ($isExist -like "*$addr*"){ netsh http delete sslcert ipport=$addr }
|
||||
# 绑定到端口
|
||||
netsh http add sslcert ipport=$addr certhash=$thumbprint
|
||||
# 删除目录下的证书文件
|
||||
Remove-Item -Path "$pfxPath" -Force
|
||||
`.trim()
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="format" label={t("workflow_node.deploy.form.local_format.label")} rules={[formRule]} initialValue={FORMAT_PEM}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.local_format.placeholder")} onSelect={handleFormatSelect}>
|
||||
<Select.Option key={FORMAT_PEM} value={FORMAT_PEM}>
|
||||
{t("workflow_node.deploy.form.local_format.option.pem.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={FORMAT_PFX} value={FORMAT_PFX}>
|
||||
{t("workflow_node.deploy.form.local_format.option.pfx.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={FORMAT_JKS} value={FORMAT_JKS}>
|
||||
{t("workflow_node.deploy.form.local_format.option.jks.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="certPath"
|
||||
label={t("workflow_node.deploy.form.local_cert_path.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.local_cert_path.tooltip") }}></span>}
|
||||
initialValue="/etc/ssl/certs/cert.crt"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_cert_path.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_PEM}>
|
||||
<Form.Item
|
||||
name="keyPath"
|
||||
label={t("workflow_node.deploy.form.local_key_path.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.local_key_path.tooltip") }}></span>}
|
||||
initialValue="/etc/ssl/certs/cert.key"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_key_path.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_PFX}>
|
||||
<Form.Item name="pfxPassword" label={t("workflow_node.deploy.form.local_pfx_password.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_pfx_password.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_JKS}>
|
||||
<Form.Item name="jksAlias" label={t("workflow_node.deploy.form.local_jks_alias.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_jks_alias.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="jksKeypass" label={t("workflow_node.deploy.form.local_jks_keypass.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_jks_keypass.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="jksStorepass" label={t("workflow_node.deploy.form.local_jks_storepass.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.local_jks_storepass.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item name="shellEnv" label={t("workflow_node.deploy.form.local_shell_env.label")} rules={[formRule]} initialValue={SHELLENV_SH}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.local_shell_env.placeholder")}>
|
||||
<Select.Option key={SHELLENV_SH} value={SHELLENV_SH}>
|
||||
{t("workflow_node.deploy.form.local_shell_env.option.sh.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={SHELLENV_CMD} value={SHELLENV_CMD}>
|
||||
{t("workflow_node.deploy.form.local_shell_env.option.cmd.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={SHELLENV_POWERSHELL} value={SHELLENV_POWERSHELL}>
|
||||
{t("workflow_node.deploy.form.local_shell_env.option.powershell.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="preCommand" label={t("workflow_node.deploy.form.local_pre_command.label")} rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.local_pre_command.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<label className="block mb-1">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex-grow max-w-full truncate">
|
||||
<span>{t("workflow_node.deploy.form.local_post_command.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "reload_nginx",
|
||||
label: t("workflow_node.deploy.form.local_preset_scripts.option.reload_nginx.label"),
|
||||
onClick: () => handlePresetScriptClick("reload_nginx"),
|
||||
},
|
||||
{
|
||||
key: "binding_iis",
|
||||
label: t("workflow_node.deploy.form.local_preset_scripts.option.binding_iis.label"),
|
||||
onClick: () => handlePresetScriptClick("binding_iis"),
|
||||
},
|
||||
{
|
||||
key: "binding_netsh",
|
||||
label: t("workflow_node.deploy.form.local_preset_scripts.option.binding_netsh.label"),
|
||||
onClick: () => handlePresetScriptClick("binding_netsh"),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.deploy.form.local_preset_scripts.button")}
|
||||
<DownOutlinedIcon />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="postCommand" rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.local_post_command.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormLocalFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormQiniuCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.qiniu_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.qiniu_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.qiniu_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.qiniu_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormQiniuCDNFields;
|
211
ui/src/components/workflow/node/DeployNodeFormSSHFields.tsx
Normal file
211
ui/src/components/workflow/node/DeployNodeFormSSHFields.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Dropdown, Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
|
||||
const FORMAT_PEM = "pem" as const;
|
||||
const FORMAT_PFX = "pfx" as const;
|
||||
const FORMAT_JKS = "jks" as const;
|
||||
|
||||
const DeployNodeFormSSHFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
format: z.union([z.literal(FORMAT_PEM), z.literal(FORMAT_PFX), z.literal(FORMAT_JKS)], {
|
||||
message: t("domain.deployment.form.ssh_format.placeholder"),
|
||||
}),
|
||||
certPath: z
|
||||
.string()
|
||||
.min(1, t("workflow_node.deploy.form.ssh_cert_path.tooltip"))
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim(),
|
||||
keyPath: z
|
||||
.string()
|
||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_PEM || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_key_path.tooltip") }),
|
||||
pfxPassword: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_PFX || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_pfx_password.tooltip") }),
|
||||
jksAlias: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_jks_alias.tooltip") }),
|
||||
jksKeypass: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_jks_keypass.tooltip") }),
|
||||
jksStorepass: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine((v) => fieldFormat !== FORMAT_JKS || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_jks_storepass.tooltip") }),
|
||||
preCommand: z
|
||||
.string()
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
||||
.nullish(),
|
||||
postCommand: z
|
||||
.string()
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
||||
.nullish(),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldFormat = Form.useWatch("format", formInst);
|
||||
const fieldCertPath = Form.useWatch("certPath", formInst);
|
||||
|
||||
const handleFormatSelect = (value: string) => {
|
||||
if (fieldFormat === value) return;
|
||||
|
||||
switch (value) {
|
||||
case FORMAT_PEM:
|
||||
{
|
||||
if (/(.pfx|.jks)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.pfx|.jks)$/, ".crt"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FORMAT_PFX:
|
||||
{
|
||||
if (/(.crt|.jks)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.jks)$/, ".pfx"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FORMAT_JKS:
|
||||
{
|
||||
if (/(.crt|.pfx)$/.test(fieldCertPath)) {
|
||||
formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.pfx)$/, ".jks"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePresetScriptClick = (key: string) => {
|
||||
switch (key) {
|
||||
case "reload_nginx":
|
||||
{
|
||||
formInst.setFieldValue("postCommand", "sudo service nginx reload");
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="format" label={t("workflow_node.deploy.form.ssh_format.label")} rules={[formRule]} initialValue={FORMAT_PEM}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.ssh_format.placeholder")} onSelect={handleFormatSelect}>
|
||||
<Select.Option key={FORMAT_PEM} value={FORMAT_PEM}>
|
||||
{t("workflow_node.deploy.form.ssh_format.option.pem.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={FORMAT_PFX} value={FORMAT_PFX}>
|
||||
{t("workflow_node.deploy.form.ssh_format.option.pfx.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={FORMAT_JKS} value={FORMAT_JKS}>
|
||||
{t("workflow_node.deploy.form.ssh_format.option.jks.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="certPath"
|
||||
label={t("workflow_node.deploy.form.ssh_cert_path.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ssh_cert_path.tooltip") }}></span>}
|
||||
initialValue="/etc/ssl/certs/cert.crt"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_cert_path.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_PEM}>
|
||||
<Form.Item
|
||||
name="keyPath"
|
||||
label={t("workflow_node.deploy.form.ssh_key_path.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ssh_key_path.tooltip") }}></span>}
|
||||
initialValue="/etc/ssl/certs/cert.key"
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_key_path.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_PFX}>
|
||||
<Form.Item name="pfxPassword" label={t("workflow_node.deploy.form.ssh_pfx_password.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_pfx_password.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldFormat === FORMAT_JKS}>
|
||||
<Form.Item name="jksAlias" label={t("workflow_node.deploy.form.ssh_jks_alias.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_jks_alias.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="jksKeypass" label={t("workflow_node.deploy.form.ssh_jks_keypass.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_jks_keypass.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="jksStorepass" label={t("workflow_node.deploy.form.ssh_jks_storepass.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.deploy.form.ssh_jks_storepass.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item label={t("workflow_node.deploy.form.ssh_shell_env.label")}>
|
||||
<Select options={[{ value: t("workflow_node.deploy.form.ssh_shell_env.value") }]} value={t("workflow_node.deploy.form.ssh_shell_env.value")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="preCommand" label={t("workflow_node.deploy.form.ssh_pre_command.label")} rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_pre_command.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0">
|
||||
<label className="block mb-1">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex-grow max-w-full truncate">
|
||||
<span>{t("workflow_node.deploy.form.ssh_post_command.label")}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "reload_nginx",
|
||||
label: t("workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label"),
|
||||
onClick: () => handlePresetScriptClick("reload_nginx"),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button size="small" type="link">
|
||||
{t("workflow_node.deploy.form.ssh_preset_scripts.button")}
|
||||
<DownOutlinedIcon />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<Form.Item name="postCommand" rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_post_command.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormSSHFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormTencentCloudCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormTencentCloudCDNFields;
|
@@ -0,0 +1,125 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const RESOURCE_TYPE_SSLDEPLOY = "ssl-deploy" as const;
|
||||
const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const;
|
||||
const RESOURCE_TYPE_LISTENER = "listener" as const;
|
||||
const RESOURCE_TYPE_RULEDOMAIN = "ruledomain" as const;
|
||||
|
||||
const DeployNodeFormTencentCloudCLBFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
resourceType: z.union(
|
||||
[z.literal(RESOURCE_TYPE_SSLDEPLOY), z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER), z.literal(RESOURCE_TYPE_RULEDOMAIN)],
|
||||
{ message: t("workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder") }
|
||||
),
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_clb_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.tencentcloud_clb_region.placeholder"))
|
||||
.trim(),
|
||||
loadbalancerId: z
|
||||
.string()
|
||||
.min(1, t("workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.placeholder"))
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim(),
|
||||
listenerId: z
|
||||
.string()
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 }))
|
||||
.trim()
|
||||
.nullish()
|
||||
.refine(
|
||||
(v) => ![RESOURCE_TYPE_SSLDEPLOY, RESOURCE_TYPE_LISTENER, RESOURCE_TYPE_RULEDOMAIN].includes(fieldResourceType) || !!v?.trim(),
|
||||
t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder")
|
||||
),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_clb_domain.placeholder") })
|
||||
.nullish()
|
||||
.refine((v) => RESOURCE_TYPE_RULEDOMAIN !== fieldResourceType || validDomainName(v!, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const fieldResourceType = Form.useWatch("resourceType", formInst);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="resourceType" label={t("workflow_node.deploy.form.tencentcloud_clb_resource_type.label")} rules={[formRule]}>
|
||||
<Select placeholder={t("workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder")}>
|
||||
<Select.Option key={RESOURCE_TYPE_SSLDEPLOY} value={RESOURCE_TYPE_SSLDEPLOY}>
|
||||
{t("workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ssl_deploy.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LOADBALANCER} value={RESOURCE_TYPE_LOADBALANCER}>
|
||||
{t("workflow_node.deploy.form.tencentcloud_clb_resource_type.option.loadbalancer.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_LISTENER} value={RESOURCE_TYPE_LISTENER}>
|
||||
{t("workflow_node.deploy.form.tencentcloud_clb_resource_type.option.listener.label")}
|
||||
</Select.Option>
|
||||
<Select.Option key={RESOURCE_TYPE_RULEDOMAIN} value={RESOURCE_TYPE_RULEDOMAIN}>
|
||||
{t("workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ruledomain.label")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_clb_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_clb_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_clb_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="loadbalancerId"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Show
|
||||
when={fieldResourceType === RESOURCE_TYPE_SSLDEPLOY || fieldResourceType === RESOURCE_TYPE_LISTENER || fieldResourceType === RESOURCE_TYPE_RULEDOMAIN}
|
||||
>
|
||||
<Form.Item
|
||||
name="listenerId"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_clb_listener_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_SSLDEPLOY}>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_clb_snidomain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Show when={fieldResourceType === RESOURCE_TYPE_RULEDOMAIN}>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_clb_ruledomain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder")} />
|
||||
</Form.Item>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormTencentCloudCLBFields;
|
@@ -0,0 +1,58 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormTencentCloudCOSFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
region: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_cos_region.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.tencentcloud_cos_region.placeholder"))
|
||||
.trim(),
|
||||
bucket: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder"))
|
||||
.trim(),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_cos_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="region"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_cos_region.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_cos_region.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_cos_region.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="bucket"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_cos_bucket.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_cos_bucket.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_cos_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_cos_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_cos_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormTencentCloudCOSFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormTencentCloudECDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_ecdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_ecdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_ecdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_ecdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormTencentCloudECDNFields;
|
@@ -0,0 +1,45 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormTencentCloudEOFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
zoneId: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder") })
|
||||
.nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder"))
|
||||
.trim(),
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.tencentcloud_eo_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="zoneId"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_eo_zone_id.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.tencentcloud_eo_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.tencentcloud_eo_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.tencentcloud_eo_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormTencentCloudEOFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormVolcEngineCDNFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.volcengine_cdn_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.volcengine_cdn_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_cdn_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.volcengine_cdn_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormVolcEngineCDNFields;
|
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { validDomainName } from "@/utils/validators";
|
||||
|
||||
const DeployNodeFormVolcEngineLiveFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
domain: z
|
||||
.string({ message: t("workflow_node.deploy.form.volcengine_live_domain.placeholder") })
|
||||
.refine((v) => validDomainName(v, true), t("common.errmsg.domain_invalid")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="domain"
|
||||
label={t("workflow_node.deploy.form.volcengine_live_domain.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.volcengine_live_domain.tooltip") }}></span>}
|
||||
>
|
||||
<Input placeholder={t("workflow_node.deploy.form.volcengine_live_domain.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormVolcEngineLiveFields;
|
@@ -0,0 +1,50 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Input } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const DeployNodeFormWebhookFields = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
webhookData: z.string({ message: t("workflow_node.deploy.form.webhook_data.placeholder") }).refine((v) => {
|
||||
try {
|
||||
JSON.parse(v);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, t("workflow_node.deploy.form.webhook_data.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const formInst = Form.useFormInstance();
|
||||
|
||||
const handleWebhookDataBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
try {
|
||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||
formInst.setFieldValue("webhookData", json);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name="webhookData"
|
||||
label={t("workflow_node.deploy.form.webhook_data.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.tooltip") }}></span>}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
placeholder={t("workflow_node.deploy.form.webhook_data.placeholder")}
|
||||
onBlur={handleWebhookDataBlur}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployNodeFormWebhookFields;
|
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Button, Form, Input, Select } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { RightOutlined as RightOutlinedIcon } from "@ant-design/icons";
|
||||
import { produce } from "immer";
|
||||
import { z } from "zod";
|
||||
|
||||
import { usePanel } from "../PanelProvider";
|
||||
@@ -37,14 +38,14 @@ const NotifyNodeForm = ({ data }: NotifyNodeFormProps) => {
|
||||
|
||||
const formSchema = z.object({
|
||||
subject: z
|
||||
.string({ message: t("workflow.nodes.notify.form.subject.placeholder") })
|
||||
.min(1, t("workflow.nodes.notify.form.subject.placeholder"))
|
||||
.string({ message: t("workflow_node.notify.form.subject.placeholder") })
|
||||
.min(1, t("workflow_node.notify.form.subject.placeholder"))
|
||||
.max(1000, t("common.errmsg.string_max", { max: 1000 })),
|
||||
message: z
|
||||
.string({ message: t("workflow.nodes.notify.form.message.placeholder") })
|
||||
.min(1, t("workflow.nodes.notify.form.message.placeholder"))
|
||||
.string({ message: t("workflow_node.notify.form.message.placeholder") })
|
||||
.min(1, t("workflow_node.notify.form.message.placeholder"))
|
||||
.max(1000, t("common.errmsg.string_max", { max: 1000 })),
|
||||
channel: z.string({ message: t("workflow.nodes.notify.form.channel.placeholder") }).min(1, t("workflow.nodes.notify.form.channel.placeholder")),
|
||||
channel: z.string({ message: t("workflow_node.notify.form.channel.placeholder") }).min(1, t("workflow_node.notify.form.channel.placeholder")),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const {
|
||||
@@ -54,29 +55,35 @@ const NotifyNodeForm = ({ data }: NotifyNodeFormProps) => {
|
||||
} = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: data?.config ?? initFormModel(),
|
||||
onSubmit: async (values) => {
|
||||
await updateNode({ ...data, config: { ...values }, validated: true });
|
||||
await formInst.validateFields();
|
||||
await updateNode(
|
||||
produce(data, (draft) => {
|
||||
draft.config = { ...values };
|
||||
draft.validated = true;
|
||||
})
|
||||
);
|
||||
hidePanel();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
||||
<Form.Item name="subject" label={t("workflow.nodes.notify.form.subject.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow.nodes.notify.form.subject.placeholder")} />
|
||||
<Form.Item name="subject" label={t("workflow_node.notify.form.subject.label")} rules={[formRule]}>
|
||||
<Input placeholder={t("workflow_node.notify.form.subject.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="message" label={t("workflow.nodes.notify.form.message.label")} rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow.nodes.notify.form.message.placeholder")} />
|
||||
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
|
||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Form.Item className="mb-0">
|
||||
<label className="block mb-1">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex-grow max-w-full truncate">{t("workflow.nodes.notify.form.channel.label")}</div>
|
||||
<div className="flex-grow max-w-full truncate">{t("workflow_node.notify.form.channel.label")}</div>
|
||||
<div className="text-right">
|
||||
<Link className="ant-typography" to="/settings/notification" target="_blank">
|
||||
<Button size="small" type="link">
|
||||
{t("workflow.nodes.notify.form.channel.button")}
|
||||
{t("workflow_node.notify.form.channel.button")}
|
||||
<RightOutlinedIcon className="text-xs" />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -92,7 +99,7 @@ const NotifyNodeForm = ({ data }: NotifyNodeFormProps) => {
|
||||
label: t(notifyChannelsMap.get(k)?.name ?? k),
|
||||
value: k,
|
||||
}))}
|
||||
placeholder={t("workflow.nodes.notify.form.channel.placeholder")}
|
||||
placeholder={t("workflow_node.notify.form.channel.placeholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Alert, Button, Form, Input, Radio } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import dayjs from "dayjs";
|
||||
import { produce } from "immer";
|
||||
import { z } from "zod";
|
||||
|
||||
import { usePanel } from "../PanelProvider";
|
||||
@@ -30,7 +31,7 @@ const StartNodeForm = ({ data }: StartNodeFormProps) => {
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
executionMethod: z.string({ message: t("workflow.nodes.start.form.trigger.placeholder") }).min(1, t("workflow.nodes.start.form.trigger.placeholder")),
|
||||
executionMethod: z.string({ message: t("workflow_node.start.form.trigger.placeholder") }).min(1, t("workflow_node.start.form.trigger.placeholder")),
|
||||
crontab: z.string().nullish(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
@@ -41,7 +42,7 @@ const StartNodeForm = ({ data }: StartNodeFormProps) => {
|
||||
if (!validCronExpression(data.crontab!)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t("workflow.nodes.start.form.trigger_cron.errmsg.invalid"),
|
||||
message: t("workflow_node.start.form.trigger_cron.errmsg.invalid"),
|
||||
path: ["crontab"],
|
||||
});
|
||||
}
|
||||
@@ -54,7 +55,13 @@ const StartNodeForm = ({ data }: StartNodeFormProps) => {
|
||||
} = useAntdForm<z.infer<typeof formSchema>>({
|
||||
initialValues: data?.config ?? initFormModel(),
|
||||
onSubmit: async (values) => {
|
||||
await updateNode({ ...data, config: { ...values }, validated: true });
|
||||
await formInst.validateFields();
|
||||
await updateNode(
|
||||
produce(data, (draft) => {
|
||||
draft.config = { ...values };
|
||||
draft.validated = true;
|
||||
})
|
||||
);
|
||||
hidePanel();
|
||||
},
|
||||
});
|
||||
@@ -82,26 +89,26 @@ const StartNodeForm = ({ data }: StartNodeFormProps) => {
|
||||
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
|
||||
<Form.Item
|
||||
name="executionMethod"
|
||||
label={t("workflow.nodes.start.form.trigger.label")}
|
||||
label={t("workflow_node.start.form.trigger.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.start.form.trigger.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger.tooltip") }}></span>}
|
||||
>
|
||||
<Radio.Group value={triggerType} onChange={(e) => handleTriggerTypeChange(e.target.value)}>
|
||||
<Radio value="auto">{t("workflow.nodes.start.form.trigger.option.auto.label")}</Radio>
|
||||
<Radio value="manual">{t("workflow.nodes.start.form.trigger.option.manual.label")}</Radio>
|
||||
<Radio value="auto">{t("workflow_node.start.form.trigger.option.auto.label")}</Radio>
|
||||
<Radio value="manual">{t("workflow_node.start.form.trigger.option.manual.label")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="crontab"
|
||||
label={t("workflow.nodes.start.form.trigger_cron.label")}
|
||||
label={t("workflow_node.start.form.trigger_cron.label")}
|
||||
hidden={triggerType !== "auto"}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.start.form.trigger_cron.tooltip") }}></span>}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger_cron.tooltip") }}></span>}
|
||||
extra={
|
||||
triggerCronLastExecutions.length > 0 ? (
|
||||
<div>
|
||||
{t("workflow.nodes.start.form.trigger_cron.extra")}
|
||||
{t("workflow_node.start.form.trigger_cron.extra")}
|
||||
<br />
|
||||
{triggerCronLastExecutions.map((date, index) => (
|
||||
<span key={index}>
|
||||
@@ -115,11 +122,11 @@ const StartNodeForm = ({ data }: StartNodeFormProps) => {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Input placeholder={t("workflow.nodes.start.form.trigger_cron.placeholder")} onChange={(e) => handleTriggerCronChange(e.target.value)} />
|
||||
<Input placeholder={t("workflow_node.start.form.trigger_cron.placeholder")} onChange={(e) => handleTriggerCronChange(e.target.value)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item hidden={triggerType !== "auto"}>
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.start.form.trigger_cron_alert.content") }}></span>} />
|
||||
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger_cron_alert.content") }}></span>} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
|
Reference in New Issue
Block a user