mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-15 17:09:51 +00:00
feat(ui): WorkflowNew page
This commit is contained in:
parent
b6dd2248c8
commit
c6a8f923e4
@ -37,8 +37,8 @@ type WorkflowNode struct {
|
||||
Name string `json:"name"`
|
||||
Next *WorkflowNode `json:"next"`
|
||||
Config map[string]any `json:"config"`
|
||||
Input []WorkflowNodeIo `json:"input"`
|
||||
Output []WorkflowNodeIo `json:"output"`
|
||||
Input []WorkflowNodeIO `json:"input"`
|
||||
Output []WorkflowNodeIO `json:"output"`
|
||||
|
||||
Validated bool `json:"validated"`
|
||||
Type string `json:"type"`
|
||||
@ -76,16 +76,16 @@ func (n *WorkflowNode) GetConfigInt64(key string) int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type WorkflowNodeIo struct {
|
||||
type WorkflowNodeIO struct {
|
||||
Label string `json:"label"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Value any `json:"value"`
|
||||
ValueSelector WorkflowNodeIoValueSelector `json:"valueSelector"`
|
||||
ValueSelector WorkflowNodeIOValueSelector `json:"valueSelector"`
|
||||
}
|
||||
|
||||
type WorkflowNodeIoValueSelector struct {
|
||||
type WorkflowNodeIOValueSelector struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ type WorkflowOutput struct {
|
||||
Workflow string `json:"workflow"`
|
||||
NodeId string `json:"nodeId"`
|
||||
Node *WorkflowNode `json:"node"`
|
||||
Output []WorkflowNodeIo `json:"output"`
|
||||
Output []WorkflowNodeIO `json:"output"`
|
||||
Succeed bool `json:"succeed"`
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func (w *WorkflowOutputRepository) Get(ctx context.Context, nodeId string) (*dom
|
||||
return nil, errors.New("failed to unmarshal node")
|
||||
}
|
||||
|
||||
output := make([]domain.WorkflowNodeIo, 0)
|
||||
output := make([]domain.WorkflowNodeIO, 0)
|
||||
if err := record.UnmarshalJSONField("output", &output); err != nil {
|
||||
return nil, errors.New("failed to unmarshal output")
|
||||
}
|
||||
|
BIN
ui/public/imgs/workflow/tpl-blank.png
Normal file
BIN
ui/public/imgs/workflow/tpl-blank.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
BIN
ui/public/imgs/workflow/tpl-standard.png
Normal file
BIN
ui/public/imgs/workflow/tpl-standard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BookOutlined as BookOutlinedIcon } from "@ant-design/icons";
|
||||
import { ReadOutlined as ReadOutlinedIcon } from "@ant-design/icons";
|
||||
import { Divider, Space, Typography } from "antd";
|
||||
|
||||
import { version } from "@/domain/version";
|
||||
@ -16,7 +16,7 @@ const Version = ({ className, style }: VersionProps) => {
|
||||
<Space className={className} style={style} size={4}>
|
||||
<Typography.Link type="secondary" href="https://docs.certimate.me" target="_blank">
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<BookOutlinedIcon />
|
||||
<ReadOutlinedIcon />
|
||||
<span>{t("common.menu.document")}</span>
|
||||
</div>
|
||||
</Typography.Link>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons";
|
||||
import { Dropdown } from "antd";
|
||||
|
||||
import { newWorkflowNode, workflowNodeDropdownList, type WorkflowNodeType } from "@/domain/workflow";
|
||||
import { type WorkflowNodeType, newNode, workflowNodeDropdownList } from "@/domain/workflow";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
|
||||
@ -12,7 +12,7 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => {
|
||||
const { addNode } = useWorkflowStore(useZustandShallowSelector(["addNode"]));
|
||||
|
||||
const handleTypeSelected = (type: WorkflowNodeType, provider?: string) => {
|
||||
const node = newWorkflowNode(type, {
|
||||
const node = newNode(type, {
|
||||
providerType: provider,
|
||||
});
|
||||
|
||||
|
@ -1,11 +1,18 @@
|
||||
import { CloudUpload, GitFork, Megaphone, NotebookPen } from "lucide-react";
|
||||
import {
|
||||
CloudUploadOutlined as CloudUploadOutlinedIcon,
|
||||
SendOutlined as SendOutlinedIcon,
|
||||
SisternodeOutlined as SisternodeOutlinedIcon,
|
||||
SolutionOutlined as SolutionOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { Avatar } from "antd";
|
||||
|
||||
import { type WorkflowNodeDropdwonItemIcon, WorkflowNodeDropdwonItemIconType } from "@/domain/workflow";
|
||||
|
||||
const icons = new Map([
|
||||
["NotebookPen", <NotebookPen size={16} />],
|
||||
["CloudUpload", <CloudUpload size={16} />],
|
||||
["GitFork", <GitFork size={16} />],
|
||||
["Megaphone", <Megaphone size={16} />],
|
||||
["ApplyNodeIcon", <SolutionOutlinedIcon />],
|
||||
["DeployNodeIcon", <CloudUploadOutlinedIcon />],
|
||||
["BranchNodeIcon", <SisternodeOutlinedIcon />],
|
||||
["NotifyNodeIcon", <SendOutlinedIcon />],
|
||||
]);
|
||||
|
||||
const DropdownMenuItemIcon = ({ type, name }: WorkflowNodeDropdwonItemIcon) => {
|
||||
@ -13,7 +20,7 @@ const DropdownMenuItemIcon = ({ type, name }: WorkflowNodeDropdwonItemIcon) => {
|
||||
if (type === WorkflowNodeDropdwonItemIconType.Icon) {
|
||||
return icons.get(name);
|
||||
} else {
|
||||
return <img src={name} className="inline-block size-4" />;
|
||||
return <Avatar src={name} size="small" />;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ export interface CertificateModel extends BaseModel {
|
||||
certUrl: string;
|
||||
certStableUrl: string;
|
||||
output: string;
|
||||
expireAt: string;
|
||||
expireAt: ISO8601String;
|
||||
workflow: string;
|
||||
nodeId: string;
|
||||
expand: {
|
||||
|
@ -1,16 +1,10 @@
|
||||
import dayjs from "dayjs";
|
||||
import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import i18n from "@/i18n";
|
||||
import { deployProvidersMap } from "./provider";
|
||||
|
||||
export type WorkflowOutput = {
|
||||
time: string;
|
||||
title: string;
|
||||
content: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export interface WorkflowModel extends BaseModel {
|
||||
name: string;
|
||||
description?: string;
|
||||
@ -22,6 +16,7 @@ export interface WorkflowModel extends BaseModel {
|
||||
hasDraft?: boolean;
|
||||
}
|
||||
|
||||
// #region Node
|
||||
export enum WorkflowNodeType {
|
||||
Start = "start",
|
||||
End = "end",
|
||||
@ -33,7 +28,7 @@ export enum WorkflowNodeType {
|
||||
Custom = "custom",
|
||||
}
|
||||
|
||||
export const workflowNodeTypeDefaultName: Map<WorkflowNodeType, string> = new Map([
|
||||
const workflowNodeTypeDefaultNames: Map<WorkflowNodeType, string> = new Map([
|
||||
[WorkflowNodeType.Start, i18n.t("workflow_node.start.label")],
|
||||
[WorkflowNodeType.End, i18n.t("workflow_node.end.label")],
|
||||
[WorkflowNodeType.Branch, i18n.t("workflow_node.branch.label")],
|
||||
@ -44,21 +39,7 @@ export const workflowNodeTypeDefaultName: Map<WorkflowNodeType, string> = new Ma
|
||||
[WorkflowNodeType.Custom, i18n.t("workflow_node.custom.title")],
|
||||
]);
|
||||
|
||||
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<WorkflowNodeType, WorkflowNodeIo[]> = new Map([
|
||||
const workflowNodeTypeDefaultInputs: Map<WorkflowNodeType, WorkflowNodeIO[]> = new Map([
|
||||
[WorkflowNodeType.Apply, []],
|
||||
[
|
||||
WorkflowNodeType.Deploy,
|
||||
@ -74,7 +55,7 @@ export const workflowNodeTypeDefaultInput: Map<WorkflowNodeType, WorkflowNodeIo[
|
||||
[WorkflowNodeType.Notify, []],
|
||||
]);
|
||||
|
||||
export const workflowNodeTypeDefaultOutput: Map<WorkflowNodeType, WorkflowNodeIo[]> = new Map([
|
||||
const workflowNodeTypeDefaultOutputs: Map<WorkflowNodeType, WorkflowNodeIO[]> = new Map([
|
||||
[
|
||||
WorkflowNodeType.Apply,
|
||||
[
|
||||
@ -90,88 +71,122 @@ export const workflowNodeTypeDefaultOutput: Map<WorkflowNodeType, WorkflowNodeIo
|
||||
[WorkflowNodeType.Notify, []],
|
||||
]);
|
||||
|
||||
export type WorkflowNodeConfig = Record<string, unknown>;
|
||||
|
||||
export type WorkflowNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: WorkflowNodeType;
|
||||
validated?: boolean;
|
||||
|
||||
input?: WorkflowNodeIo[];
|
||||
config?: WorkflowNodeConfig;
|
||||
output?: WorkflowNodeIo[];
|
||||
config?: Record<string, unknown>;
|
||||
input?: WorkflowNodeIO[];
|
||||
output?: WorkflowNodeIO[];
|
||||
|
||||
next?: WorkflowNode | WorkflowBranchNode;
|
||||
branches?: WorkflowNode[];
|
||||
|
||||
validated?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type WorkflowBranchNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: WorkflowNodeType.Branch;
|
||||
|
||||
branches: WorkflowNode[];
|
||||
|
||||
next?: WorkflowNode | WorkflowBranchNode;
|
||||
};
|
||||
|
||||
type NewWorkflowNodeOptions = {
|
||||
export type WorkflowNodeIO = {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
label: string;
|
||||
value?: string;
|
||||
valueSelector?: WorkflowNodeIOValueSelector;
|
||||
};
|
||||
|
||||
export type WorkflowNodeIOValueSelector = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
// #endregion
|
||||
|
||||
type InitWorkflowOptions = {
|
||||
template?: "standard";
|
||||
};
|
||||
|
||||
export const initWorkflow = (options: InitWorkflowOptions = {}): WorkflowModel => {
|
||||
const root = newNode(WorkflowNodeType.Start, {}) as WorkflowNode;
|
||||
root.config = { executionMethod: "manual" };
|
||||
|
||||
if (options.template === "standard") {
|
||||
let temp = root;
|
||||
temp.next = newNode(WorkflowNodeType.Apply, {});
|
||||
|
||||
temp = temp.next;
|
||||
temp.next = newNode(WorkflowNodeType.Deploy, {});
|
||||
|
||||
temp = temp.next;
|
||||
temp.next = newNode(WorkflowNodeType.Notify, {});
|
||||
}
|
||||
|
||||
return {
|
||||
id: null!,
|
||||
name: `MyWorkflow-${dayjs().format("YYYYMMDDHHmmss")}`,
|
||||
type: root.config!.executionMethod as string,
|
||||
crontab: root.config!.crontab as string,
|
||||
enabled: false,
|
||||
draft: root,
|
||||
hasDraft: true,
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
type NewNodeOptions = {
|
||||
branchIndex?: number;
|
||||
providerType?: string;
|
||||
};
|
||||
|
||||
export const initWorkflow = (): WorkflowModel => {
|
||||
// 开始节点
|
||||
const rs = newWorkflowNode(WorkflowNodeType.Start, {});
|
||||
let root = rs;
|
||||
export const newNode = (nodeType: WorkflowNodeType, options: NewNodeOptions): WorkflowNode | WorkflowBranchNode => {
|
||||
const nodeTypeName = workflowNodeTypeDefaultNames.get(nodeType) || "";
|
||||
const nodeName = options.branchIndex != null ? `${nodeTypeName} ${options.branchIndex + 1}` : nodeTypeName;
|
||||
|
||||
// 申请节点
|
||||
root.next = newWorkflowNode(WorkflowNodeType.Apply, {});
|
||||
root = root.next;
|
||||
|
||||
// 部署节点
|
||||
root.next = newWorkflowNode(WorkflowNodeType.Deploy, {});
|
||||
root = root.next;
|
||||
|
||||
// 通知节点
|
||||
root.next = newWorkflowNode(WorkflowNodeType.Notify, {});
|
||||
|
||||
return {
|
||||
id: "",
|
||||
name: i18n.t("workflow.props.name.default"),
|
||||
type: "auto",
|
||||
crontab: "0 0 * * *",
|
||||
enabled: false,
|
||||
draft: rs,
|
||||
created: new Date().toUTCString(),
|
||||
updated: new Date().toUTCString(),
|
||||
};
|
||||
const node: WorkflowNode | WorkflowBranchNode = {
|
||||
id: nanoid(),
|
||||
name: nodeName,
|
||||
type: nodeType,
|
||||
};
|
||||
|
||||
export const newWorkflowNode = (type: WorkflowNodeType, options: NewWorkflowNodeOptions): WorkflowNode | WorkflowBranchNode => {
|
||||
const id = nanoid();
|
||||
const typeName = workflowNodeTypeDefaultName.get(type) || "";
|
||||
const name = options.branchIndex !== undefined ? `${typeName} ${options.branchIndex + 1}` : typeName;
|
||||
|
||||
let rs: WorkflowNode | WorkflowBranchNode = {
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
};
|
||||
|
||||
if (type === WorkflowNodeType.Apply || type === WorkflowNodeType.Deploy) {
|
||||
rs = {
|
||||
...rs,
|
||||
config: {
|
||||
switch (nodeType) {
|
||||
case WorkflowNodeType.Apply:
|
||||
case WorkflowNodeType.Deploy:
|
||||
{
|
||||
node.config = {
|
||||
providerType: options.providerType,
|
||||
},
|
||||
input: workflowNodeTypeDefaultInput.get(type),
|
||||
output: workflowNodeTypeDefaultOutput.get(type),
|
||||
};
|
||||
node.input = workflowNodeTypeDefaultInputs.get(nodeType);
|
||||
node.output = workflowNodeTypeDefaultOutputs.get(nodeType);
|
||||
}
|
||||
break;
|
||||
|
||||
case WorkflowNodeType.Condition:
|
||||
{
|
||||
node.validated = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case WorkflowNodeType.Branch:
|
||||
{
|
||||
node.branches = [newNode(WorkflowNodeType.Condition, { branchIndex: 0 }), newNode(WorkflowNodeType.Condition, { branchIndex: 1 })];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == WorkflowNodeType.Condition) {
|
||||
rs.validated = true;
|
||||
}
|
||||
|
||||
if (type === WorkflowNodeType.Branch) {
|
||||
rs = {
|
||||
...rs,
|
||||
branches: [newWorkflowNode(WorkflowNodeType.Condition, { branchIndex: 0 }), newWorkflowNode(WorkflowNodeType.Condition, { branchIndex: 1 })],
|
||||
};
|
||||
}
|
||||
|
||||
return rs;
|
||||
return node;
|
||||
};
|
||||
|
||||
export const isWorkflowBranchNode = (node: WorkflowNode | WorkflowBranchNode): node is WorkflowBranchNode => {
|
||||
@ -226,7 +241,7 @@ export const addBranch = (node: WorkflowNode | WorkflowBranchNode, branchNodeId:
|
||||
return draft;
|
||||
}
|
||||
current.branches.push(
|
||||
newWorkflowNode(WorkflowNodeType.Condition, {
|
||||
newNode(WorkflowNodeType.Condition, {
|
||||
branchIndex: current.branches.length,
|
||||
})
|
||||
);
|
||||
@ -340,21 +355,24 @@ export const getWorkflowOutputBeforeId = (node: WorkflowNode | WorkflowBranchNod
|
||||
return output;
|
||||
};
|
||||
|
||||
export const isAllNodesValidated = (node: WorkflowNode | WorkflowBranchNode): boolean => {
|
||||
export const isAllNodesValidated = (node: WorkflowNode): boolean => {
|
||||
let current = node as typeof node | undefined;
|
||||
while (current) {
|
||||
if (!isWorkflowBranchNode(current) && !current.validated) {
|
||||
return false;
|
||||
}
|
||||
if (isWorkflowBranchNode(current)) {
|
||||
for (const branch of current.branches) {
|
||||
if (current.type === WorkflowNodeType.Branch) {
|
||||
for (const branch of current.branches!) {
|
||||
if (!isAllNodesValidated(branch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!current.validated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -372,14 +390,9 @@ export const getExecuteMethod = (node: WorkflowNode): { type: string; crontab: s
|
||||
}
|
||||
};
|
||||
|
||||
export type WorkflowBranchNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: WorkflowNodeType;
|
||||
branches: WorkflowNode[];
|
||||
next?: WorkflowNode | WorkflowBranchNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
type WorkflowNodeDropdwonItem = {
|
||||
type: WorkflowNodeType;
|
||||
providerType?: string;
|
||||
@ -389,16 +402,25 @@ type WorkflowNodeDropdwonItem = {
|
||||
children?: WorkflowNodeDropdwonItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export enum WorkflowNodeDropdwonItemIconType {
|
||||
Icon,
|
||||
Provider,
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export type WorkflowNodeDropdwonItemIcon = {
|
||||
type: WorkflowNodeDropdwonItemIconType;
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const workflowNodeDropdownDeployList: WorkflowNodeDropdwonItem[] = Array.from(deployProvidersMap.values()).map((item) => {
|
||||
return {
|
||||
type: WorkflowNodeType.Apply,
|
||||
@ -412,41 +434,44 @@ const workflowNodeDropdownDeployList: WorkflowNodeDropdwonItem[] = Array.from(de
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const workflowNodeDropdownList: WorkflowNodeDropdwonItem[] = [
|
||||
{
|
||||
type: WorkflowNodeType.Apply,
|
||||
name: workflowNodeTypeDefaultName.get(WorkflowNodeType.Apply) ?? "",
|
||||
name: workflowNodeTypeDefaultNames.get(WorkflowNodeType.Apply) ?? "",
|
||||
icon: {
|
||||
type: WorkflowNodeDropdwonItemIconType.Icon,
|
||||
name: "NotebookPen",
|
||||
name: "ApplyNodeIcon",
|
||||
},
|
||||
leaf: true,
|
||||
},
|
||||
{
|
||||
type: WorkflowNodeType.Deploy,
|
||||
name: workflowNodeTypeDefaultName.get(WorkflowNodeType.Deploy) ?? "",
|
||||
name: workflowNodeTypeDefaultNames.get(WorkflowNodeType.Deploy) ?? "",
|
||||
icon: {
|
||||
type: WorkflowNodeDropdwonItemIconType.Icon,
|
||||
name: "CloudUpload",
|
||||
name: "DeployNodeIcon",
|
||||
},
|
||||
children: workflowNodeDropdownDeployList,
|
||||
},
|
||||
{
|
||||
type: WorkflowNodeType.Branch,
|
||||
name: workflowNodeTypeDefaultName.get(WorkflowNodeType.Branch) ?? "",
|
||||
name: workflowNodeTypeDefaultNames.get(WorkflowNodeType.Branch) ?? "",
|
||||
leaf: true,
|
||||
icon: {
|
||||
type: WorkflowNodeDropdwonItemIconType.Icon,
|
||||
name: "GitFork",
|
||||
name: "BranchNodeIcon",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: WorkflowNodeType.Notify,
|
||||
name: workflowNodeTypeDefaultName.get(WorkflowNodeType.Notify) ?? "",
|
||||
name: workflowNodeTypeDefaultNames.get(WorkflowNodeType.Notify) ?? "",
|
||||
leaf: true,
|
||||
icon: {
|
||||
type: WorkflowNodeDropdwonItemIconType.Icon,
|
||||
name: "Megaphone",
|
||||
name: "NotifyNodeIcon",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { type WorkflowOutput } from "./workflow";
|
||||
|
||||
export interface WorkflowRunModel extends BaseModel {
|
||||
workflow: string;
|
||||
log: WorkflowRunLog[];
|
||||
@ -10,5 +8,12 @@ export interface WorkflowRunModel extends BaseModel {
|
||||
export type WorkflowRunLog = {
|
||||
nodeName: string;
|
||||
error: string;
|
||||
outputs: WorkflowOutput[];
|
||||
outputs: WorkflowRunLogOutput[];
|
||||
};
|
||||
|
||||
export type WorkflowRunLogOutput = {
|
||||
time: ISO8601String;
|
||||
title: string;
|
||||
content: string;
|
||||
error: string;
|
||||
};
|
||||
|
4
ui/src/global.d.ts
vendored
4
ui/src/global.d.ts
vendored
@ -1,6 +1,8 @@
|
||||
import { type BaseModel as PbBaseModel } from "pocketbase";
|
||||
|
||||
declare global {
|
||||
declare type ISO8601String = string;
|
||||
|
||||
declare interface BaseModel extends PbBaseModel {
|
||||
created: ISO8601String;
|
||||
updated: ISO8601String;
|
||||
@ -10,8 +12,6 @@ declare global {
|
||||
declare type MaybeModelRecord<T extends BaseModel = BaseModel> = T | Omit<T, "id" | "created" | "updated" | "deleted">;
|
||||
|
||||
declare type MaybeModelRecordWithId<T extends BaseModel = BaseModel> = T | Pick<T, "id">;
|
||||
|
||||
declare type ISO8601String = string;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
@ -3,7 +3,7 @@ import { initReactI18next } from "react-i18next";
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import resources, { LOCALE_ZH_NAME, LOCALE_EN_NAME } from "./locales";
|
||||
import resources, { LOCALE_EN_NAME, LOCALE_ZH_NAME } from "./locales";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
|
@ -3,9 +3,8 @@
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.copy": "Copy",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.edit": "Edit",
|
||||
"common.button.enable": "Enable",
|
||||
"common.button.more": "More",
|
||||
"common.button.ok": "Ok",
|
||||
"common.button.reset": "Reset",
|
||||
"common.button.save": "Save",
|
||||
|
@ -7,14 +7,9 @@
|
||||
"workflow.action.edit": "Edit workflow",
|
||||
"workflow.action.delete": "Delete workflow",
|
||||
"workflow.action.delete.confirm": "Are you sure to delete this workflow?",
|
||||
"workflow.action.discard": "Discard changes",
|
||||
"workflow.action.discard.confirm": "Are you sure to discard your changes?",
|
||||
"workflow.action.release": "Release",
|
||||
"workflow.action.release.confirm": "Are you sure to release your changes?",
|
||||
"workflow.action.release.failed.uncompleted": "Please complete the orchestration first",
|
||||
"workflow.action.run": "Run",
|
||||
"workflow.action.run.confirm": "There are unreleased changes, are you sure to run this workflow based on the latest released version?",
|
||||
"workflow.action.enable": "Enable",
|
||||
"workflow.action.enable.failed.uncompleted": "Please complete the orchestration and publish the changes first",
|
||||
"workflow.action.disable": "Disable",
|
||||
|
||||
"workflow.props.name": "Name",
|
||||
"workflow.props.description": "Description",
|
||||
@ -28,14 +23,28 @@
|
||||
"workflow.props.created_at": "Created at",
|
||||
"workflow.props.updated_at": "Updated at",
|
||||
|
||||
"workflow.detail.orchestration.tab": "Orchestration",
|
||||
"workflow.detail.runs.tab": "History runs",
|
||||
"workflow.new.title": "Create Workflow",
|
||||
"workflow.new.subtitle": "Apply, deploy and notify with Workflows",
|
||||
"workflow.new.templates.title": "Choose a Workflow Template",
|
||||
"workflow.new.templates.template.standard.title": "Standard template",
|
||||
"workflow.new.templates.template.standard.description": "A standard operating procedure that includes application, deployment, and notification steps.",
|
||||
"workflow.new.templates.template.blank.title": "Blank template",
|
||||
"workflow.new.templates.template.blank.description": "Customize all the contents of the workflow from the beginning.",
|
||||
|
||||
"workflow.detail.baseinfo.modal.title": "Workflow base information",
|
||||
"workflow.detail.baseinfo.form.name.label": "Name",
|
||||
"workflow.detail.baseinfo.form.name.placeholder": "Please enter name",
|
||||
"workflow.detail.baseinfo.form.description.label": "Description",
|
||||
"workflow.detail.baseinfo.form.description.placeholder": "Please enter description",
|
||||
"workflow.detail.orchestration.tab": "Orchestration",
|
||||
"workflow.detail.orchestration.action.discard": "Discard changes",
|
||||
"workflow.detail.orchestration.action.discard.confirm": "Are you sure to discard your changes?",
|
||||
"workflow.detail.orchestration.action.release": "Release",
|
||||
"workflow.detail.orchestration.action.release.confirm": "Are you sure to release your changes?",
|
||||
"workflow.detail.orchestration.action.release.failed.uncompleted": "Please complete the orchestration first",
|
||||
"workflow.detail.orchestration.action.run": "Run",
|
||||
"workflow.detail.orchestration.action.run.confirm": "There are unreleased changes, are you sure to run this workflow based on the latest released version?",
|
||||
"workflow.detail.runs.tab": "History runs",
|
||||
|
||||
"workflow.common.certificate.label": "Certificate",
|
||||
"workflow.node.setting.label": "Setting Node",
|
||||
|
@ -3,9 +3,8 @@
|
||||
"common.button.cancel": "取消",
|
||||
"common.button.copy": "复制",
|
||||
"common.button.delete": "刪除",
|
||||
"common.button.disable": "禁用",
|
||||
"common.button.edit": "编辑",
|
||||
"common.button.enable": "启用",
|
||||
"common.button.more": "更多",
|
||||
"common.button.ok": "确定",
|
||||
"common.button.reset": "重置",
|
||||
"common.button.save": "保存",
|
||||
|
@ -7,14 +7,9 @@
|
||||
"workflow.action.edit": "编辑工作流",
|
||||
"workflow.action.delete": "删除工作流",
|
||||
"workflow.action.delete.confirm": "确定要删除此工作流吗?",
|
||||
"workflow.action.discard": "撤销更改",
|
||||
"workflow.action.discard.confirm": "确定要撤销更改并回退到最近一次发布的版本吗?",
|
||||
"workflow.action.release": "发布更改",
|
||||
"workflow.action.release.confirm": "确定要发布更改吗?",
|
||||
"workflow.action.release.failed.uncompleted": "请先完成流程编排",
|
||||
"workflow.action.run": "执行",
|
||||
"workflow.action.run.confirm": "存在未发布的更改,确定要按最近一次发布的版本来执行此工作流吗?",
|
||||
"workflow.action.enable": "启用",
|
||||
"workflow.action.enable.failed.uncompleted": "请先完成流程编排并发布更改",
|
||||
"workflow.action.disable": "禁用",
|
||||
|
||||
"workflow.props.name": "名称",
|
||||
"workflow.props.description": "描述",
|
||||
@ -28,14 +23,28 @@
|
||||
"workflow.props.created_at": "创建时间",
|
||||
"workflow.props.updated_at": "更新时间",
|
||||
|
||||
"workflow.detail.orchestration.tab": "流程编排",
|
||||
"workflow.detail.runs.tab": "执行历史",
|
||||
"workflow.new.title": "新建工作流",
|
||||
"workflow.new.subtitle": "使用工作流来申请证书、部署上传和发送通知",
|
||||
"workflow.new.templates.title": "选择工作流模板",
|
||||
"workflow.new.templates.template.standard.title": "标准模板",
|
||||
"workflow.new.templates.template.standard.description": "一个包含申请 + 部署 + 通知步骤的标准工作流程。",
|
||||
"workflow.new.templates.template.blank.title": "空白模板",
|
||||
"workflow.new.templates.template.blank.description": "从零开始自定义工作流的任务内容。",
|
||||
|
||||
"workflow.detail.baseinfo.modal.title": "编辑基本信息",
|
||||
"workflow.detail.baseinfo.form.name.label": "名称",
|
||||
"workflow.detail.baseinfo.form.name.placeholder": "请输入工作流名称",
|
||||
"workflow.detail.baseinfo.form.description.label": "描述",
|
||||
"workflow.detail.baseinfo.form.description.placeholder": "请输入工作流描述",
|
||||
"workflow.detail.orchestration.tab": "流程编排",
|
||||
"workflow.detail.orchestration.action.discard": "撤销更改",
|
||||
"workflow.detail.orchestration.action.discard.confirm": "确定要撤销更改并回退到最近一次发布的版本吗?",
|
||||
"workflow.detail.orchestration.action.release": "发布更改",
|
||||
"workflow.detail.orchestration.action.release.confirm": "确定要发布更改吗?",
|
||||
"workflow.detail.orchestration.action.release.failed.uncompleted": "流程编排未完成,请检查是否有节点未设置",
|
||||
"workflow.detail.orchestration.action.run": "执行",
|
||||
"workflow.detail.orchestration.action.run.confirm": "此工作流存在未发布的更改,将以最近一次发布的版本为准,确定要继续执行吗?",
|
||||
"workflow.detail.runs.tab": "执行历史",
|
||||
|
||||
"workflow.common.certificate.label": "证书",
|
||||
"workflow.node.setting.label": "设置节点",
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
ApartmentOutlined as ApartmentOutlinedIcon,
|
||||
CaretRightOutlined as CaretRightOutlinedIcon,
|
||||
DeleteOutlined as DeleteOutlinedIcon,
|
||||
DownOutlined as DownOutlinedIcon,
|
||||
EllipsisOutlined as EllipsisOutlinedIcon,
|
||||
HistoryOutlined as HistoryOutlinedIcon,
|
||||
UndoOutlined as UndoOutlinedIcon,
|
||||
@ -45,8 +46,8 @@ const WorkflowDetail = () => {
|
||||
);
|
||||
useEffect(() => {
|
||||
// TODO: loading
|
||||
init(workflowId);
|
||||
}, [workflowId, init]);
|
||||
init(workflowId!);
|
||||
}, [workflowId]);
|
||||
|
||||
const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration");
|
||||
|
||||
@ -70,10 +71,13 @@ const WorkflowDetail = () => {
|
||||
|
||||
const [allowDiscard, setAllowDiscard] = useState(false);
|
||||
const [allowRelease, setAllowRelease] = useState(false);
|
||||
const [allowRun, setAllowRun] = useState(false);
|
||||
useDeepCompareEffect(() => {
|
||||
const hasReleased = !!workflow.content;
|
||||
const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content);
|
||||
setAllowDiscard(hasChanges && !workflowRunning);
|
||||
setAllowRelease(hasChanges && !workflowRunning);
|
||||
setAllowDiscard(!workflowRunning && hasReleased && hasChanges);
|
||||
setAllowRelease(!workflowRunning && hasChanges);
|
||||
setAllowRun(hasReleased);
|
||||
}, [workflow, workflowRunning]);
|
||||
|
||||
const handleBaseInfoFormFinish = async (values: Pick<WorkflowModel, "name" | "description">) => {
|
||||
@ -86,13 +90,18 @@ const WorkflowDetail = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnableChange = () => {
|
||||
if (!workflow.enabled && !isAllNodesValidated(workflow.content!)) {
|
||||
const handleEnableChange = async () => {
|
||||
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
|
||||
messageApi.warning(t("workflow.action.enable.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
||||
switchEnable();
|
||||
try {
|
||||
await switchEnable();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
@ -114,18 +123,24 @@ const WorkflowDetail = () => {
|
||||
};
|
||||
|
||||
const handleDiscardClick = () => {
|
||||
modalApi.confirm({
|
||||
title: t("workflow.detail.orchestration.action.discard"),
|
||||
content: t("workflow.detail.orchestration.action.discard.confirm"),
|
||||
onOk: () => {
|
||||
alert("TODO");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleReleaseClick = () => {
|
||||
if (!isAllNodesValidated(workflow.draft!)) {
|
||||
messageApi.warning(t("workflow.action.release.failed.uncompleted"));
|
||||
messageApi.warning(t("workflow.detail.orchestration.action.release.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.confirm({
|
||||
title: t("workflow.action.release"),
|
||||
content: t("workflow.action.release.confirm"),
|
||||
title: t("workflow.detail.orchestration.action.release"),
|
||||
content: t("workflow.detail.orchestration.action.release.confirm"),
|
||||
onOk: async () => {
|
||||
try {
|
||||
await save();
|
||||
@ -148,8 +163,8 @@ const WorkflowDetail = () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
if (workflow.hasDraft) {
|
||||
modalApi.confirm({
|
||||
title: t("workflow.action.run"),
|
||||
content: t("workflow.action.run.confirm"),
|
||||
title: t("workflow.detail.orchestration.action.run"),
|
||||
content: t("workflow.detail.orchestration.action.run.confirm"),
|
||||
onOk: () => resolve(void 0),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
@ -164,7 +179,7 @@ const WorkflowDetail = () => {
|
||||
try {
|
||||
await runWorkflow(workflowId!);
|
||||
|
||||
messageApi.warning(t("common.text.operation_succeeded"));
|
||||
messageApi.success(t("common.text.operation_succeeded"));
|
||||
} catch (err) {
|
||||
if (err instanceof ClientResponseError && err.isAbort) {
|
||||
return;
|
||||
@ -189,17 +204,19 @@ const WorkflowDetail = () => {
|
||||
style={{ paddingBottom: 0 }}
|
||||
title={workflow.name}
|
||||
extra={[
|
||||
<Button.Group key="actions">
|
||||
<WorkflowBaseInfoModalForm data={workflow} trigger={<Button>{t("common.button.edit")}</Button>} onFinish={handleBaseInfoFormFinish} />
|
||||
<WorkflowBaseInfoModalForm key="edit" data={workflow} trigger={<Button>{t("common.button.edit")}</Button>} onFinish={handleBaseInfoFormFinish} />,
|
||||
|
||||
<Button onClick={handleEnableChange}>{workflow.enabled ? t("common.button.disable") : t("common.button.enable")}</Button>
|
||||
<Button key="enable" onClick={handleEnableChange}>
|
||||
{workflow.enabled ? t("workflow.action.disable") : t("workflow.action.enable")}
|
||||
</Button>,
|
||||
|
||||
<Dropdown
|
||||
key="more"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
label: t("workflow.action.delete"),
|
||||
danger: true,
|
||||
icon: <DeleteOutlinedIcon />,
|
||||
onClick: () => {
|
||||
@ -210,9 +227,10 @@ const WorkflowDetail = () => {
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button icon={<EllipsisOutlinedIcon />} />
|
||||
</Dropdown>
|
||||
</Button.Group>,
|
||||
<Button icon={<DownOutlinedIcon />} iconPosition="end">
|
||||
{t("common.button.more")}
|
||||
</Button>
|
||||
</Dropdown>,
|
||||
]}
|
||||
>
|
||||
<Typography.Paragraph type="secondary">{workflow.description}</Typography.Paragraph>
|
||||
@ -239,13 +257,13 @@ const WorkflowDetail = () => {
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 z-[1]">
|
||||
<Space>
|
||||
<Button icon={<CaretRightOutlinedIcon />} loading={workflowRunning} type="primary" onClick={handleRunClick}>
|
||||
{t("workflow.action.run")}
|
||||
<Button disabled={!allowRun} icon={<CaretRightOutlinedIcon />} loading={workflowRunning} type="primary" onClick={handleRunClick}>
|
||||
{t("workflow.detail.orchestration.action.run")}
|
||||
</Button>
|
||||
|
||||
<Button.Group>
|
||||
<Button color="primary" disabled={!allowRelease} variant="outlined" onClick={handleReleaseClick}>
|
||||
{t("workflow.action.release")}
|
||||
{t("workflow.detail.orchestration.action.release")}
|
||||
</Button>
|
||||
|
||||
<Dropdown
|
||||
@ -254,7 +272,7 @@ const WorkflowDetail = () => {
|
||||
{
|
||||
key: "discard",
|
||||
disabled: !allowDiscard,
|
||||
label: t("workflow.action.discard"),
|
||||
label: t("workflow.detail.orchestration.action.discard"),
|
||||
icon: <UndoOutlinedIcon />,
|
||||
onClick: handleDiscardClick,
|
||||
},
|
||||
|
@ -245,7 +245,7 @@ const WorkflowList = () => {
|
||||
|
||||
const handleEnabledChange = async (workflow: WorkflowModel) => {
|
||||
try {
|
||||
if (!workflow.enabled && !isAllNodesValidated(workflow.content!)) {
|
||||
if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
|
||||
messageApi.warning(t("workflow.action.enable.failed.uncompleted"));
|
||||
return;
|
||||
}
|
||||
|
123
ui/src/pages/workflows/WorkflowNew.tsx
Normal file
123
ui/src/pages/workflows/WorkflowNew.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Card, Col, Row, Spin, Typography, notification } from "antd";
|
||||
import { sleep } from "radash";
|
||||
|
||||
import { type WorkflowModel, initWorkflow } from "@/domain/workflow";
|
||||
import { save as saveWorkflow } from "@/repository/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
const TEMPLATE_KEY_BLANK = "blank" as const;
|
||||
const TEMPLATE_KEY_STANDARD = "standard" as const;
|
||||
type TemplateKeys = typeof TEMPLATE_KEY_BLANK | typeof TEMPLATE_KEY_STANDARD;
|
||||
|
||||
const WorkflowNew = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const templateGridSpans = {
|
||||
xs: { flex: "100%" },
|
||||
md: { flex: "100%" },
|
||||
lg: { flex: "50%" },
|
||||
xl: { flex: "50%" },
|
||||
xxl: { flex: "50%" },
|
||||
};
|
||||
const [templateSelectKey, setTemplateSelectKey] = useState<TemplateKeys>();
|
||||
|
||||
const handleTemplateSelect = async (key: TemplateKeys) => {
|
||||
if (templateSelectKey) return;
|
||||
|
||||
setTemplateSelectKey(key);
|
||||
|
||||
try {
|
||||
let workflow: WorkflowModel;
|
||||
|
||||
switch (key) {
|
||||
case TEMPLATE_KEY_BLANK:
|
||||
workflow = initWorkflow();
|
||||
break;
|
||||
|
||||
case TEMPLATE_KEY_STANDARD:
|
||||
workflow = initWorkflow({ template: "standard" });
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Invalid args: `key`";
|
||||
}
|
||||
|
||||
workflow = await saveWorkflow(workflow);
|
||||
await sleep(500);
|
||||
|
||||
await navigate(`/workflows/${workflow.id}`, { replace: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
|
||||
setTemplateSelectKey(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{NotificationContextHolder}
|
||||
|
||||
<Card styles={{ body: { padding: "0.5rem", paddingBottom: 0 } }}>
|
||||
<PageHeader title={t("workflow.new.title")}>
|
||||
<Typography.Paragraph type="secondary">{t("workflow.new.subtitle")}</Typography.Paragraph>
|
||||
</PageHeader>
|
||||
</Card>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="max-w-[960px] mx-auto px-2">
|
||||
<Typography.Text type="secondary">
|
||||
<div className="mt-4 mb-8 text-xl">{t("workflow.new.templates.title")}</div>
|
||||
</Typography.Text>
|
||||
|
||||
<Row className="justify-stretch" gutter={[16, 16]}>
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-standard.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateSelect(TEMPLATE_KEY_STANDARD)}
|
||||
>
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<Card.Meta
|
||||
className="flex-grow"
|
||||
title={t("workflow.new.templates.template.standard.title")}
|
||||
description={t("workflow.new.templates.template.standard.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_STANDARD} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col {...templateGridSpans}>
|
||||
<Card
|
||||
className="size-full"
|
||||
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-blank.png" />}
|
||||
hoverable
|
||||
onClick={() => handleTemplateSelect(TEMPLATE_KEY_BLANK)}
|
||||
>
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<Card.Meta
|
||||
className="flex-grow"
|
||||
title={t("workflow.new.templates.template.blank.title")}
|
||||
description={t("workflow.new.templates.template.blank.description")}
|
||||
/>
|
||||
<Spin spinning={templateSelectKey === TEMPLATE_KEY_BLANK} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowNew;
|
@ -1,18 +1,16 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import {
|
||||
type WorkflowBranchNode,
|
||||
type WorkflowModel,
|
||||
type WorkflowNode,
|
||||
addBranch,
|
||||
addNode,
|
||||
getExecuteMethod,
|
||||
getWorkflowOutputBeforeId,
|
||||
initWorkflow,
|
||||
removeBranch,
|
||||
removeNode,
|
||||
updateNode,
|
||||
type WorkflowBranchNode,
|
||||
type WorkflowModel,
|
||||
type WorkflowNode,
|
||||
WorkflowNodeType,
|
||||
} from "@/domain/workflow";
|
||||
import { get as getWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
|
||||
@ -27,44 +25,29 @@ export type WorkflowState = {
|
||||
getWorkflowOuptutBeforeId: (id: string, type: string) => WorkflowNode[];
|
||||
switchEnable(): void;
|
||||
save(): void;
|
||||
init(id?: string): void;
|
||||
init(id: string): void;
|
||||
setBaseInfo: (name: string, description: string) => void;
|
||||
};
|
||||
|
||||
export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
workflow: {
|
||||
id: "",
|
||||
name: "",
|
||||
type: WorkflowNodeType.Start,
|
||||
} as WorkflowModel,
|
||||
workflow: {} as WorkflowModel,
|
||||
initialized: false,
|
||||
init: async (id?: string) => {
|
||||
let data = {
|
||||
id: "",
|
||||
name: "",
|
||||
type: "auto",
|
||||
} as WorkflowModel;
|
||||
|
||||
if (!id) {
|
||||
data = initWorkflow();
|
||||
} else {
|
||||
data = await getWorkflow(id);
|
||||
}
|
||||
init: async (id: string) => {
|
||||
const data = await getWorkflow(id);
|
||||
|
||||
set({
|
||||
workflow: data,
|
||||
initialized: true,
|
||||
});
|
||||
},
|
||||
|
||||
setBaseInfo: async (name: string, description: string) => {
|
||||
const data: Record<string, string | boolean | WorkflowNode> = {
|
||||
id: (get().workflow.id as string) ?? "",
|
||||
name: name || "",
|
||||
description: description || "",
|
||||
};
|
||||
if (!data.id) {
|
||||
data.draft = get().workflow.draft as WorkflowNode;
|
||||
}
|
||||
const resp = await saveWorkflow(data);
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
@ -77,17 +60,18 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
switchEnable: async () => {
|
||||
const root = get().workflow.draft as WorkflowNode;
|
||||
const root = get().workflow.content as WorkflowNode;
|
||||
const executeMethod = getExecuteMethod(root);
|
||||
const resp = await saveWorkflow({
|
||||
id: (get().workflow.id as string) ?? "",
|
||||
content: root,
|
||||
enabled: !get().workflow.enabled,
|
||||
hasDraft: false,
|
||||
type: executeMethod.type,
|
||||
crontab: executeMethod.crontab,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -95,13 +79,13 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
id: resp.id,
|
||||
content: resp.content,
|
||||
enabled: resp.enabled,
|
||||
hasDraft: false,
|
||||
type: resp.type,
|
||||
crontab: resp.crontab,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
save: async () => {
|
||||
const root = get().workflow.draft as WorkflowNode;
|
||||
const executeMethod = getExecuteMethod(root);
|
||||
@ -112,6 +96,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
type: executeMethod.type,
|
||||
crontab: executeMethod.crontab,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -125,6 +110,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
updateNode: async (node: WorkflowNode | WorkflowBranchNode) => {
|
||||
const newRoot = updateNode(get().workflow.draft as WorkflowNode, node);
|
||||
const resp = await saveWorkflow({
|
||||
@ -132,6 +118,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
draft: newRoot,
|
||||
hasDraft: true,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -143,6 +130,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
addNode: async (node: WorkflowNode | WorkflowBranchNode, preId: string) => {
|
||||
const newRoot = addNode(get().workflow.draft as WorkflowNode, preId, node);
|
||||
const resp = await saveWorkflow({
|
||||
@ -150,6 +138,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
draft: newRoot,
|
||||
hasDraft: true,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -161,6 +150,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
addBranch: async (branchId: string) => {
|
||||
const newRoot = addBranch(get().workflow.draft as WorkflowNode, branchId);
|
||||
const resp = await saveWorkflow({
|
||||
@ -168,6 +158,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
draft: newRoot,
|
||||
hasDraft: true,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -179,6 +170,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
removeBranch: async (branchId: string, index: number) => {
|
||||
const newRoot = removeBranch(get().workflow.draft as WorkflowNode, branchId, index);
|
||||
const resp = await saveWorkflow({
|
||||
@ -186,6 +178,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
draft: newRoot,
|
||||
hasDraft: true,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -197,6 +190,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
removeNode: async (nodeId: string) => {
|
||||
const newRoot = removeNode(get().workflow.draft as WorkflowNode, nodeId);
|
||||
const resp = await saveWorkflow({
|
||||
@ -204,6 +198,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
draft: newRoot,
|
||||
hasDraft: true,
|
||||
});
|
||||
|
||||
set((state: WorkflowState) => {
|
||||
return {
|
||||
workflow: {
|
||||
@ -215,6 +210,7 @@ export const useWorkflowStore = create<WorkflowState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getWorkflowOuptutBeforeId: (id: string, type: string) => {
|
||||
return getWorkflowOutputBeforeId(get().workflow.draft as WorkflowNode, id, type);
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user