diff --git a/ui/src/components/workflow/AddNode.tsx b/ui/src/components/workflow/AddNode.tsx index f4623f1e..f577e693 100644 --- a/ui/src/components/workflow/AddNode.tsx +++ b/ui/src/components/workflow/AddNode.tsx @@ -36,7 +36,7 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => { }; return ( -
+
diff --git a/ui/src/components/workflow/BranchNode.tsx b/ui/src/components/workflow/BranchNode.tsx index ca617bc2..8bbfd0ce 100644 --- a/ui/src/components/workflow/BranchNode.tsx +++ b/ui/src/components/workflow/BranchNode.tsx @@ -25,7 +25,7 @@ const BranchNode = memo(({ data }: BrandNodeProps) => { return ( <> -
+
+
+ + + + ); +}; + +export default DeployToAliyunALB; diff --git a/ui/src/components/workflow/DeployToAliyunCDN.tsx b/ui/src/components/workflow/DeployToAliyunCDN.tsx new file mode 100644 index 00000000..394fb173 --- /dev/null +++ b/ui/src/components/workflow/DeployToAliyunCDN.tsx @@ -0,0 +1,141 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToAliyunCDN = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z.object({ + providerType: z.string(), + certificate: z.string().min(1), + domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { + message: t("common.errmsg.domain_invalid"), + }), + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "aliyun-cdn", + + domain: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + domain: config.domain as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.domain.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToAliyunCDN; diff --git a/ui/src/components/workflow/DeployToAliyunCLB.tsx b/ui/src/components/workflow/DeployToAliyunCLB.tsx new file mode 100644 index 00000000..b37ad2ac --- /dev/null +++ b/ui/src/components/workflow/DeployToAliyunCLB.tsx @@ -0,0 +1,217 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { DeployFormProps } from "./DeployForm"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { Button } from "../ui/button"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); + +const DeployToAliyunCLB = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z + .object({ + providerType: z.string(), + certificate: z.string().min(1), + region: z.string().min(1, t("domain.deployment.form.aliyun_clb_region.placeholder")), + resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], { + message: t("domain.deployment.form.aliyun_clb_resource_type.placeholder"), + }), + loadbalancerId: z.string().optional(), + listenerPort: z.string().optional(), + }) + .refine((data) => (data.resourceType === "loadbalancer" || data.resourceType === "listener" ? !!data.loadbalancerId?.trim() : true), { + message: t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }) + .refine((data) => (data.resourceType === "listener" ? +data.listenerPort! > 0 && +data.listenerPort! < 65535 : true), { + message: t("domain.deployment.form.aliyun_clb_listener_port.placeholder"), + path: ["listenerPort"], + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "aliyun-clb", + region: "", + resourceType: "", + loadbalancerId: "", + listenerPort: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + region: config.region as string, + resourceType: config.resourceType as "loadbalancer" | "listener", + loadbalancerId: config.loadbalancerId as string, + listenerPort: config.listenerPort as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + ( + + {t("domain.deployment.form.aliyun_clb_region.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_clb_resource_type.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_clb_loadbalancer_id.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_clb_listener_port.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToAliyunCLB; diff --git a/ui/src/components/workflow/DeployToAliyunNLB.tsx b/ui/src/components/workflow/DeployToAliyunNLB.tsx new file mode 100644 index 00000000..f1389475 --- /dev/null +++ b/ui/src/components/workflow/DeployToAliyunNLB.tsx @@ -0,0 +1,217 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { DeployFormProps } from "./DeployForm"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { Button } from "../ui/button"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); + +const DeployToAliyunNLB = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z + .object({ + providerType: z.string(), + certificate: z.string().min(1), + region: z.string().min(1, t("domain.deployment.form.aliyun_nlb_region.placeholder")), + resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], { + message: t("domain.deployment.form.aliyun_nlb_resource_type.placeholder"), + }), + loadbalancerId: z.string().optional(), + listenerId: z.string().optional(), + }) + .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), { + message: t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }) + .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), { + message: t("domain.deployment.form.aliyun_nlb_listener_id.placeholder"), + path: ["listenerId"], + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "aliyun-nlb", + region: "", + resourceType: "", + loadbalancerId: "", + listenerId: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + region: config.region as string, + resourceType: config.resourceType as "loadbalancer" | "listener", + loadbalancerId: config.loadbalancerId as string, + listenerId: config.listenerId as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + ( + + {t("domain.deployment.form.aliyun_nlb_region.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_nlb_resource_type.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_nlb_loadbalancer_id.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.aliyun_nlb_listener_id.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToAliyunNLB; diff --git a/ui/src/components/workflow/DeployToAliyunOss.tsx b/ui/src/components/workflow/DeployToAliyunOss.tsx index 130a98b8..854802a7 100644 --- a/ui/src/components/workflow/DeployToAliyunOss.tsx +++ b/ui/src/components/workflow/DeployToAliyunOss.tsx @@ -177,4 +177,3 @@ const DeployToAliyunOSS = ({ data }: DeployFormProps) => { }; export default DeployToAliyunOSS; - diff --git a/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx b/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx new file mode 100644 index 00000000..a54f8a9f --- /dev/null +++ b/ui/src/components/workflow/DeployToBaiduCloudCDN.tsx @@ -0,0 +1,141 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z.object({ + providerType: z.string(), + certificate: z.string().min(1), + domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { + message: t("common.errmsg.domain_invalid"), + }), + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "baiducloud-cdn", + + domain: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + domain: config.domain as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.domain.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToBaiduCloudCDN; diff --git a/ui/src/components/workflow/DeployToDogeCloudCDN.tsx b/ui/src/components/workflow/DeployToDogeCloudCDN.tsx new file mode 100644 index 00000000..3cc2e608 --- /dev/null +++ b/ui/src/components/workflow/DeployToDogeCloudCDN.tsx @@ -0,0 +1,141 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToDogeCloudCDN = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z.object({ + providerType: z.string(), + certificate: z.string().min(1), + domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { + message: t("common.errmsg.domain_invalid"), + }), + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "dogecloud-cdn", + + domain: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + domain: config.domain as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.domain.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToDogeCloudCDN; diff --git a/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx b/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx new file mode 100644 index 00000000..b30e79f5 --- /dev/null +++ b/ui/src/components/workflow/DeployToHuaweiCloudCDN.tsx @@ -0,0 +1,160 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToHuaweiCloudCDN = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z.object({ + providerType: z.string(), + certificate: z.string().min(1), + region: z.string().min(1, { + message: t("domain.deployment.form.huaweicloud_cdn_region.placeholder"), + }), + domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { + message: t("common.errmsg.domain_invalid"), + }), + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "huaweicloud-cdn", + region: "", + domain: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + region: config.region as string, + domain: config.domain as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.huaweicloud_cdn_region.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.domain.label")} + + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToHuaweiCloudCDN; diff --git a/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx b/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx new file mode 100644 index 00000000..39e6637f --- /dev/null +++ b/ui/src/components/workflow/DeployToHuaweiCloudELB.tsx @@ -0,0 +1,252 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToHuaweiCloudELB = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [resourceType, setResourceType] = useState<"certificate" | "loadbalancer" | "listener">(); + + useEffect(() => { + setResourceType(data.config?.resourceType as "certificate" | "loadbalancer" | "listener"); + }, [data]); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z + .object({ + providerType: z.string(), + certificate: z.string().min(1), + region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")), + resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], { + message: t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder"), + }), + certificateId: z.string().optional(), + loadbalancerId: z.string().optional(), + listenerId: z.string().optional(), + }) + .refine((data) => (data.resourceType === "certificate" ? !!data.certificateId?.trim() : true), { + message: t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder"), + path: ["certificateId"], + }) + .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), { + message: t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }) + .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), { + message: t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder"), + path: ["listenerId"], + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "huaweicloud-elb", + resouceType: "", + certificateId: "", + loadbalancerId: "", + listenerId: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + region: config.region as string, + resourceType: config.resourceType as "certificate" | "loadbalancer" | "listener", + certificateId: config.certificateId as string, + loadbalancerId: config.loadbalancerId as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.huaweicloud_cdn_region.label")} + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.huaweicloud_elb_resource_type.label")} + + + + + + + )} + /> + + {resourceType === "certificate" && ( + ( + + {t("domain.deployment.form.huaweicloud_elb_certificate_id.label")} + + + + + + + )} + /> + )} + + {resourceType === "loadbalancer" && ( + ( + + {t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")} + + + + + + + )} + /> + )} + + {resourceType === "listener" && ( + ( + + {t("domain.deployment.form.huaweicloud_elb_listener_id.label")} + + + + + + + )} + /> + )} + +
+ +
+ + + + ); +}; + +export default DeployToHuaweiCloudELB; diff --git a/ui/src/components/workflow/DeployToKubernetesSecret.tsx b/ui/src/components/workflow/DeployToKubernetesSecret.tsx new file mode 100644 index 00000000..40360778 --- /dev/null +++ b/ui/src/components/workflow/DeployToKubernetesSecret.tsx @@ -0,0 +1,196 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; + +import { Input } from "@/components/ui/input"; +import { DeployFormProps } from "./DeployForm"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { Button } from "../ui/button"; + +import { useEffect, useState } from "react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { SelectLabel } from "@radix-ui/react-select"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); +const DeployToKubernetesSecret = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const formSchema = z.object({ + providerType: z.string(), + certificate: z.string().min(1), + namespace: z.string().min(1, { + message: t("domain.deployment.form.k8s_namespace.placeholder"), + }), + secretName: z.string().min(1, { + message: t("domain.deployment.form.k8s_secret_name.placeholder"), + }), + secretDataKeyForCrt: z.string().min(1, { + message: t("domain.deployment.form.k8s_secret_data_key_for_crt.placeholder"), + }), + secretDataKeyForKey: z.string().min(1, { + message: t("domain.deployment.form.k8s_secret_data_key_for_key.placeholder"), + }), + }); + + let config: WorkflowNodeConfig = { + certificate: "", + providerType: "k8s-secret", + namespace: "", + secretName: "", + secretDataKeyForCrt: "", + secretDataKeyForKey: "", + }; + if (data) config = data.config ?? config; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: config.providerType as string, + certificate: config.certificate as string, + namespace: config.namespace as string, + secretName: config.secretName as string, + secretDataKeyForCrt: config.secretDataKeyForCrt as string, + secretDataKeyForKey: config.secretDataKeyForKey as string, + }, + }); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config: { ...config } }); + hidePanel(); + }; + + return ( + <> +
+ { + e.stopPropagation(); + form.handleSubmit(onSubmit)(e); + }} + className="space-y-8" + > + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.k8s_namespace.label")} + + + + + + )} + /> + + ( + + {t("domain.deployment.form.k8s_secret_name.label")} + + + + + + )} + /> + + ( + + {t("domain.deployment.form.k8s_secret_data_key_for_crt.label")} + + + + + + )} + /> + + ( + + {t("domain.deployment.form.k8s_secret_data_key_for_key.label")} + + + + + + )} + /> + +
+ +
+ + + + ); +}; + +export default DeployToKubernetesSecret; diff --git a/ui/src/components/workflow/DeployToLocal.tsx b/ui/src/components/workflow/DeployToLocal.tsx new file mode 100644 index 00000000..b4f369c6 --- /dev/null +++ b/ui/src/components/workflow/DeployToLocal.tsx @@ -0,0 +1,432 @@ +import { useTranslation } from "react-i18next"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select"; +import { Button } from "../ui/button"; +import { DeployFormProps } from "./DeployForm"; +import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; +import { useShallow } from "zustand/shallow"; +import { usePanel } from "./PanelProvider"; +import { useEffect, useState } from "react"; +import i18n from "@/i18n"; +import { WorkflowNode } from "@/domain/workflow"; +import { Textarea } from "../ui/textarea"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu"; + +const selectState = (state: WorkflowState) => ({ + updateNode: state.updateNode, + getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId, +}); + +const t = i18n.t; + +const formSchema = z + .object({ + providerType: z.string(), + certificate: z.string().min(1), + format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], { + message: t("domain.deployment.form.file_format.placeholder"), + }), + certPath: z + .string() + .min(1, t("domain.deployment.form.file_cert_path.placeholder")) + .max(255, t("common.errmsg.string_max", { max: 255 })), + keyPath: z + .string() + .min(0, t("domain.deployment.form.file_key_path.placeholder")) + .max(255, t("common.errmsg.string_max", { max: 255 })), + pfxPassword: z.string().optional(), + jksAlias: z.string().optional(), + jksKeypass: z.string().optional(), + jksStorepass: z.string().optional(), + preCommand: z.string().optional(), + command: z.string().optional(), + shell: z.union([z.literal("sh"), z.literal("cmd"), z.literal("powershell")], { + message: t("domain.deployment.form.shell.placeholder"), + }), + }) + .refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), { + message: t("domain.deployment.form.file_key_path.placeholder"), + path: ["keyPath"], + }) + .refine((data) => (data.format === "pfx" ? !!data.pfxPassword?.trim() : true), { + message: t("domain.deployment.form.file_pfx_password.placeholder"), + path: ["pfxPassword"], + }) + .refine((data) => (data.format === "jks" ? !!data.jksAlias?.trim() : true), { + message: t("domain.deployment.form.file_jks_alias.placeholder"), + path: ["jksAlias"], + }) + .refine((data) => (data.format === "jks" ? !!data.jksKeypass?.trim() : true), { + message: t("domain.deployment.form.file_jks_keypass.placeholder"), + path: ["jksKeypass"], + }) + .refine((data) => (data.format === "jks" ? !!data.jksStorepass?.trim() : true), { + message: t("domain.deployment.form.file_jks_storepass.placeholder"), + path: ["jksStorepass"], + }); + +const DeployToLocal = ({ data }: DeployFormProps) => { + const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState)); + const { hidePanel } = usePanel(); + const { t } = useTranslation(); + + const [beforeOutput, setBeforeOutput] = useState([]); + + useEffect(() => { + const rs = getWorkflowOuptutBeforeId(data.id, "certificate"); + console.log(rs); + setBeforeOutput(rs); + }, [data]); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerType: "local", + certificate: data.config?.certificate as string, + format: (data.config?.format as "pem" | "pfx" | "jks") || "pem", + certPath: (data.config?.certPath as string) || "/etc/ssl/certs/cert.crt", + keyPath: (data.config?.keyPath as string) || "/etc/ssl/private/cert.key", + pfxPassword: (data.config?.pfxPassword as string) || "", + jksAlias: (data.config?.jksAlias as string) || "", + jksKeypass: (data.config?.jksKeypass as string) || "", + jksStorepass: (data.config?.jksStorepass as string) || "", + preCommand: (data.config?.preCommand as string) || "", + command: (data.config?.command as string) || "service nginx reload", + shell: (data.config?.shell as "sh" | "cmd" | "powershell") || "sh", + }, + }); + + const format = form.watch("format"); + const certPath = form.watch("certPath"); + + useEffect(() => { + if (format === "pem" && /(.pfx|.jks)$/.test(certPath)) { + form.setValue("certPath", certPath.replace(/(.pfx|.jks)$/, ".crt")); + } else if (format === "pfx" && /(.crt|.jks)$/.test(certPath)) { + form.setValue("certPath", certPath.replace(/(.crt|.jks)$/, ".pfx")); + } else if (format === "jks" && /(.crt|.pfx)$/.test(certPath)) { + form.setValue("certPath", certPath.replace(/(.crt|.pfx)$/, ".jks")); + } + }, [format]); + + const onSubmit = async (config: z.infer) => { + updateNode({ ...data, config }); + hidePanel(); + }; + + const handleUsePresetScript = (key: string) => { + switch (key) { + case "reload_nginx": + { + form.setValue("shell", "sh"); + form.setValue("command", "sudo service nginx reload"); + } + break; + + case "binding_iis": + { + form.setValue("shell", "powershell"); + form.setValue( + "command", + `# 请将以下变量替换为实际值 +$pfxPath = "" # PFX 文件路径 +$pfxPassword = "" # PFX 密码 +$siteName = "" # IIS 网站名称 +$domain = "" # 域名 +$ipaddr = "" # 绑定 IP,“*”表示所有 IP 绑定 +$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": + { + form.setValue("shell", "powershell"); + form.setValue( + "command", + `# 请将以下变量替换为实际值 +$pfxPath = "" # PFX 文件路径 +$pfxPassword = "" # PFX 密码 +$ipaddr = "" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名。 +$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 ( +
+ + ( + + 证书 + + + + + + + )} + /> + + ( + + {t("domain.deployment.form.file_format.label")} + + + + )} + /> + + ( + + {t("domain.deployment.form.file_cert_path.label")} + + + + + + )} + /> + + ( + + 密钥路径 + + + + + + )} + /> + + {format === "pfx" && ( + ( + + PFX 密码 + + + + + + )} + /> + )} + + {format === "jks" && ( + <> + ( + + JKS 别名 + + + + + + )} + /> + + ( + + JKS Keypass + + + + + + )} + /> + + ( + + JKS Storepass + + + + + + )} + /> + + )} + + ( + + {t("domain.deployment.form.shell.label")} + + + + )} + /> + + ( + + {t("domain.deployment.form.shell_pre_command.label")} + +