diff --git a/ui/src/components/workflow/DeployForm.tsx b/ui/src/components/workflow/DeployForm.tsx
new file mode 100644
index 00000000..3e0b7883
--- /dev/null
+++ b/ui/src/components/workflow/DeployForm.tsx
@@ -0,0 +1,26 @@
+import { WorkflowNode } from "@/domain/workflow";
+import { memo } from "react";
+import DeployToAliyunOSS from "./DeployToAliyunOss";
+
+export type DeployFormProps = {
+ data: WorkflowNode;
+};
+const DeployForm = ({ data }: DeployFormProps) => {
+ return getForm(data);
+};
+
+export default memo(DeployForm);
+
+const getForm = (data: WorkflowNode) => {
+ switch (data.config?.providerType) {
+ case "aliyun-oss":
+ return ;
+ case "tencent":
+ return ;
+ case "aws":
+ return ;
+ default:
+ return <>>;
+ }
+};
+
diff --git a/ui/src/components/workflow/DeployPanelBody.tsx b/ui/src/components/workflow/DeployPanelBody.tsx
index 5b5b2ff3..8fcdb589 100644
--- a/ui/src/components/workflow/DeployPanelBody.tsx
+++ b/ui/src/components/workflow/DeployPanelBody.tsx
@@ -1,39 +1,58 @@
import { accessProviders } from "@/domain/access";
import { WorkflowNode } from "@/domain/workflow";
-import { memo } from "react";
+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";
type DeployPanelBodyProps = {
data: WorkflowNode;
};
const DeployPanelBody = ({ data }: DeployPanelBodyProps) => {
const { t } = useTranslation();
+
+ const [providerType, setProviderType] = useState("");
+
+ useEffect(() => {
+ if (data.config?.providerType) {
+ setProviderType(data.config.providerType as string);
+ }
+ }, [data]);
return (
<>
{/* 默认展示服务商列表 */}
-
选择服务商
- {accessProviders
- .filter((provider) => provider[3] === "apply" || provider[3] === "all")
- .reduce((acc: string[][][], provider, index) => {
- if (index % 2 === 0) {
- acc.push([provider]);
- } else {
- acc[acc.length - 1].push(provider);
- }
- return acc;
- }, [])
- .map((providerRow, rowIndex) => (
-
- {providerRow.map((provider, index) => (
-
-
![{provider[1]}]({provider[2]})
-
{t(provider[1])}
-
- ))}
-
- ))}
+ }>
+ 选择服务商
+ {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) => (
+
+ {providerRow.map((provider, index) => (
+
{
+ setProviderType(provider.type);
+ }}
+ >
+

+
{t(provider.name)}
+
+ ))}
+
+ ))}
+
>
);
};
export default memo(DeployPanelBody);
+
diff --git a/ui/src/components/workflow/DeployToAliyunOss.tsx b/ui/src/components/workflow/DeployToAliyunOss.tsx
new file mode 100644
index 00000000..5ce80b7b
--- /dev/null
+++ b/ui/src/components/workflow/DeployToAliyunOss.tsx
@@ -0,0 +1,180 @@
+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 DeployToAliyunOSS = ({ 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),
+ endpoint: z.string().min(1, {
+ message: t("domain.deployment.form.aliyun_oss_endpoint.placeholder"),
+ }),
+ bucket: z.string().min(1, {
+ message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
+ }),
+ domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
+ message: t("common.errmsg.domain_invalid"),
+ }),
+ });
+
+ let config: WorkflowNodeConfig = {
+ certificate: "",
+ providerType: "aliyun-oss",
+ endpoint: "",
+ bucket: "",
+ domain: "",
+ };
+ if (data) config = data.config ?? config;
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ providerType: config.providerType as string,
+ certificate: config.certificate as string,
+ endpoint: config.endpoint as string,
+ bucket: config.bucket as string,
+ domain: config.domain as string,
+ },
+ });
+
+ const onSubmit = async (config: z.infer) => {
+ updateNode({ ...data, config: { ...config } });
+ hidePanel();
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default DeployToAliyunOSS;
+
diff --git a/ui/src/components/workflow/PanelBody.tsx b/ui/src/components/workflow/PanelBody.tsx
index 6945222f..bba04935 100644
--- a/ui/src/components/workflow/PanelBody.tsx
+++ b/ui/src/components/workflow/PanelBody.tsx
@@ -13,6 +13,8 @@ const PanelBody = ({ data }: PanelBodyProps) => {
return ;
case WorkflowNodeType.Apply:
return ;
+ case WorkflowNodeType.Deploy:
+ return ;
case WorkflowNodeType.Notify:
return ;
case WorkflowNodeType.Branch:
@@ -28,3 +30,4 @@ const PanelBody = ({ data }: PanelBodyProps) => {
};
export default PanelBody;
+
diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts
index c4b5f683..54679df1 100644
--- a/ui/src/domain/domain.ts
+++ b/ui/src/domain/domain.ts
@@ -63,34 +63,39 @@ export type Statistic = {
disabled: number;
};
-type DeployTarget = {
+export type DeployTarget = {
type: string;
provider: string;
name: string;
icon: string;
};
+export const deployTargetList: string[][] = [
+ ["aliyun-oss", "common.provider.aliyun.oss", "/imgs/providers/aliyun.svg"],
+ ["aliyun-cdn", "common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"],
+ ["aliyun-dcdn", "common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"],
+ ["aliyun-clb", "common.provider.aliyun.clb", "/imgs/providers/aliyun.svg"],
+ ["aliyun-alb", "common.provider.aliyun.alb", "/imgs/providers/aliyun.svg"],
+ ["aliyun-nlb", "common.provider.aliyun.nlb", "/imgs/providers/aliyun.svg"],
+ ["tencent-cdn", "common.provider.tencent.cdn", "/imgs/providers/tencent.svg"],
+ ["tencent-ecdn", "common.provider.tencent.ecdn", "/imgs/providers/tencent.svg"],
+ ["tencent-clb", "common.provider.tencent.clb", "/imgs/providers/tencent.svg"],
+ ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"],
+ ["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"],
+ ["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
+ ["huaweicloud-elb", "common.provider.huaweicloud.elb", "/imgs/providers/huaweicloud.svg"],
+ ["baiducloud-cdn", "common.provider.baiducloud.cdn", "/imgs/providers/baiducloud.svg"],
+ ["qiniu-cdn", "common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"],
+ ["dogecloud-cdn", "common.provider.dogecloud.cdn", "/imgs/providers/dogecloud.svg"],
+ ["local", "common.provider.local", "/imgs/providers/local.svg"],
+ ["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
+ ["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
+ ["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
+];
+
export const deployTargetsMap: Map = new Map(
- [
- ["aliyun-oss", "common.provider.aliyun.oss", "/imgs/providers/aliyun.svg"],
- ["aliyun-cdn", "common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"],
- ["aliyun-dcdn", "common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"],
- ["aliyun-clb", "common.provider.aliyun.clb", "/imgs/providers/aliyun.svg"],
- ["aliyun-alb", "common.provider.aliyun.alb", "/imgs/providers/aliyun.svg"],
- ["aliyun-nlb", "common.provider.aliyun.nlb", "/imgs/providers/aliyun.svg"],
- ["tencent-cdn", "common.provider.tencent.cdn", "/imgs/providers/tencent.svg"],
- ["tencent-ecdn", "common.provider.tencent.ecdn", "/imgs/providers/tencent.svg"],
- ["tencent-clb", "common.provider.tencent.clb", "/imgs/providers/tencent.svg"],
- ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"],
- ["tencent-teo", "common.provider.tencent.teo", "/imgs/providers/tencent.svg"],
- ["huaweicloud-cdn", "common.provider.huaweicloud.cdn", "/imgs/providers/huaweicloud.svg"],
- ["huaweicloud-elb", "common.provider.huaweicloud.elb", "/imgs/providers/huaweicloud.svg"],
- ["baiducloud-cdn", "common.provider.baiducloud.cdn", "/imgs/providers/baiducloud.svg"],
- ["qiniu-cdn", "common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"],
- ["dogecloud-cdn", "common.provider.dogecloud.cdn", "/imgs/providers/dogecloud.svg"],
- ["local", "common.provider.local", "/imgs/providers/local.svg"],
- ["ssh", "common.provider.ssh", "/imgs/providers/ssh.svg"],
- ["webhook", "common.provider.webhook", "/imgs/providers/webhook.svg"],
- ["k8s-secret", "common.provider.kubernetes.secret", "/imgs/providers/k8s.svg"],
- ].map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
+ deployTargetList.map(([type, name, icon]) => [type, { type, provider: type.split("-")[0], name, icon }])
);
+
+export const deployTargets = deployTargetList.map(([type, name, icon]) => ({ type, provider: type.split("-")[0], name, icon }));
+
diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts
index 1e3cb2fa..d26c2029 100644
--- a/ui/src/domain/workflow.ts
+++ b/ui/src/domain/workflow.ts
@@ -2,6 +2,7 @@ import { produce } from "immer";
import { nanoid } from "nanoid";
import { accessProviders } from "./access";
import i18n from "@/i18n";
+import { deployTargets } from "./domain";
export enum WorkflowNodeType {
Start = "start",
@@ -25,6 +26,52 @@ export const workflowNodeTypeDefaultName: Map = new Ma
[WorkflowNodeType.Custom, "自定义"],
]);
+export type WorkflowNodeIo = {
+ name: string;
+ type: string;
+ required: boolean;
+ label: string;
+ value?: string;
+ valueSelector?: WorkflowNodeIoValueSelector;
+};
+
+export type WorkflowNodeIoValueSelector = {
+ id: string;
+ name: string;
+};
+
+export const workflowNodeTypeDefaultInput: Map = new Map([
+ [WorkflowNodeType.Apply, []],
+ [
+ WorkflowNodeType.Deploy,
+ [
+ {
+ name: "certificate",
+ type: " certificate",
+ required: true,
+ label: "证书",
+ },
+ ],
+ ],
+ [WorkflowNodeType.Notify, []],
+]);
+
+export const workflowNodeTypeDefaultOutput: Map = new Map([
+ [
+ WorkflowNodeType.Apply,
+ [
+ {
+ name: "certificate",
+ type: "certificate",
+ required: true,
+ label: "证书",
+ },
+ ],
+ ],
+ [WorkflowNodeType.Deploy, []],
+ [WorkflowNodeType.Notify, []],
+]);
+
export type WorkflowNodeConfig = Record;
export type WorkflowNode = {
@@ -32,7 +79,7 @@ export type WorkflowNode = {
name: string;
type: WorkflowNodeType;
- parameters?: WorkflowNodeIo[];
+ input?: WorkflowNodeIo[];
config?: WorkflowNodeConfig;
configured?: boolean;
output?: WorkflowNodeIo[];
@@ -62,6 +109,8 @@ export const newWorkflowNode = (type: WorkflowNodeType, options: NewWorkflowNode
config: {
providerType: options.providerType,
},
+ input: workflowNodeTypeDefaultInput.get(type),
+ output: workflowNodeTypeDefaultOutput.get(type),
};
}
@@ -202,6 +251,46 @@ export const removeBranch = (node: WorkflowNode | WorkflowBranchNode, branchNode
});
};
+// 1 个分支的节点,不应该能获取到相邻分支上节点的输出
+export const getWorkflowOutputBeforeId = (node: WorkflowNode | WorkflowBranchNode, id: string, type: string): WorkflowNode[] => {
+ const output: WorkflowNode[] = [];
+
+ const traverse = (current: WorkflowNode | WorkflowBranchNode, output: WorkflowNode[]) => {
+ if (!current) {
+ return false;
+ }
+ if (current.id === id) {
+ return true;
+ }
+
+ if (!isWorkflowBranchNode(current) && current.output && current.output.some((io) => io.type === type)) {
+ output.push({
+ ...current,
+ output: current.output.filter((io) => io.type === type),
+ });
+ }
+
+ if (isWorkflowBranchNode(current)) {
+ const currentLength = output.length;
+ console.log(currentLength);
+ for (const branch of current.branches) {
+ if (traverse(branch, output)) {
+ return true;
+ }
+ // 如果当前分支没有输出,清空之前的输出
+ if (output.length > currentLength) {
+ output.splice(currentLength);
+ }
+ }
+ }
+
+ return traverse(current.next as WorkflowNode, output);
+ };
+
+ traverse(node, output);
+ return output;
+};
+
export type WorkflowBranchNode = {
id: string;
name: string;
@@ -210,20 +299,6 @@ export type WorkflowBranchNode = {
next?: WorkflowNode | WorkflowBranchNode;
};
-export type WorkflowNodeIo = {
- name: string;
- type: string;
- required: boolean;
- description?: string;
- value?: string;
- valueSelector?: WorkflowNodeIoValueSelector;
-};
-
-export type WorkflowNodeIoValueSelector = {
- id: string;
- name: string;
-};
-
type WorkflowwNodeDropdwonItem = {
type: WorkflowNodeType;
providerType?: string;
@@ -243,39 +318,18 @@ export type WorkflowwNodeDropdwonItemIcon = {
name: string;
};
-const workflowNodeDropdownApplyList: WorkflowwNodeDropdwonItem[] = accessProviders
- .filter((item) => {
- return item[3] === "apply" || item[3] === "all";
- })
- .map((item) => {
- return {
- type: WorkflowNodeType.Apply,
- providerType: item[0],
- name: i18n.t(item[1]),
- leaf: true,
- icon: {
- type: WorkflowwNodeDropdwonItemIconType.Provider,
- name: item[2],
- },
- };
- });
-
-const workflowNodeDropdownDeployList: WorkflowwNodeDropdwonItem[] = accessProviders
- .filter((item) => {
- return item[3] === "deploy" || item[3] === "all";
- })
- .map((item) => {
- return {
- type: WorkflowNodeType.Apply,
- providerType: item[0],
- name: i18n.t(item[1]),
- leaf: true,
- icon: {
- type: WorkflowwNodeDropdwonItemIconType.Provider,
- name: item[2],
- },
- };
- });
+const workflowNodeDropdownDeployList: WorkflowwNodeDropdwonItem[] = deployTargets.map((item) => {
+ return {
+ type: WorkflowNodeType.Apply,
+ providerType: item.type,
+ name: i18n.t(item.name),
+ leaf: true,
+ icon: {
+ type: WorkflowwNodeDropdwonItemIconType.Provider,
+ name: item.icon,
+ },
+ };
+});
export const workflowNodeDropdownList: WorkflowwNodeDropdwonItem[] = [
{
@@ -285,7 +339,7 @@ export const workflowNodeDropdownList: WorkflowwNodeDropdwonItem[] = [
type: WorkflowwNodeDropdwonItemIconType.Icon,
name: "NotebookPen",
},
- children: workflowNodeDropdownApplyList,
+ leaf: true,
},
{
type: WorkflowNodeType.Deploy,
@@ -315,3 +369,4 @@ export const workflowNodeDropdownList: WorkflowwNodeDropdwonItem[] = [
},
},
];
+
diff --git a/ui/src/providers/workflow/index.ts b/ui/src/providers/workflow/index.ts
index 93ada241..d644799d 100644
--- a/ui/src/providers/workflow/index.ts
+++ b/ui/src/providers/workflow/index.ts
@@ -1,4 +1,14 @@
-import { addBranch, addNode, removeBranch, removeNode, updateNode, WorkflowBranchNode, WorkflowNode, WorkflowNodeType } from "@/domain/workflow";
+import {
+ addBranch,
+ addNode,
+ getWorkflowOutputBeforeId,
+ removeBranch,
+ removeNode,
+ updateNode,
+ WorkflowBranchNode,
+ WorkflowNode,
+ WorkflowNodeType,
+} from "@/domain/workflow";
import { create } from "zustand";
export type WorkflowState = {
@@ -8,16 +18,17 @@ export type WorkflowState = {
addBranch: (branchId: string) => void;
removeNode: (nodeId: string) => void;
removeBranch: (branchId: string, index: number) => void;
+ getWorkflowOuptutBeforeId: (id: string, type: string) => WorkflowNode[];
};
-export const useWorkflowStore = create((set) => ({
+export const useWorkflowStore = create((set, get) => ({
root: {
id: "1",
name: "开始",
type: WorkflowNodeType.Start,
next: {
id: "2",
- name: "结束",
+ name: "分支",
type: WorkflowNodeType.Branch,
branches: [
{
@@ -81,4 +92,9 @@ export const useWorkflowStore = create((set) => ({
root: newRoot,
};
}),
+
+ getWorkflowOuptutBeforeId: (id: string, type: string) => {
+ return getWorkflowOutputBeforeId(get().root, id, type);
+ },
}));
+