diff --git a/ui/package-lock.json b/ui/package-lock.json
index 2f5392d09..46fccd086 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1,25 +1,25 @@
{
- "name": "ui",
+ "name": "@certimate/webui",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "ui",
- "version": "0.0.0",
+ "name": "@certimate/webui",
"dependencies": {
"@ant-design/pro-components": "^2.8.10",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.2",
"@codemirror/legacy-modes": "^6.5.1",
- "@flowgram.ai/fixed-layout-editor": "^0.2.28",
+ "@flowgram.ai/document": "^0.3.2",
+ "@flowgram.ai/fixed-layout-editor": "^0.3.2",
"@tabler/icons-react": "^3.34.1",
- "@uiw/codemirror-extensions-basic-setup": "^4.24.1",
- "@uiw/codemirror-theme-vscode": "^4.24.1",
- "@uiw/react-codemirror": "^4.24.1",
+ "@uiw/codemirror-extensions-basic-setup": "^4.24.2",
+ "@uiw/codemirror-theme-vscode": "^4.24.2",
+ "@uiw/react-codemirror": "^4.24.2",
"ahooks": "^3.9.0",
- "antd": "^5.26.6",
+ "antd": "^5.26.7",
"antd-zod": "^7.0.0",
"clsx": "^2.1.1",
"cron-parser": "^5.3.0",
@@ -36,8 +36,8 @@
"react-i18next": "^15.6.1",
"react-router-dom": "^7.7.1",
"tailwind-merge": "^3.3.1",
- "zod": "^4.0.10",
- "zustand": "^5.0.6"
+ "zod": "^4.0.14",
+ "zustand": "^5.0.7"
},
"devDependencies": {
"@eslint/js": "^9.32.0",
@@ -54,7 +54,7 @@
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-better-tailwindcss": "^3.7.2",
+ "eslint-plugin-better-tailwindcss": "^3.7.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react-hooks": "^5.2.0",
@@ -2784,12 +2784,12 @@
}
},
"node_modules/@flowgram.ai/background-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.2.28.tgz",
- "integrity": "sha512-GEH0e1TioFQuKTZli7UO0yz171xs2T2XAnoWdxGhuWTcZ3PVyJSwJSF6BPDxfWGgN3kL9JeghbCPgElhwEOKGg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.3.2.tgz",
+ "integrity": "sha512-3BeVeZ3bvhqIfsSVuUpxMBdOON1eY9E+7SlAvs1iWbEgzn8LVg7i6lXdl0BIVlUEIthl7aLH7uxrbb/MAj9Fxg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -2799,11 +2799,11 @@
}
},
"node_modules/@flowgram.ai/command": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.2.28.tgz",
- "integrity": "sha512-/OMswJnXKm6aHIjwOQfWzCeYCr19x6gA8YNY8z9BpWJa3mPpPMUKjigldvOZVq/LZ3UPLj4EtbW7u4yyfsNCgA==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.3.2.tgz",
+ "integrity": "sha512-P6tuALZrSzjGBAB3BttJWumtmN0NcBJ88dHGsWStMMDW81LYCFKCsybB2wU0YoGVV7viRjCe/szSoQAbp8B2NQ==",
"dependencies": {
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -2813,12 +2813,12 @@
}
},
"node_modules/@flowgram.ai/core": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.2.28.tgz",
- "integrity": "sha512-iVHIGHZTjoI0OJCf2m2ch70jkopWu6P+G1wy4cYMWqWI89lz2grtAEd7Qg8vRHQameLAdoQnAHuSkAXpIIKcaA==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.3.2.tgz",
+ "integrity": "sha512-pb+YP2DKIrTP2woQEbt7cKWnqwXObsMtGhS0DuRgbCIN1+6fLXFjvN2M2QhMRsOPiqY60dWnZWLKtagwPVUDqA==",
"dependencies": {
- "@flowgram.ai/command": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/command": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"@phosphor/messaging": "^1.3.0",
"@tweenjs/tween.js": "^18",
"clsx": "^1.1.1",
@@ -2858,12 +2858,12 @@
}
},
"node_modules/@flowgram.ai/document": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.2.28.tgz",
- "integrity": "sha512-HzEG5EZNdtooAaGjCCd4k9cjxoUaMs0j2dowVA2/z8CO42ylqLnk+GJqr8jE+gPxS4muwjxdzEa/OXcgsrlqhw==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.3.2.tgz",
+ "integrity": "sha512-N+VAQzZcNt8PUfB/32cGzMFtUUiKGTzCFMFMElB9ffSWG2stHgueRO3kBWCD8yweArfUofhCTk3eERJ345dj3A==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.2",
@@ -2888,29 +2888,29 @@
}
},
"node_modules/@flowgram.ai/editor": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.2.28.tgz",
- "integrity": "sha512-QMSuOfFXZHHtnjuuX/pDqoGn/iCKjD6I05wcKUn0LqqbFgyYKp49Ju77IS9rGBs6R1Cj0jqFPl3OLW50sFqLEg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.3.2.tgz",
+ "integrity": "sha512-Vdq53nKUoHQKWtVOmMRcI3DgS8U8pW2jFQRklYfAVhmLxyw4636ZplF8F3/AiAbt5tJ7P9OOs5xI456U1U2dqQ==",
"dependencies": {
- "@flowgram.ai/background-plugin": "0.2.28",
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/group-plugin": "0.2.28",
- "@flowgram.ai/history": "0.2.28",
- "@flowgram.ai/history-node-plugin": "0.2.28",
- "@flowgram.ai/i18n-plugin": "0.2.28",
- "@flowgram.ai/materials-plugin": "0.2.28",
- "@flowgram.ai/node": "0.2.28",
- "@flowgram.ai/node-core-plugin": "0.2.28",
- "@flowgram.ai/node-variable-plugin": "0.2.28",
- "@flowgram.ai/playground-react": "0.2.28",
- "@flowgram.ai/redux-devtool-plugin": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
- "@flowgram.ai/shortcuts-plugin": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
- "@flowgram.ai/variable-plugin": "0.2.28",
+ "@flowgram.ai/background-plugin": "0.3.2",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/group-plugin": "0.3.2",
+ "@flowgram.ai/history": "0.3.2",
+ "@flowgram.ai/history-node-plugin": "0.3.2",
+ "@flowgram.ai/i18n-plugin": "0.3.2",
+ "@flowgram.ai/materials-plugin": "0.3.2",
+ "@flowgram.ai/node": "0.3.2",
+ "@flowgram.ai/node-core-plugin": "0.3.2",
+ "@flowgram.ai/node-variable-plugin": "0.3.2",
+ "@flowgram.ai/playground-react": "0.3.2",
+ "@flowgram.ai/redux-devtool-plugin": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
+ "@flowgram.ai/shortcuts-plugin": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
+ "@flowgram.ai/variable-plugin": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -2920,13 +2920,13 @@
}
},
"node_modules/@flowgram.ai/fixed-drag-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.2.28.tgz",
- "integrity": "sha512-dNrFBd6G1uFgkhf0LpAC9/7vV8kIQCBuBAmbeUi6EQZkmWsp9rVd4SAdKGqhNLrs6klt8n5oMltv6WJsMCRS6Q==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.3.2.tgz",
+ "integrity": "sha512-XK09rklrkU1EzhmqFBsm/zZGcnnb4LXhNMkol2fDviiq2Aq0UumXL2jRjRaWcFQJVjBawlOd3YciTamSY6xNgg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -2936,29 +2936,29 @@
}
},
"node_modules/@flowgram.ai/fixed-history-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.2.28.tgz",
- "integrity": "sha512-s9ckjS6NS3ds+FfmvTIvO/02N8rSh+EcSDyKwLaGcrYk6X6Fs1T5h/PyBgGTryJ7EzseqlLTFVBqYXzgbymDtg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.3.2.tgz",
+ "integrity": "sha512-27+z5s+9LeRBG+sk5e6mPuqu2dw4nBFV49ouNe/l11r+A+w5j6haZdX2kLR0wrWdJWDu7Dn0y9MQyxwPSyVEmg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/history": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/history": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
}
},
"node_modules/@flowgram.ai/fixed-layout-core": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.2.28.tgz",
- "integrity": "sha512-pIa7vaksAmBZm3aaDMkpxB8RsS+fHGntDjGBWuP5tKJkjMKSXyPDzA7vGOwcVLyX3OrYiMD8ypuCDOGPruAr2A==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.3.2.tgz",
+ "integrity": "sha512-e+J/oxFs4HD8FOdMHh79bXLEp+7gzoCk+42fMTAJ4Or4HEIkep8gRTpFxB0fINnWxpw3CpZZFnhMLIuyR5R46g==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
@@ -2969,19 +2969,19 @@
}
},
"node_modules/@flowgram.ai/fixed-layout-editor": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.2.28.tgz",
- "integrity": "sha512-PpLQXWjC2L7eqVzNdt7Z4DxEh6GcIJyTrOQE0FjUB3XuS2IW7ciHogs3gEfAVpoFbZGj8tzIcs6IMUu1GxjeqQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.3.2.tgz",
+ "integrity": "sha512-wZxyYOt4f9CFX+iUiq5J4tyn4K9YmfcprYs26i7CB2D542vvpREVWXzl/FdTiylcW6qTTGcFAsQkdVdmd33xEg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/editor": "0.2.28",
- "@flowgram.ai/fixed-drag-plugin": "0.2.28",
- "@flowgram.ai/fixed-history-plugin": "0.2.28",
- "@flowgram.ai/fixed-layout-core": "0.2.28",
- "@flowgram.ai/history": "0.2.28",
- "@flowgram.ai/reactive": "0.2.28",
- "@flowgram.ai/select-box-plugin": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/editor": "0.3.2",
+ "@flowgram.ai/fixed-drag-plugin": "0.3.2",
+ "@flowgram.ai/fixed-history-plugin": "0.3.2",
+ "@flowgram.ai/fixed-layout-core": "0.3.2",
+ "@flowgram.ai/history": "0.3.2",
+ "@flowgram.ai/reactive": "0.3.2",
+ "@flowgram.ai/select-box-plugin": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -2991,12 +2991,12 @@
}
},
"node_modules/@flowgram.ai/form": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.2.28.tgz",
- "integrity": "sha512-OgMDayBmHDUEjb6xWZeDmyn8BKZZazEsas3WJKlm2r6zaobygxE5K5NzJ/oy3OyLTgMtpYq3JOWZPgGu16ZRHg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.3.2.tgz",
+ "integrity": "sha512-OHp6IhOQUcW3Hw/BWrxi4Kn/UTB96BkQgjl2mQ0o+HyqTwzGa9wNtLkNWqIK/CogD/l31kRp39L6rFH71S2AgQ==",
"dependencies": {
- "@flowgram.ai/reactive": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/reactive": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"fast-equals": "^2.0.0",
"lodash": "^4.17.21",
"nanoid": "^4.0.2"
@@ -3007,13 +3007,13 @@
}
},
"node_modules/@flowgram.ai/form-core": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.2.28.tgz",
- "integrity": "sha512-JpapezHkKEl4hgNnhBo/5mBUcgAv7aUQpp3ypZ0g/0L9qLd9vB3PjBw7mL+K4t+954ckJMLW0AHJyqeWKXpO4g==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.3.2.tgz",
+ "integrity": "sha512-RjipVRN/pKqDF27vL4GhtRpRtlcDlZZgv2chEehd1tnedJRmgH30REN3ET9ya+cF2Lte9z9ALdAD6KMfCqdySw==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
@@ -3041,16 +3041,16 @@
}
},
"node_modules/@flowgram.ai/free-layout-core": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.2.28.tgz",
- "integrity": "sha512-oGLr4dTggl1e7h/usm0Hs4uYjiAKOU8ZY6W9xpEgvXJkRngiGDgh1+S3Sh7WfxvDQeHkaQhGRgcKNqECGqL1LQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.3.2.tgz",
+ "integrity": "sha512-xvoneqfmNqsfzLotqdikBOfMDXC173G0Gqs5XxDZv3fpCuUMyy3eI0KrnoJZMIp6BD5buDmBrm6vk9D8vE1WnQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/node": "0.2.28",
- "@flowgram.ai/reactive": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/node": "0.3.2",
+ "@flowgram.ai/reactive": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
@@ -3079,14 +3079,14 @@
}
},
"node_modules/@flowgram.ai/group-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.2.28.tgz",
- "integrity": "sha512-Gk4A5oCOxF45Htf68OIR2yZyQR2416HMDsDISWCuAZiV/LafGDSNwRWiUv/6tpyUpwT1uHSipkTFSRwAWApO8g==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.3.2.tgz",
+ "integrity": "sha512-9fn4Sdu6uDqUPz00VBJu8F4CEdkQECg2ZIDETZLU8iG49eeX1qCGPOai7MRMGY83LcEDIR0Sk5w0Z7i4wM5mXQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -3096,12 +3096,12 @@
}
},
"node_modules/@flowgram.ai/history": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.2.28.tgz",
- "integrity": "sha512-Em5yq2ZmP5lRglrdIfHe54zt3PSMM3BUj95jge5rC1uxNes7vhu5jaKhVIK6aehu9m4pOY8ZJ7/Dudr3w04aJQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.3.2.tgz",
+ "integrity": "sha512-dL4v3tbUkZ4q4ph3LsCHQV5gV+kyYoPfjcR+ok4SyMoLzOg46YsAri7KVhNDR2El77EzGDPiCZ+9ZSQJqOX/eQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.2",
@@ -3109,17 +3109,17 @@
}
},
"node_modules/@flowgram.ai/history-node-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.2.28.tgz",
- "integrity": "sha512-NnFt6ocTvGwWjgluUuvJL+Xivj75LrPEFPqhC+GcmciiNmD0HkhW/ojO0aBKv8/Ppemfjo840dhSyxAxyInPsw==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.3.2.tgz",
+ "integrity": "sha512-Q4kMsq11Z7tpHD9mIwPN3jQMrFter7xzbMznr36G4TLRlPV9bfsWNBhLbBto1WEhbiphQ/TTEiBjWQrxlpAkXQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/history": "0.2.28",
- "@flowgram.ai/node": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/history": "0.3.2",
+ "@flowgram.ai/node": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
@@ -3148,32 +3148,32 @@
}
},
"node_modules/@flowgram.ai/i18n": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.2.28.tgz",
- "integrity": "sha512-FsFPlLa834zTBhLKgw6PWm+SoLvsPp2xL9XiphXdjD3kDgLvAsotiO/mdGGuwC+nNMByo2MsUgf7gS7BIVMuTw==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.3.2.tgz",
+ "integrity": "sha512-k+9f6gwDh4YTL91BfliRL8IEgzGZ4ALBAuFmGVucpx+01NEYAoqwF1+g4u9udlXX9Ncny5DDMNNka1C3B5m46Q==",
"dependencies": {
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/utils": "0.3.2",
"i18n-js": "^4.5.1"
}
},
"node_modules/@flowgram.ai/i18n-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.2.28.tgz",
- "integrity": "sha512-U0AjDZekzx7Z8ttxSI5JZWE6A2rzQncILYQ4ALTPpaS6nFqna7ju2qEedcUAIf/xo4gZiYugcQc511lvqtnhLg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.3.2.tgz",
+ "integrity": "sha512-1Zyk6Sh+W62FAF7oP/p+fJ1JxF7iqpoKpOCC65VEHKVQ1TAbHg6fyOoSxNTE7wjnATvt6c3fDw7AYcevIlcRSA==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/i18n": "0.2.28"
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/i18n": "0.3.2"
}
},
"node_modules/@flowgram.ai/materials-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.2.28.tgz",
- "integrity": "sha512-2s50E0MZwHnE4QVS564d2abd29iMv70r7cnRWeLtc6w91Y+mKHQWM5noeoi/diggIWUmj6w4pFYDnSOcN4DeGA==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.3.2.tgz",
+ "integrity": "sha512-svG1M1uMIeavXYj8UV5Mv2glcumQ7AYg4/U6xVGhMJFJmnAWNlNVQCqz5PFRkjyIH6FwW/xJVh03TSqpR2Svkw==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -3184,15 +3184,15 @@
}
},
"node_modules/@flowgram.ai/node": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.2.28.tgz",
- "integrity": "sha512-XZ+kKjZdOFYdC8XL2L+BCYAa/wVLnb2dD/lfedMd9Fnf0QaJaQupwhwPnLtmL4HcdB8ro8uBICSIgVGXED66Uw==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.3.2.tgz",
+ "integrity": "sha512-jVr5zlL/vab7g1JSv69fIivKZAb+kPDQJugeNm9Ux9vjaF3nfssFRPeeRCj8JWMw4hqdRUnTms9Mx1udrCLXTg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.2",
@@ -3204,16 +3204,16 @@
}
},
"node_modules/@flowgram.ai/node-core-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.2.28.tgz",
- "integrity": "sha512-72R6APzlW2Yy1od2QibIhSW3TW+xz44s0+k4rzJ+EZW0gHEwtmo+OCQLo19WM0CrBFjAomTyK/2J5L6a1mp9OA==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.3.2.tgz",
+ "integrity": "sha512-pSOKiVXSSL4u+7REMfK57n29Kpx7iuH///FAU9/1VdpjtnwLH25F/RBF8fyR+VLqdtwTfhqz2A7OjGMqIardmg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/node": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/node": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -3224,16 +3224,16 @@
}
},
"node_modules/@flowgram.ai/node-variable-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.2.28.tgz",
- "integrity": "sha512-3GpLS6LG1w6Y24TJrXnxSa8BnMhs5sgjqv9VgBdx5DFQRynNtRV52bd3X+eNarNFMMcpEqTGiybrvRqmf84K/w==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.3.2.tgz",
+ "integrity": "sha512-GjSqMXv3IqZs6mbiSbHTk/anLQFHl6ozdqM1iCZoOn4V15HW7gJMvXHe3VGwsGxAunsgrbCkkciiZmayWSN9Xg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/form-core": "0.2.28",
- "@flowgram.ai/node": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
- "@flowgram.ai/variable-plugin": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/form-core": "0.3.2",
+ "@flowgram.ai/node": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
+ "@flowgram.ai/variable-plugin": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
@@ -3262,14 +3262,14 @@
}
},
"node_modules/@flowgram.ai/playground-react": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.2.28.tgz",
- "integrity": "sha512-VvUewy+Av1XHeUa5CBPumkSZR3J/M0m4ME9na6hTfzs7bigKjU8rk+X7397JOWswUBBxGod3Lrw+ujttxxvNag==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.3.2.tgz",
+ "integrity": "sha512-dbuuLF+UKCaIgvlVLd1TbM9t13bePKc+BrFRcYvriXZTAbKI6XvWUD7kQ1hIydU5GgTrhpZEWR6WnrxAvEBlrA==",
"dependencies": {
- "@flowgram.ai/background-plugin": "0.2.28",
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/shortcuts-plugin": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/background-plugin": "0.3.2",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/shortcuts-plugin": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -3279,11 +3279,11 @@
}
},
"node_modules/@flowgram.ai/reactive": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.2.28.tgz",
- "integrity": "sha512-WC8FWI4iBhrrip8If1ghFUhqBxGcHExE4rc6Erx1QQqN7YNFII5/cKF1wAaEgQ99rSVGFxIxjkwrUOjNXnnS6g==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.3.2.tgz",
+ "integrity": "sha512-U39SOidMenXvcKRgWZ01L7NGCefDwE90S7nil1/1yJ2bqAT9YLpmIY07Sw474sepeZgx2R9HDStuVpLpZOgq0Q==",
"dependencies": {
- "@flowgram.ai/utils": "0.2.28"
+ "@flowgram.ai/utils": "0.3.2"
},
"peerDependencies": {
"react": ">=16.8",
@@ -3291,12 +3291,12 @@
}
},
"node_modules/@flowgram.ai/redux-devtool-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.2.28.tgz",
- "integrity": "sha512-D8C0sWzGAFpFElacEHwONRr2vezcLa3jT6SRtYEbQ0poSA6HLPPYSigRjug8A19jd9NayCVulPm1M59GpuZF3Q==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.3.2.tgz",
+ "integrity": "sha512-yPeQYWK3juW+TeSWYDxjcpjSH1MlbXGpumKaoX4dFfoeNatVVzH77z3R345jLeJv2SwB0bHC7djfp6Dlb2vIsQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/variable-core": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/variable-core": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2",
"styled-components": "^5"
@@ -3351,14 +3351,14 @@
}
},
"node_modules/@flowgram.ai/renderer": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.2.28.tgz",
- "integrity": "sha512-7AVVZqsUFeNvq339UfUSohk0ByVDq9ocllODScbeIAVWGZyNjG0tyJyaIEoCYrSqh2l+78C+AsaaBJvOPZG5bg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.3.2.tgz",
+ "integrity": "sha512-Pzc43vzAxwpXjJrUkuMopgMMxkplarSlWPw3KdBd9wSIroAslBI6i7z7x3WljfGTJPHGt50EI24tLKTZzGECeQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/i18n": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/i18n": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"reflect-metadata": "~0.2.2"
@@ -3369,12 +3369,12 @@
}
},
"node_modules/@flowgram.ai/select-box-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.2.28.tgz",
- "integrity": "sha512-xIPLKpT3hUtlHzvSOqeu8yqhVJDL7En8cRawKWwJjBs0nrFXEGryPnMCrsmbF1JwTFZrvKrznJacocqmiLwI3A==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.3.2.tgz",
+ "integrity": "sha512-mM58wToixwB/lfGtojwYPgip/sh6Go3+caEH4Eo447dlf88R6bSjWxThZG689QFsY6ZSQEfchg4GO1eYERvOwQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/renderer": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/renderer": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2"
},
@@ -3384,13 +3384,13 @@
}
},
"node_modules/@flowgram.ai/shortcuts-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.2.28.tgz",
- "integrity": "sha512-S3XkBmZnQ4FZ+A7sD56v6L2UFOhMSwqTmA4S1G8XqMvmbfHjUjR/OxojfQw/MSd5QNygD4ikBesPoj6FMP45FQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.3.2.tgz",
+ "integrity": "sha512-D5Z3CAd61c2e0bQ9n8ULFAK1yFqV9q7UpMQ89IAu+armcT6w0XORC0CP/c7ojoBvGKYaWMH1gZf0NlQHiT6CZQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2",
"styled-components": "^5"
@@ -3445,9 +3445,9 @@
}
},
"node_modules/@flowgram.ai/utils": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.2.28.tgz",
- "integrity": "sha512-HG4LpVo3gUcTp/tJCe72vZ9nByRXgkvp2wYyFFD3ZnGvEL8V3PIRwHGvBMP0UqgYBPHOZyqOZhOenP73msvkwg==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.3.2.tgz",
+ "integrity": "sha512-XVmiYft8JfrZsV5M2M8i2AMjhz6Fe4Euv0gOuLiSsZL1ZnR95IHzEHuvSd98of4srcUL+b/RcwB2BMXH+/EJ9w==",
"dependencies": {
"clsx": "^1.1.1",
"inversify": "^6.0.1",
@@ -3485,18 +3485,18 @@
}
},
"node_modules/@flowgram.ai/variable-core": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.2.28.tgz",
- "integrity": "sha512-Au6UCupM1d044w++GMv2HC4YGc2nsDNBdem71gjxcDLowGo9FDgKF1Fxh2E+UfCv9QfAcShJzCg0R4EtCpEsuQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.3.2.tgz",
+ "integrity": "sha512-95E7akQXzPItx7H/tKT4g5CZ0gg6OCsBou13OfotivZS1Yn5GxtqOeWGz7EHUZwurzB9w6o30VR6Y8vuk049Bg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
"fast-equals": "^2.0.0",
"inversify": "^6.0.1",
"lodash": "^4.17.21",
"nanoid": "^4.0.2",
"reflect-metadata": "~0.2.2",
- "rxjs": "^7.8.1"
+ "rxjs": "^7.8.2"
},
"peerDependencies": {
"react": ">=16.8",
@@ -3521,15 +3521,15 @@
}
},
"node_modules/@flowgram.ai/variable-layout": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.2.28.tgz",
- "integrity": "sha512-31mxxnLJFanmdMkMvl6WzGV+CO15gPT0jnMsf0Mqp+s7qadtwuHcwzsDEUU9RKR+d/oXuGBpNGrdtQJzBh2JMQ==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.3.2.tgz",
+ "integrity": "sha512-vX9lGLl5yY0VAYv5Bua3jqkh2M80+YB/4Qea0crkPAWzg1eVQcG4zbnR+Wc6c3wNz9cTpT+vq+2kiTsP5+95eQ==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/free-layout-core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
- "@flowgram.ai/variable-core": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/free-layout-core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
+ "@flowgram.ai/variable-core": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2",
"styled-components": "^5"
@@ -3584,16 +3584,16 @@
}
},
"node_modules/@flowgram.ai/variable-plugin": {
- "version": "0.2.28",
- "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.2.28.tgz",
- "integrity": "sha512-lPxcLNClR865dQMGAGy+NSbC+80mWbVXRolbN1ScIj7on9ypTtDs86VTdFGlKMzP8BSWIXM1Rjb6oSEShCxuJA==",
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.3.2.tgz",
+ "integrity": "sha512-Fm4DS/DrfcyKtvwUyoB4y5dBr8D/JWaSZjhu6kWee3HIV46uJLruufynMBQprWEgSMj9RyYFoFVQWH4hYisNWg==",
"dependencies": {
- "@flowgram.ai/core": "0.2.28",
- "@flowgram.ai/document": "0.2.28",
- "@flowgram.ai/free-layout-core": "0.2.28",
- "@flowgram.ai/utils": "0.2.28",
- "@flowgram.ai/variable-core": "0.2.28",
- "@flowgram.ai/variable-layout": "0.2.28",
+ "@flowgram.ai/core": "0.3.2",
+ "@flowgram.ai/document": "0.3.2",
+ "@flowgram.ai/free-layout-core": "0.3.2",
+ "@flowgram.ai/utils": "0.3.2",
+ "@flowgram.ai/variable-core": "0.3.2",
+ "@flowgram.ai/variable-layout": "0.3.2",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2",
"styled-components": "^5"
@@ -4028,9 +4028,9 @@
}
},
"node_modules/@rc-component/trigger": {
- "version": "2.2.7",
- "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.7.tgz",
- "integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.0.tgz",
+ "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
"dependencies": {
"@babel/runtime": "^7.23.2",
"@rc-component/portal": "^1.1.0",
@@ -5000,9 +5000,9 @@
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
- "version": "4.24.1",
- "resolved": "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.1.tgz",
- "integrity": "sha512-o1m1a8eUS3fWERMbDFvN8t8sZUFPgDKNemmlQ5Ot2vKm+Ax84lKP1dhEFgkiOaZ1bDHk4T5h6SjHuTghrJHKww==",
+ "version": "4.24.2",
+ "resolved": "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz",
+ "integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
@@ -5026,20 +5026,20 @@
}
},
"node_modules/@uiw/codemirror-theme-vscode": {
- "version": "4.24.1",
- "resolved": "https://registry.npmmirror.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.1.tgz",
- "integrity": "sha512-w2/eaPcYsHi03aWoKzVxIWv5dBDKW4YPnjwyYLgFY3uNdZmSfoNkFhqW7GJgNNF77ATixPq2wm9XqYeOTtvuww==",
+ "version": "4.24.2",
+ "resolved": "https://registry.npmmirror.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.2.tgz",
+ "integrity": "sha512-5rLvAMKNjqtHSy3BZnI1Q4ZLqUDDJC5fKOp8Mv5VjPqEmAxEAIGrIXBnhAFlRg5VjHbHEMtM1O5ugL3IeHoBdQ==",
"dependencies": {
- "@uiw/codemirror-themes": "4.24.1"
+ "@uiw/codemirror-themes": "4.24.2"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
}
},
"node_modules/@uiw/codemirror-themes": {
- "version": "4.24.1",
- "resolved": "https://registry.npmmirror.com/@uiw/codemirror-themes/-/codemirror-themes-4.24.1.tgz",
- "integrity": "sha512-hduBbFNiWNW6nYa2/giKQ9YpzhWNw87BGpCjC+cXYMZ7bCD6q5DC6Hw+7z7ZwSzEaOQvV91lmirOjJ8hn9+pkg==",
+ "version": "4.24.2",
+ "resolved": "https://registry.npmmirror.com/@uiw/codemirror-themes/-/codemirror-themes-4.24.2.tgz",
+ "integrity": "sha512-0fQusJE08APL+1WM0xYqvampVM2RpABHjvZbcJWHdRc+teZbrP8907VW1ZX1tO/7/xgRRt4Fc3RHPCcTs7b0NQ==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@@ -5055,15 +5055,15 @@
}
},
"node_modules/@uiw/react-codemirror": {
- "version": "4.24.1",
- "resolved": "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.24.1.tgz",
- "integrity": "sha512-BivF4NLqbuBQK5gPVhSkOARi9nPXw8X5r25EnInPeY+I9l1dfEX8O9V6+0xHTlGHyUo0cNfGEF9t1KHEicUfJw==",
+ "version": "4.24.2",
+ "resolved": "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz",
+ "integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
- "@uiw/codemirror-extensions-basic-setup": "4.24.1",
+ "@uiw/codemirror-extensions-basic-setup": "4.24.2",
"codemirror": "^6.0.0"
},
"funding": {
@@ -5075,8 +5075,8 @@
"@codemirror/theme-one-dark": ">=6.0.0",
"@codemirror/view": ">=6.0.0",
"codemirror": ">=6.0.0",
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0"
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0"
}
},
"node_modules/@umijs/route-utils": {
@@ -5472,9 +5472,9 @@
}
},
"node_modules/antd": {
- "version": "5.26.6",
- "resolved": "https://registry.npmmirror.com/antd/-/antd-5.26.6.tgz",
- "integrity": "sha512-k8ipeT+UL2tP/x4jHTXElScAxsD94JgrIEeGHj80nNO4dL9hqcmaOUBpHo3ieCf6MFjhS7gLUthysQeP6e7DUg==",
+ "version": "5.26.7",
+ "resolved": "https://registry.npmmirror.com/antd/-/antd-5.26.7.tgz",
+ "integrity": "sha512-iCyXN6+i2CUVEOSzzJKfbKeg115qoJhGvSkCh5uzAf9hANwHUOJQhsMn+KtN+Lx/2NQ6wfM7nGZ+7NPNO5Pn1w==",
"dependencies": {
"@ant-design/colors": "^7.2.1",
"@ant-design/cssinjs": "^1.23.0",
@@ -5487,7 +5487,7 @@
"@rc-component/mutate-observer": "^1.1.0",
"@rc-component/qrcode": "~1.0.0",
"@rc-component/tour": "~1.15.1",
- "@rc-component/trigger": "^2.2.7",
+ "@rc-component/trigger": "^2.3.0",
"classnames": "^2.5.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.11",
@@ -5517,7 +5517,7 @@
"rc-switch": "~4.1.0",
"rc-table": "~7.51.1",
"rc-tabs": "~15.6.1",
- "rc-textarea": "~1.10.0",
+ "rc-textarea": "~1.10.1",
"rc-tooltip": "~6.4.0",
"rc-tree": "~5.13.1",
"rc-tree-select": "~5.27.0",
@@ -6151,6 +6151,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
@@ -6631,9 +6648,9 @@
}
},
"node_modules/eslint-plugin-better-tailwindcss": {
- "version": "3.7.2",
- "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.2.tgz",
- "integrity": "sha512-uJnoDGmUPtxHHcIW5njEoh1CYlq39E4J2pWq+D7REDGhW41arSCeeaiiwAT4Ok04kXNvmFBiYmC6Jdd6Bz7o2Q==",
+ "version": "3.7.3",
+ "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.3.tgz",
+ "integrity": "sha512-7I2MM1/OetBamiNWGkAetzQtvph6N7B6Iy5nIU9srFkHXnqR5G6lFX7ZT+JZmLrirrYSn8E0THeJg9QTQaI/kQ==",
"dev": true,
"dependencies": {
"@eslint/css-tree": "^3.6.2",
@@ -9326,9 +9343,9 @@
}
},
"node_modules/rc-textarea": {
- "version": "1.10.0",
- "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.0.tgz",
- "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==",
+ "version": "1.10.2",
+ "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.2.tgz",
+ "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
"dependencies": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1",
@@ -11146,17 +11163,17 @@
}
},
"node_modules/zod": {
- "version": "4.0.10",
- "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.10.tgz",
- "integrity": "sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==",
+ "version": "4.0.14",
+ "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.14.tgz",
+ "integrity": "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
- "version": "5.0.6",
- "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.6.tgz",
- "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==",
+ "version": "5.0.7",
+ "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.7.tgz",
+ "integrity": "sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==",
"engines": {
"node": ">=12.20.0"
},
diff --git a/ui/package.json b/ui/package.json
index 8150cac20..4bfdbc5e7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,6 @@
{
- "name": "ui",
+ "name": "@certimate/webui",
"private": true,
- "version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
@@ -15,13 +14,14 @@
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.2",
"@codemirror/legacy-modes": "^6.5.1",
- "@flowgram.ai/fixed-layout-editor": "^0.2.28",
+ "@flowgram.ai/fixed-layout-editor": "^0.3.2",
+ "@flowgram.ai/document": "^0.3.2",
"@tabler/icons-react": "^3.34.1",
- "@uiw/codemirror-extensions-basic-setup": "^4.24.1",
- "@uiw/codemirror-theme-vscode": "^4.24.1",
- "@uiw/react-codemirror": "^4.24.1",
+ "@uiw/codemirror-extensions-basic-setup": "^4.24.2",
+ "@uiw/codemirror-theme-vscode": "^4.24.2",
+ "@uiw/react-codemirror": "^4.24.2",
"ahooks": "^3.9.0",
- "antd": "^5.26.6",
+ "antd": "^5.26.7",
"antd-zod": "^7.0.0",
"clsx": "^2.1.1",
"cron-parser": "^5.3.0",
@@ -38,8 +38,8 @@
"react-i18next": "^15.6.1",
"react-router-dom": "^7.7.1",
"tailwind-merge": "^3.3.1",
- "zod": "^4.0.10",
- "zustand": "^5.0.6"
+ "zod": "^4.0.14",
+ "zustand": "^5.0.7"
},
"devDependencies": {
"@eslint/js": "^9.32.0",
@@ -56,7 +56,7 @@
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-better-tailwindcss": "^3.7.2",
+ "eslint-plugin-better-tailwindcss": "^3.7.3",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react-hooks": "^5.2.0",
diff --git a/ui/src/components/MultipleSplitValueInput.tsx b/ui/src/components/MultipleSplitValueInput.tsx
index 2f571aabd..d161c8474 100644
--- a/ui/src/components/MultipleSplitValueInput.tsx
+++ b/ui/src/components/MultipleSplitValueInput.tsx
@@ -2,7 +2,7 @@
import { IconList } from "@tabler/icons-react";
import { useControllableValue } from "ahooks";
import { Button, Form, Input, type InputProps, Space } from "antd";
-import { nanoid } from "nanoid";
+import { nanoid } from "nanoid/non-secure";
import { useAntdForm } from "@/hooks";
import ModalForm from "./ModalForm";
diff --git a/ui/src/components/workflow/WorkflowElementsContainer.tsx b/ui/src/components/workflow/WorkflowElementsContainer.tsx
index dcc83c724..bf189d3a0 100644
--- a/ui/src/components/workflow/WorkflowElementsContainer.tsx
+++ b/ui/src/components/workflow/WorkflowElementsContainer.tsx
@@ -30,15 +30,19 @@ const WorkflowElementsContainer = ({ className, style, disabled }: WorkflowEleme
-
-
-
-
} disabled={scale <= MIN_SCALE} onClick={() => setScale((s) => Math.max(MIN_SCALE, s - STEP_SCALE))} />
-
{Math.round(scale * 100)}%
-
} disabled={scale >= MAX_SCALE} onClick={() => setScale((s) => Math.min(MAX_SCALE, s + STEP_SCALE))} />
-
} onClick={() => setScale(1)} />
+
+
+
+
+
+ } disabled={scale <= MIN_SCALE} onClick={() => setScale((s) => Math.max(MIN_SCALE, s - STEP_SCALE))} />
+ {Math.round(scale * 100)}%
+ } disabled={scale >= MAX_SCALE} onClick={() => setScale((s) => Math.min(MAX_SCALE, s + STEP_SCALE))} />
+ } onClick={() => setScale(1)} />
+
+
-
+
);
diff --git a/ui/src/components/workflow/designer/Editor.tsx b/ui/src/components/workflow/designer/Editor.tsx
new file mode 100644
index 000000000..eed080e4f
--- /dev/null
+++ b/ui/src/components/workflow/designer/Editor.tsx
@@ -0,0 +1,119 @@
+import { useMemo, useRef } from "react";
+import {
+ ConstantKeys,
+ EditorRenderer,
+ FixedLayoutEditorProvider,
+ type FixedLayoutPluginContext,
+ type FixedLayoutProps,
+ type FlowDocumentJSON,
+ FlowTextKey,
+} from "@flowgram.ai/fixed-layout-editor";
+import "@flowgram.ai/fixed-layout-editor/index.css";
+import { useDeepCompareEffect } from "ahooks";
+import { theme } from "antd";
+
+import { getFlowComponents } from "./components";
+import NodeRender from "./NodeRender";
+import { getFlowNodeRegistries } from "./nodes";
+import { BlockNode } from "./nodes/_shared";
+import "./flowgram.css";
+
+export interface EditorProps {
+ className?: string;
+ style?: React.CSSProperties;
+ children?: React.ReactNode;
+ initialData?: FlowDocumentJSON;
+ readonly?: boolean;
+}
+
+const Editor = ({ className, style, children, initialData, readonly }: EditorProps) => {
+ const { token: themeToken } = theme.useToken();
+
+ const flowgramEditorRef = useRef(null);
+ const flowgramEditorProps = useMemo(
+ () => ({
+ initialData: initialData,
+
+ constants: {
+ [ConstantKeys.BASE_COLOR]: themeToken.colorBorder,
+ [ConstantKeys.BASE_ACTIVATED_COLOR]: themeToken.colorPrimary,
+ [ConstantKeys.NODE_SPACING]: 64,
+ [ConstantKeys.BRANCH_SPACING]: 64,
+ // [ConstantKeys.INLINE_BLOCKS_PADDING_TOP]: 48,
+ // [ConstantKeys.INLINE_BLOCKS_PADDING_BOTTOM]: 48,
+ },
+
+ background: {
+ backgroundColor: themeToken.colorBgContainer,
+ dotSize: 0,
+ },
+
+ playground: {
+ autoFocus: true,
+ autoResize: true,
+ preventGlobalGesture: true,
+ },
+
+ scroll: {
+ enableScrollLimit: true,
+ },
+
+ readonly: readonly,
+
+ nodeEngine: {
+ enable: true,
+ },
+
+ variableEngine: {
+ enable: true,
+ },
+
+ materials: {
+ components: getFlowComponents(),
+ renderTexts: {
+ [FlowTextKey.TRY_START_TEXT]: "Try",
+ [FlowTextKey.TRY_END_TEXT]: "Finally",
+ [FlowTextKey.CATCH_TEXT]: "Catch",
+ },
+ renderDefaultNode: NodeRender,
+ },
+
+ nodeRegistries: getFlowNodeRegistries(),
+
+ getNodeDefaultRegistry(type) {
+ return {
+ type,
+ meta: {
+ defaultExpanded: true,
+ },
+ formMeta: {
+ render: () => {type} ,
+ },
+ };
+ },
+
+ onAllLayersRendered: (ctx) => {
+ // 画布初始化后向下滚动一点,露出可能被 Alert 遮挡的部分
+ setTimeout(() => {
+ ctx.playground.config.scroll({ scrollY: -80 });
+ }, 1);
+ },
+ }),
+ [themeToken, initialData, readonly]
+ );
+
+ useDeepCompareEffect(() => {
+ flowgramEditorRef.current?.document?.fromJSON?.(initialData ?? {});
+ }, [initialData]);
+
+ return (
+
+
+
+ {children}
+
+
+ );
+};
+
+export default Editor;
diff --git a/ui/src/components/workflow/designer/NodeRender.tsx b/ui/src/components/workflow/designer/NodeRender.tsx
new file mode 100644
index 000000000..55a40ac03
--- /dev/null
+++ b/ui/src/components/workflow/designer/NodeRender.tsx
@@ -0,0 +1,27 @@
+import { type NodeRenderProps, useNodeRender } from "@flowgram.ai/fixed-layout-editor";
+
+export interface NodeProps extends NodeRenderProps {}
+
+const Node = (_: NodeProps) => {
+ const nodeRender = useNodeRender();
+
+ return (
+ {
+ nodeRender.startDrag(e);
+ e.stopPropagation();
+ }}
+ >
+ {nodeRender.form?.render()}
+
+ );
+};
+
+export default Node;
diff --git a/ui/src/components/workflow/designer/Toolbar.tsx b/ui/src/components/workflow/designer/Toolbar.tsx
new file mode 100644
index 000000000..72245af6f
--- /dev/null
+++ b/ui/src/components/workflow/designer/Toolbar.tsx
@@ -0,0 +1,78 @@
+import { useCallback, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { EditorState, FlowLayoutDefault, useClientContext, usePlaygroundTools, useRefresh } from "@flowgram.ai/fixed-layout-editor";
+import { IconHandStop, IconLayoutCards, IconMaximize, IconMinus, IconPlus, IconPointer } from "@tabler/icons-react";
+import { Button, Tooltip } from "antd";
+
+import { mergeCls } from "@/utils/css";
+
+export interface ToolbarProps {
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const Toolbar = ({ className, style }: ToolbarProps) => {
+ const { t } = useTranslation();
+
+ const ctx = useClientContext();
+ const { playground } = ctx;
+
+ const tools = usePlaygroundTools({ minZoom: 0.1, maxZoom: 3, padding: 48 });
+
+ const refresh = useRefresh();
+ useEffect(() => {
+ const disposable = playground.config.onReadonlyOrDisabledChange(() => refresh());
+ return () => disposable.dispose();
+ }, [playground]);
+
+ const [isMouseFriendly, setIsMouseFriendly] = useState(playground.editorState.is(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id));
+
+ const handleToggleLayout = useCallback(() => {
+ if (tools.isVertical) {
+ tools.changeLayout(FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT);
+ } else {
+ tools.changeLayout(FlowLayoutDefault.VERTICAL_FIXED_LAYOUT);
+ }
+ }, [tools.isVertical]);
+
+ const handleToggleMouseFriendly = useCallback(() => {
+ if (isMouseFriendly) {
+ playground.editorState.changeState(EditorState.STATE_SELECT.id);
+ setIsMouseFriendly(false);
+ } else {
+ playground.editorState.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id);
+ setIsMouseFriendly(true);
+ }
+ }, [isMouseFriendly]);
+
+ return (
+
+
+
+ } onClick={() => tools.zoomout()} />
+
+
+ tools.updateZoom(1)}>
+ {Math.round(tools.zoom * 100)}%
+
+
+
+ } onClick={() => tools.zoomin()} />
+
+
+ } onClick={() => tools.fitView()} />
+
+
+
+ } onClick={handleToggleLayout} />
+
+
+
+ : } onClick={handleToggleMouseFriendly} />
+
+
+
+ );
+};
+
+export default Toolbar;
diff --git a/ui/src/components/workflow/designer/components/Adder.tsx b/ui/src/components/workflow/designer/components/Adder.tsx
new file mode 100644
index 000000000..d915ae6bb
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/Adder.tsx
@@ -0,0 +1,65 @@
+import { type AdderProps as FlowgramAdderProps, useClientContext } from "@flowgram.ai/fixed-layout-editor";
+
+import { IconPlus } from "@tabler/icons-react";
+import { Button, Dropdown } from "antd";
+
+import { getFlowNodeRegistries } from "../nodes";
+
+export interface AdderProps extends FlowgramAdderProps {}
+
+const Adder = ({ from, hoverActivated }: AdderProps) => {
+ const ctx = useClientContext();
+ const { operation, playground } = ctx;
+
+ const menuItems = getFlowNodeRegistries()
+ .filter((registry) => {
+ if (registry.canAdd != null) {
+ return registry.canAdd(ctx, from);
+ }
+ if (registry.meta?.addDisable != null) {
+ return registry.meta.addDisable;
+ }
+ return true;
+ })
+ .map((registry) => {
+ const Icon = registry.meta?.icon;
+
+ return {
+ key: registry.type,
+ label: registry.type,
+ icon: (
+
+
+
+ ),
+ onClick: () => {
+ const props = registry.onAdd!(ctx, from);
+ const block = operation.addFromNode(from, {
+ ...props,
+ blocks: props?.blocks ?? [],
+ });
+
+ setTimeout(() => {
+ playground.scrollToView({
+ bounds: block.bounds,
+ scrollToCenter: true,
+ });
+ }, 1);
+ },
+ };
+ });
+
+ return playground.config.readonlyOrDisabled ? null : (
+
+
+ {hoverActivated ? (
+ } shape="circle" size="small" type="primary" />
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default Adder;
diff --git a/ui/src/components/workflow/designer/components/BranchAdder.tsx b/ui/src/components/workflow/designer/components/BranchAdder.tsx
new file mode 100644
index 000000000..b2ca2c6db
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/BranchAdder.tsx
@@ -0,0 +1,62 @@
+import { useTranslation } from "react-i18next";
+import { FlowNodeBaseType, type FlowNodeEntity, type AdderProps as FlowgramAdderProps, useClientContext } from "@flowgram.ai/fixed-layout-editor";
+import { Button } from "antd";
+import { nanoid } from "nanoid";
+
+import { BranchBlockNodeRegistry } from "../nodes/ConditionNode";
+import { CatchBlockNodeRegistry } from "../nodes/TryCatchNode";
+import { NodeType } from "../nodes/typings";
+
+export interface BranchAdderProps extends FlowgramAdderProps {}
+
+const BranchAdder = ({ node }: BranchAdderProps) => {
+ const { t } = useTranslation();
+
+ const ctx = useClientContext();
+ const { operation, playground } = ctx;
+
+ const handleAddBranch = () => {
+ let block: FlowNodeEntity;
+ switch (node.flowNodeType) {
+ case NodeType.Condition:
+ block = operation.addBlock(node, BranchBlockNodeRegistry.onAdd!(ctx, node));
+ break;
+
+ case NodeType.TryCatch:
+ block = operation.addBlock(node, CatchBlockNodeRegistry.onAdd!(ctx, node));
+ break;
+
+ default:
+ console.warn(`[certimate] unsupported node type for adding branch: '${node.flowNodeType}'`);
+ block = operation.addBlock(node, {
+ id: nanoid(),
+ type: FlowNodeBaseType.BLOCK,
+ data: {
+ name: "Branch",
+ },
+ });
+ break;
+ }
+
+ setTimeout(() => {
+ playground.scrollToView({
+ bounds: block.bounds,
+ scrollToCenter: true,
+ });
+ }, 1);
+ };
+
+ return playground.config.readonlyOrDisabled ? null : (
+ node.firstChild?.renderData?.toggleMouseEnter()}
+ onMouseLeave={() => node.firstChild?.renderData?.toggleMouseLeave()}
+ >
+
+ {t("workflow_node.action.add_branch")}
+
+
+ );
+};
+
+export default BranchAdder;
diff --git a/ui/src/components/workflow/designer/components/Collapse.tsx b/ui/src/components/workflow/designer/components/Collapse.tsx
new file mode 100644
index 000000000..28560fd96
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/Collapse.tsx
@@ -0,0 +1,9 @@
+import { type CollapseProps as FlowgramCollapseProps } from "@flowgram.ai/fixed-layout-editor";
+
+export interface CollapseProps extends FlowgramCollapseProps {}
+
+const Collapse = (_: CollapseProps) => {
+ return null;
+};
+
+export default Collapse;
diff --git a/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx b/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx
new file mode 100644
index 000000000..f4543ef65
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx
@@ -0,0 +1,14 @@
+import { type DragNodeProps as FlowgramDragNodeProps } from "@flowgram.ai/fixed-layout-editor";
+import { IconGradienter } from "@tabler/icons-react";
+
+const DragHighlightAdder = (_: FlowgramDragNodeProps) => {
+ return (
+
+ );
+};
+
+export default DragHighlightAdder;
diff --git a/ui/src/components/workflow/designer/components/DragNode.tsx b/ui/src/components/workflow/designer/components/DragNode.tsx
new file mode 100644
index 000000000..e6a2ddd64
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/DragNode.tsx
@@ -0,0 +1,26 @@
+import { type FlowNodeEntity, type DragNodeProps as FlowgramDragNodeProps, getNodeForm } from "@flowgram.ai/fixed-layout-editor";
+import { Badge, Card } from "antd";
+
+export interface DragNodeProps extends FlowgramDragNodeProps {
+ dragStart: FlowNodeEntity;
+ dragNodes: FlowNodeEntity[];
+}
+
+const DragNode = ({ dragStart, dragNodes }: DragNodeProps) => {
+ const count = (dragNodes || [])
+ .map((n) => (n.allCollapsedChildren.length ? n.allCollapsedChildren.filter((_n) => !_n.hidden).length : 1))
+ .reduce((acc, cur) => acc + cur, 0);
+ return (
+ 1 ? count : 0} size="small">
+
+
+
+
{dragStart ? getNodeForm(dragStart)?.getValueIn("name") || `#${dragStart?.id}` : "\u00A0"}
+
+
+
+
+ );
+};
+
+export default DragNode;
diff --git a/ui/src/components/workflow/designer/components/DraggingAdder.tsx b/ui/src/components/workflow/designer/components/DraggingAdder.tsx
new file mode 100644
index 000000000..97f52ed4e
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/DraggingAdder.tsx
@@ -0,0 +1,31 @@
+import { FlowDragLayer, type AdderProps as FlowgramAdderProps, usePlayground } from "@flowgram.ai/fixed-layout-editor";
+import { IconChevronsDown } from "@tabler/icons-react";
+
+export interface DraggingAdderProps extends FlowgramAdderProps {}
+
+const DraggingAdder = ({ from }: DraggingAdderProps) => {
+ const playground = usePlayground();
+
+ const layer = playground.getLayer(FlowDragLayer);
+ if (!layer) return <>>;
+ if (
+ layer.options.canDrop &&
+ !layer.options.canDrop({
+ dragNodes: layer.dragEntities ?? [],
+ dropNode: from,
+ isBranch: false,
+ })
+ ) {
+ return <>>;
+ }
+
+ return (
+
+ );
+};
+
+export default DraggingAdder;
diff --git a/ui/src/components/workflow/designer/components/Null.tsx b/ui/src/components/workflow/designer/components/Null.tsx
new file mode 100644
index 000000000..fe29349c2
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/Null.tsx
@@ -0,0 +1,11 @@
+import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor";
+
+export interface NullProps {
+ node: FlowNodeEntity;
+}
+
+const Null = (_: NullProps) => {
+ return null;
+};
+
+export default Null;
diff --git a/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx
new file mode 100644
index 000000000..77f68d38a
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx
@@ -0,0 +1,69 @@
+import { useState } from "react";
+import {
+ type CustomLabelProps,
+ FlowNodeRenderData,
+ FlowNodeTransformData,
+ FlowRendererRegistry,
+ FlowTextKey,
+ useBaseColor,
+} from "@flowgram.ai/fixed-layout-editor";
+
+export interface TryCatchCollapseProps extends CustomLabelProps {}
+
+const TryCatchCollapse = ({ node, ...props }: TryCatchCollapseProps) => {
+ const { baseColor, baseActivatedColor } = useBaseColor();
+
+ const nodeActivateData = node.getData(FlowNodeRenderData)!;
+ const nodeTransformData = node.getData(FlowNodeTransformData)!;
+
+ const [hoverActivated, setHoverActivated] = useState(false);
+
+ const handleMouseEnter = () => {
+ setHoverActivated(true);
+ nodeActivateData.activated = true;
+ };
+
+ const handleMouseLeave = () => {
+ setHoverActivated(false);
+ nodeActivateData.activated = false;
+ };
+
+ if (!nodeTransformData || !nodeTransformData.parent) {
+ return <>>;
+ }
+
+ const width = nodeTransformData.inputPoint.x - nodeTransformData.parent.inputPoint.x;
+ const height = 40;
+ return (
+
+
+ {node.getService(FlowRendererRegistry).getText(FlowTextKey.CATCH_TEXT)}
+
+
+ {/* {renderCollapse()} */}
+
+ );
+};
+
+export default TryCatchCollapse;
diff --git a/ui/src/components/workflow/designer/components/index.ts b/ui/src/components/workflow/designer/components/index.ts
new file mode 100644
index 000000000..90fb868a3
--- /dev/null
+++ b/ui/src/components/workflow/designer/components/index.ts
@@ -0,0 +1,30 @@
+import { FlowRendererKey } from "@flowgram.ai/fixed-layout-editor";
+
+import Adder from "./Adder";
+import BranchAdder from "./BranchAdder";
+import Collapse from "./Collapse";
+import DraggingAdder from "./DraggingAdder";
+import DragHighlightAdder from "./DragHighlightAdder";
+import DragNode from "./DragNode";
+import Null from "./Null";
+import TryCatchCollapse from "./TryCatchCollapse";
+
+export const getFlowComponents = () => {
+ return {
+ [FlowRendererKey.ADDER]: Adder,
+ [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
+ [FlowRendererKey.SLOT_ADDER]: Null,
+
+ [FlowRendererKey.COLLAPSE]: Collapse,
+ [FlowRendererKey.TRY_CATCH_COLLAPSE]: TryCatchCollapse,
+ [FlowRendererKey.SLOT_COLLPASE]: Null,
+
+ [FlowRendererKey.DRAG_NODE]: DragNode,
+ [FlowRendererKey.DRAG_HIGHLIGHT_ADDER]: DragHighlightAdder,
+ [FlowRendererKey.DRAG_BRANCH_HIGHLIGHT_ADDER]: DragHighlightAdder,
+ [FlowRendererKey.DRAGGABLE_ADDER]: DraggingAdder,
+
+ [FlowRendererKey.CONTEXT_MENU_POPOVER]: Null,
+ [FlowRendererKey.SELECTOR_BOX_POPOVER]: Null,
+ };
+};
diff --git a/ui/src/components/workflow/designer/flowgram.css b/ui/src/components/workflow/designer/flowgram.css
new file mode 100644
index 000000000..0e4ac1936
--- /dev/null
+++ b/ui/src/components/workflow/designer/flowgram.css
@@ -0,0 +1,22 @@
+/* 隐藏 flowgram 的标签背景 */
+/* flowgram 暂未提供相关配置项,这里需与 antd.colorBgContainer 保持一致 */
+:root {
+ --g-editor-background: #fff;
+}
+.dark {
+ --g-editor-background: #262c2d;
+}
+
+/* 隐藏 flowgram 的折叠按钮 */
+.flow-canvas-collapse-adder > .flow-canvas-collapse {
+ display: hidden !important;
+ position: absolute !important;
+ top: -99999px !important;
+ left: -99999px !important;
+}
+
+/* 控制 flowgram 线条样式 */
+/* flowgram 暂未提供相关配置项 */
+.flow-lines-container {
+ stroke-width: 1.5px;
+}
diff --git a/ui/src/components/workflow/designer/nodes/BusinessNode.tsx b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx
new file mode 100644
index 000000000..af9288615
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx
@@ -0,0 +1,206 @@
+import { getI18n } from "react-i18next";
+import { IconCloudUpload, IconContract, IconDeviceDesktopSearch, IconPackage, IconSend } from "@tabler/icons-react";
+import { Avatar, Typography } from "antd";
+import { nanoid } from "nanoid";
+
+import { deploymentProvidersMap, notificationProvidersMap } from "@/domain/provider";
+
+import { BaseNode } from "./_shared";
+import { type NodeRegistry, NodeType } from "./typings";
+
+export const BizApplyNodeRegistry: NodeRegistry = {
+ type: NodeType.BizApply,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.apply.help"),
+
+ icon: IconContract,
+ iconColor: "#fff",
+ iconBgColor: "#5b65f5",
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldDomains = form.getValueIn("config.domains");
+
+ return {fieldDomains ? fieldDomains.split(";").join("; ") : t("workflow.detail.design.nodes.placeholder")} ;
+ },
+ },
+
+ onAdd: () => {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BizApply,
+ data: {
+ name: t("workflow_node.apply.default_name"),
+ },
+ };
+ },
+};
+
+export const BizUploadNodeRegistry: NodeRegistry = {
+ type: NodeType.BizUpload,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.upload.help"),
+
+ icon: IconCloudUpload,
+ iconColor: "#fff",
+ iconBgColor: "#5b65f5",
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldDomains = form.getValueIn("config.domains");
+
+ return {fieldDomains ? fieldDomains.split(";").join("; ") : t("workflow.detail.design.nodes.placeholder")} ;
+ },
+ },
+
+ onAdd: () => {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BizUpload,
+ data: {
+ name: t("workflow_node.upload.default_name"),
+ },
+ };
+ },
+};
+
+export const BizMonitorNodeRegistry: NodeRegistry = {
+ type: NodeType.BizMonitor,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.monitor.help"),
+
+ icon: IconDeviceDesktopSearch,
+ iconColor: "#fff",
+ iconBgColor: "#5b65f5",
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldDomain = form.getValueIn("config.domain");
+ const fieldHost = form.getValueIn("config.host");
+
+ return {fieldDomain || fieldHost ? fieldDomain || fieldHost : t("workflow.detail.design.nodes.placeholder")} ;
+ },
+ },
+
+ onAdd: () => {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BizMonitor,
+ data: {
+ name: t("workflow_node.monitor.default_name"),
+ },
+ };
+ },
+};
+
+export const BizDeployNodeRegistry: NodeRegistry = {
+ type: NodeType.BizDeploy,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.deploy.help"),
+
+ icon: IconPackage,
+ iconColor: "#fff",
+ iconBgColor: "#5b65f5",
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldProvider = form.getValueIn("config.provider");
+
+ return (
+
+
+ {fieldProvider ? (
+ <>
+
{t(deploymentProvidersMap.get(fieldProvider)?.name ?? "")}
+
+ >
+ ) : (
+ t("workflow.detail.design.nodes.placeholder")
+ )}
+
+
+ );
+ },
+ },
+
+ onAdd: () => {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BizMonitor,
+ data: {
+ name: t("workflow_node.deploy.default_name"),
+ },
+ };
+ },
+};
+
+export const BizNotifyNodeRegistry: NodeRegistry = {
+ type: NodeType.BizNotify,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.notify.help"),
+
+ icon: IconSend,
+ iconColor: "#fff",
+ iconBgColor: "#0693d4",
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldProvider = form.getValueIn("config.provider");
+
+ return (
+
+
+ {fieldProvider ? (
+ <>
+
{t(notificationProvidersMap.get(fieldProvider)?.name ?? "")}
+
+ >
+ ) : (
+ t("workflow.detail.design.nodes.placeholder")
+ )}
+
+
+ );
+ },
+ },
+
+ onAdd: () => {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BizNotify,
+ data: {
+ name: t("workflow_node.notify.default_name"),
+ },
+ };
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx
new file mode 100644
index 000000000..71f106c9d
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx
@@ -0,0 +1,108 @@
+import { getI18n } from "react-i18next";
+import { FlowNodeBaseType, FlowNodeSplitType, ValidateTrigger } from "@flowgram.ai/fixed-layout-editor";
+import { IconFilter, IconFilterFilled, IconSitemap } from "@tabler/icons-react";
+import { nanoid } from "nanoid";
+
+import { BaseNode, BlockNode } from "./_shared";
+import { type NodeRegistry, NodeType } from "./typings";
+
+export const ConditionNodeRegistry: NodeRegistry = {
+ type: NodeType.Condition,
+
+ extend: FlowNodeSplitType.DYNAMIC_SPLIT,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.condition.help"),
+
+ icon: IconSitemap,
+ iconColor: "#fff",
+ iconBgColor: "#373c43",
+
+ expandable: false,
+ },
+
+ formMeta: {
+ validateTrigger: ValidateTrigger.onChange,
+ render: () => {
+ return ;
+ },
+ },
+
+ onAdd() {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.Condition,
+ data: {
+ name: t("workflow_node.condition.default_name"),
+ },
+ blocks: [
+ {
+ id: nanoid(),
+ type: NodeType.BranchBlock,
+ blocks: [],
+ data: {
+ name: t("workflow_node.branch_block.default_name") + " 1",
+ },
+ },
+ {
+ id: nanoid(),
+ type: NodeType.BranchBlock,
+ data: {
+ name: t("workflow_node.branch_block.default_name") + " 2",
+ },
+ },
+ ],
+ };
+ },
+};
+
+export const BranchBlockNodeRegistry: NodeRegistry = {
+ type: NodeType.BranchBlock,
+
+ extend: FlowNodeBaseType.BLOCK,
+
+ meta: {
+ addDisable: true,
+ copyDisable: true,
+ },
+
+ formMeta: {
+ validateTrigger: ValidateTrigger.onChange,
+ render: ({ form }) => {
+ const fieldExpr = form.getValueIn("config.expression");
+
+ return (
+
+
+
+ {fieldExpr ? : }
+
+
{form.getValueIn("name") || "\u00A0"}
+
+
+ );
+ },
+ },
+
+ canAdd: () => {
+ return false;
+ },
+
+ canDelete: (_, node) => {
+ return node.parent!.blocks.length >= 2;
+ },
+
+ onAdd() {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.BranchBlock,
+ data: {
+ name: t("workflow_node.branch_block.default_name"),
+ },
+ };
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/EndNode.tsx b/ui/src/components/workflow/designer/nodes/EndNode.tsx
new file mode 100644
index 000000000..c47b23755
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/EndNode.tsx
@@ -0,0 +1,75 @@
+import { getI18n } from "react-i18next";
+import { FlowNodeBaseType } from "@flowgram.ai/fixed-layout-editor";
+import { IconLogout } from "@tabler/icons-react";
+import { nanoid } from "nanoid";
+
+import { BaseNode } from "./_shared";
+import { type NodeRegistry, NodeType } from "./typings";
+
+export const EndNodeRegistry: NodeRegistry = {
+ type: NodeType.End,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.end.help"),
+
+ icon: IconLogout,
+ iconColor: "#fff",
+ iconBgColor: "#336df4",
+
+ isNodeEnd: true,
+
+ expandable: false,
+ selectable: false,
+
+ copyDisable: true,
+ },
+
+ formMeta: {
+ render: () => {
+ return ;
+ },
+ },
+
+ canAdd(_, from) {
+ // You can only add to the last node of the branch
+ if (!from.isLast) return false;
+
+ /**
+ * condition
+ * blockIcon
+ * inlineBlocks
+ * block1
+ * blockOrderIcon
+ * <---- [add end]
+ * block2
+ * blockOrderIcon
+ * end
+ */
+ // originParent can determine whether it is condition , and then determine whether it is the last one
+ // https://github.com/bytedance/flowgram.ai/pull/146
+ if (from.parent && from.parent.parent?.flowNodeType === FlowNodeBaseType.INLINE_BLOCKS && from.parent.originParent && !from.parent.originParent.isLast) {
+ const allBranches = from.parent.parent!.blocks;
+ // Determine whether the last node of all branch is end, All branches are not allowed to be end
+ const branchEndCount = allBranches.filter((block) => block.blocks[block.blocks.length - 1]?.getNodeMeta().isNodeEnd).length;
+ return branchEndCount < allBranches.length - 1;
+ }
+
+ return true;
+ },
+
+ canDelete(ctx, node) {
+ return node.parent !== ctx.document.root;
+ },
+
+ onAdd() {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.End,
+ data: {
+ name: t("workflow_node.end.default_name"),
+ },
+ };
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/StartNode.tsx b/ui/src/components/workflow/designer/nodes/StartNode.tsx
new file mode 100644
index 000000000..5b14291b7
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/StartNode.tsx
@@ -0,0 +1,67 @@
+import { getI18n } from "react-i18next";
+import { IconRocket } from "@tabler/icons-react";
+import { Typography } from "antd";
+
+import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
+
+import { BaseNode } from "./_shared";
+import { type NodeRegistry, NodeType } from "./typings";
+
+export const StartNodeRegistry: NodeRegistry = {
+ type: NodeType.Start,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.start.help"),
+
+ icon: IconRocket,
+ iconColor: "#fff",
+ iconBgColor: "#ed6d0c",
+
+ isStart: true,
+
+ expandable: false,
+ selectable: false,
+
+ addDisable: true,
+ copyDisable: true,
+ deleteDisable: true,
+ },
+
+ formMeta: {
+ render: ({ form }) => {
+ const { t } = getI18n();
+
+ const fieldTrigger = form.getValueIn("config.trigger");
+ const fieldTriggerCron = form.getValueIn("config.triggerCron");
+
+ return (
+
+
+ {fieldTrigger ? (
+ <>
+
+ {fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED
+ ? t("workflow.props.trigger.scheduled")
+ : fieldTrigger === WORKFLOW_TRIGGERS.MANUAL
+ ? t("workflow.props.trigger.manual")
+ : "\u00A0"}
+
+
{fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED ? fieldTriggerCron || "\u00A0" : ""}
+ >
+ ) : (
+ t("workflow.detail.design.nodes.placeholder")
+ )}
+
+
+ );
+ },
+ },
+
+ canAdd: () => {
+ return false;
+ },
+
+ canDelete: () => {
+ return false;
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx
new file mode 100644
index 000000000..c4b0d7add
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx
@@ -0,0 +1,105 @@
+import { getI18n } from "react-i18next";
+import { ValidateTrigger } from "@flowgram.ai/fixed-layout-editor";
+import { IconArrowsSplit, IconCircleX } from "@tabler/icons-react";
+import { nanoid } from "nanoid";
+
+import { BaseNode, BlockNode } from "./_shared";
+import { type NodeRegistry, NodeType } from "./typings";
+
+export const TryCatchNodeRegistry: NodeRegistry = {
+ type: NodeType.TryCatch,
+
+ meta: {
+ helpText: getI18n().t("workflow_node.try_catch.help"),
+
+ icon: IconArrowsSplit,
+ iconColor: "#fff",
+ iconBgColor: "#373c43",
+
+ expandable: false,
+ },
+
+ formMeta: {
+ validateTrigger: ValidateTrigger.onChange,
+ render: () => {
+ return ;
+ },
+ },
+
+ onAdd() {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.TryCatch,
+ data: {
+ name: t("workflow_node.try_catch.default_name"),
+ },
+ blocks: [
+ {
+ id: nanoid(),
+ type: NodeType.TryBlock,
+ blocks: [],
+ },
+ {
+ id: nanoid(),
+ type: NodeType.CatchBlock,
+ blocks: [
+ {
+ id: nanoid(),
+ type: NodeType.End,
+ data: {
+ name: t("workflow_node.end.default_name"),
+ },
+ },
+ ],
+ data: {
+ name: t("workflow_node.catch_block.default_name"),
+ },
+ },
+ ],
+ };
+ },
+};
+
+export const CatchBlockNodeRegistry: NodeRegistry = {
+ type: NodeType.CatchBlock,
+
+ meta: {
+ addDisable: true,
+ copyDisable: true,
+ },
+
+ formMeta: {
+ validateTrigger: ValidateTrigger.onChange,
+ render: ({ form }) => {
+ return (
+
+
+
+
+
+
{form.getValueIn("name") || "\u00A0"}
+
+
+ );
+ },
+ },
+
+ canAdd: () => false,
+
+ canDelete: (_, node) => node.parent != null && node.parent.blocks.length >= 2,
+
+ onAdd() {
+ const { t } = getI18n();
+
+ return {
+ id: nanoid(),
+ type: NodeType.CatchBlock,
+ blocks: [],
+ data: {
+ name: t("workflow_node.catch_block.default_name"),
+ },
+ };
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/_example.ts b/ui/src/components/workflow/designer/nodes/_example.ts
new file mode 100644
index 000000000..cd6bc972f
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/_example.ts
@@ -0,0 +1,63 @@
+import { type FlowNodeRegistry } from "@flowgram.ai/fixed-layout-editor";
+import { nanoid } from "nanoid";
+
+/**
+ * 自定义节点注册
+ */
+export const nodeRegistry: FlowNodeRegistry = {
+ /**
+ * 自定义节点类型
+ */
+ type: "condition",
+
+ /**
+ * 自定义节点扩展:
+ * - loop: 扩展为循环节点
+ * - start: 扩展为开始节点
+ * - dynamicSplit: 扩展为分支节点
+ * - end: 扩展为结束节点
+ * - tryCatch: 扩展为 tryCatch 节点
+ * - break: 分支断开
+ * - default: 扩展为普通节点 (默认)
+ */
+ extend: "dynamicSplit",
+
+ /**
+ * 节点配置信息
+ */
+ meta: {
+ isStart: false, // 是否为开始节点
+ isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
+ draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
+ selectable: false, // 触发器等开始节点不能被框选
+ deleteDisable: true, // 禁止删除
+ copyDisable: true, // 禁止copy
+ addDisable: true, // 禁止添加
+ },
+
+ onAdd() {
+ return {
+ id: `condition_${nanoid(5)}`,
+ type: "condition",
+ data: {
+ title: "Condition",
+ },
+ blocks: [
+ {
+ id: nanoid(5),
+ type: "block",
+ data: {
+ title: "If_0",
+ },
+ },
+ {
+ id: nanoid(5),
+ type: "block",
+ data: {
+ title: "If_1",
+ },
+ },
+ ],
+ };
+ },
+};
diff --git a/ui/src/components/workflow/designer/nodes/_shared.tsx b/ui/src/components/workflow/designer/nodes/_shared.tsx
new file mode 100644
index 000000000..a9bd61bdb
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/_shared.tsx
@@ -0,0 +1,161 @@
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { useClientContext, useNodeRender } from "@flowgram.ai/fixed-layout-editor";
+import { IconCopy, IconDotsVertical, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react";
+import { Button, type ButtonProps, Card, Dropdown, type MenuProps, Popover, Tooltip, Typography, theme } from "antd";
+import { mergeCls } from "@/utils/css";
+
+import { type NodeRegistry } from "./typings";
+
+export const BaseNode = ({ className, style, children }: { className?: string; style?: React.CSSProperties; children?: React.ReactNode }) => {
+ const { token: themeToken } = theme.useToken();
+
+ const ctx = useClientContext();
+ const { playground } = ctx;
+
+ const nodeRender = useNodeRender();
+ const nodeRegistry = nodeRender.node.getNodeRegistry();
+
+ const NodeIcon = nodeRegistry.meta?.icon;
+ const renderNodeIcon = () => {
+ return NodeIcon == null ? null : (
+
+
+
+ );
+ };
+
+ return (
+
+
+
+ {nodeRegistry.meta?.helpText == null ? (
+ renderNodeIcon()
+ ) : (
+
} mouseEnterDelay={1}>
+ {renderNodeIcon()}
+
+ )}
+
+
+ {nodeRender.data?.name || "\u00A0"}
+
+ {children != null && (
+
+ {children}
+
+ )}
+
+
e.stopPropagation()}>
+
+
+
+
+ {!playground.config.readonlyOrDisabled && nodeRegistry.meta?.draggable === true && (
+
+
+
+ )}
+
+ );
+};
+
+export const BlockNode = ({ className, style, children }: { className?: string; style?: React.CSSProperties; children?: React.ReactNode }) => {
+ const ctx = useClientContext();
+ const { playground } = ctx;
+
+ const nodeRender = useNodeRender();
+ const nodeRegistry = nodeRender.node.getNodeRegistry();
+
+ return (
+ } placement="right">
+
+
+
+
{children ?? (nodeRender.data?.name || "\u00A0")}
+
+
+ {!playground.config.readonlyOrDisabled && nodeRegistry.meta?.draggable === true && (
+
+
+
+ )}
+
+
+ );
+};
+
+export const NodeMenuButton = ({ className, style, ...props }: ButtonProps) => {
+ const ctx = useClientContext();
+ const { playground } = ctx;
+
+ const menuItems = useNodeMenuItems();
+
+ return playground.config.readonlyOrDisabled ? null : (
+
+ } type="text" {...props} />
+
+ );
+};
+
+export const useNodeMenuItems = () => {
+ const { t } = useTranslation();
+
+ const ctx = useClientContext();
+ const { operation } = ctx;
+
+ const nodeRender = useNodeRender();
+ const nodeRegistry = nodeRender.node.getNodeRegistry();
+
+ const nodeDeleteDisabled = useMemo(() => {
+ if (nodeRegistry.canDelete != null) {
+ return !nodeRegistry.canDelete(ctx, nodeRender.node);
+ }
+ return nodeRegistry.meta!.deleteDisable;
+ }, [nodeRegistry, nodeRender.node]);
+
+ const menuItems = useMemo["items"]>(() => {
+ return [
+ {
+ key: "rename",
+ label: t("workflow_node.action.rename_node"),
+ icon: ,
+ onClick: () => {
+ operation.deleteNode(nodeRender.node);
+ alert("TODO: rename");
+ },
+ },
+ {
+ key: "duplicate",
+ label: t("workflow_node.action.duplicate_node"),
+ icon: ,
+ onClick: () => {
+ operation.deleteNode(nodeRender.node);
+ alert("TODO: duplicate");
+ },
+ },
+ {
+ type: "divider",
+ },
+ {
+ key: "remove",
+ label: t("workflow_node.action.remove_node"),
+ icon: ,
+ danger: true,
+ disabled: nodeDeleteDisabled,
+ onClick: () => {
+ operation.deleteNode(nodeRender.node);
+ alert("TODO: remove");
+ },
+ },
+ ];
+ }, [nodeRender.node, nodeDeleteDisabled]);
+
+ return menuItems;
+};
diff --git a/ui/src/components/workflow/designer/nodes/index.ts b/ui/src/components/workflow/designer/nodes/index.ts
new file mode 100644
index 000000000..049381575
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/index.ts
@@ -0,0 +1,23 @@
+import { BizApplyNodeRegistry, BizDeployNodeRegistry, BizMonitorNodeRegistry, BizNotifyNodeRegistry, BizUploadNodeRegistry } from "./BusinessNode";
+import { BranchBlockNodeRegistry, ConditionNodeRegistry } from "./ConditionNode";
+import { EndNodeRegistry } from "./EndNode";
+import { StartNodeRegistry } from "./StartNode";
+import { CatchBlockNodeRegistry, TryCatchNodeRegistry } from "./TryCatchNode";
+
+export const getFlowNodeRegistries = () => {
+ return [
+ StartNodeRegistry,
+ BizApplyNodeRegistry,
+ BizUploadNodeRegistry,
+ BizMonitorNodeRegistry,
+ BizDeployNodeRegistry,
+ BizNotifyNodeRegistry,
+ ConditionNodeRegistry,
+ BranchBlockNodeRegistry,
+ TryCatchNodeRegistry,
+ CatchBlockNodeRegistry,
+ EndNodeRegistry,
+ ];
+};
+
+export type * from "./typings";
diff --git a/ui/src/components/workflow/designer/nodes/typings.ts b/ui/src/components/workflow/designer/nodes/typings.ts
new file mode 100644
index 000000000..f2d9f4d58
--- /dev/null
+++ b/ui/src/components/workflow/designer/nodes/typings.ts
@@ -0,0 +1,55 @@
+import { FlowNodeBaseType } from "@flowgram.ai/document";
+import {
+ type FixedLayoutPluginContext,
+ type FlowNodeEntity,
+ type FlowNodeJSON,
+ type FlowNodeMeta,
+ type FlowNodeRegistry,
+ type FormMeta,
+ type FormRenderProps,
+} from "@flowgram.ai/fixed-layout-editor";
+
+export enum NodeType {
+ Start = FlowNodeBaseType.START,
+ End = FlowNodeBaseType.END,
+ Condition = "condition",
+ BranchBlock = "branchBlock",
+ TryCatch = "tryCatch",
+ TryBlock = "tryBlock",
+ CatchBlock = "catchBlock",
+ BizApply = "bizApply",
+ BizUpload = "bizUpload",
+ BizMonitor = "bizMonitor",
+ BizDeploy = "bizDeploy",
+ BizNotify = "bizNotify",
+}
+
+export interface NodeJSON extends FlowNodeJSON {
+ data: {
+ name?: string;
+ disabled?: boolean;
+ [key: string]: any;
+ };
+}
+
+export interface DocumentJSON {
+ nodes: NodeJSON[];
+}
+
+export interface NodeMeta extends FlowNodeMeta {
+ style?: React.CSSProperties;
+ helpText?: React.ReactNode;
+ icon?: React.Component;
+ iconColor?: string;
+ iconBgColor?: string;
+}
+
+export interface NodeRegistry extends FlowNodeRegistry {
+ meta?: FlowNodeMeta;
+ formMeta?: Omit, "render"> & {
+ render: (props: FormRenderProps) => React.ReactElement;
+ };
+ canAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
+ canDelete?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
+ onAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => FlowNodeJSON;
+}
diff --git a/ui/src/components/workflow/node/_SharedNode.tsx b/ui/src/components/workflow/node/_SharedNode.tsx
index 7d7106086..5c7e853f3 100644
--- a/ui/src/components/workflow/node/_SharedNode.tsx
+++ b/ui/src/components/workflow/node/_SharedNode.tsx
@@ -253,8 +253,8 @@ const SharedNodeBlock = ({ children, node, disabled, onClick }: SharedNodeBlockP
content={ } variant="text" />} />}
placement="rightTop"
>
-
-
+
+
resolve(void 0),
onCancel: () => reject(),
});
diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json
index b7ce41366..bc6e8b025 100644
--- a/ui/src/i18n/locales/en/nls.workflow.json
+++ b/ui/src/i18n/locales/en/nls.workflow.json
@@ -28,7 +28,7 @@
"workflow.action.rollback.button": "Rollback",
"workflow.action.rollback.modal.title": "Rollback changes",
"workflow.action.rollback.modal.content": "Are you sure to rollback your changes to the last published version?",
- "workflow.action.run.button": "Run at once",
+ "workflow.action.run.button": "Run",
"workflow.action.run.modal.title": "Run workflow",
"workflow.action.run.modal.content": "You have unpublished changes. Do you really want to run this workflow based on the last published version?",
"workflow.action.run.prompt": "Running... Please check the history later",
@@ -60,8 +60,18 @@
"workflow.detail.baseinfo.name.placeholder": "Please enter workflow name",
"workflow.detail.baseinfo.description.placeholder": "Please enter workflow description",
- "workflow.detail.design.tab": "Orchestration",
- "workflow.detail.design_legacy.tab": "Orchestration (Legacy)",
- "workflow.detail.design.draft.alert": "The orchestration is not released yet.",
+ "workflow.detail.design_legacy.tab": "Orchestration (legacy)",
+ "workflow.detail.design.tab": "Orchestration (TODO)",
+ "workflow.detail.design.toolbar.zoom": "Zoom",
+ "workflow.detail.design.toolbar.zoomin": "Zoom in",
+ "workflow.detail.design.toolbar.zoomout": "Zoom out",
+ "workflow.detail.design.toolbar.auto_fit": "Auto fit view",
+ "workflow.detail.design.toolbar.horizontal_layout": "Horizontal Layout",
+ "workflow.detail.design.toolbar.vertical_layout": "Vertical Layout",
+ "workflow.detail.design.toolbar.hand_mode": "Hand mode",
+ "workflow.detail.design.toolbar.pointer_mode": "Pointer mode",
+ "workflow.detail.design.nodes.placeholder": "Please configure",
+ "workflow.detail.design.unpublished_draft.alert": "The orchestration is not released yet.",
+ "workflow.detail.design.unsaved_changes.confirm": "You have unsaved changes. Do you really want to close the panel and drop those changes?",
"workflow.detail.runs.tab": "History runs"
}
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index c5151c593..6dd7d89c5 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -9,9 +9,8 @@
"workflow_node.action.duplicate_branch": "Duplicate branch",
"workflow_node.action.remove_branch": "Delete branch",
- "workflow_node.unsaved_changes.confirm": "You have unsaved changes. Do you really want to close the panel and drop those changes?",
-
"workflow_node.start.label": "Start",
+ "workflow_node.start.help": "Define the trigger for workflow initiation. A workflow can only have one start node.",
"workflow_node.start.default_name": "Start",
"workflow_node.start.form.trigger.label": "Trigger",
"workflow_node.start.form.trigger.placeholder": "Please select trigger",
@@ -24,7 +23,8 @@
"workflow_node.start.form.trigger_cron.extra": "Expected execution time for the last 5 times:",
"workflow_node.start.form.trigger_cron.guide": "Tips: If you have multiple workflows, it is recommended to set them to run at different times of the day instead of always running at a specific time. And please don't always set it to midnight every day to avoid spikes in traffic. Reference links: 1. Let’s Encrypt rate limits 2. Why should my Let’s Encrypt (ACME) client run at a random time? ",
- "workflow_node.apply.label": "Obtain certificate",
+ "workflow_node.apply.label": "Request certificate",
+ "workflow_node.apply.help": "Apply for SSL certificate issuance from the certificate authority.",
"workflow_node.apply.default_name": "Application",
"workflow_node.apply.form.domains.label": "Domains",
"workflow_node.apply.form.domains.placeholder": "Please enter domains (separated by semicolons)",
@@ -104,6 +104,7 @@
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "Be careful not to exceed the validity period limit of the issued certificate, otherwise the certificate may never be renewed.",
"workflow_node.upload.label": "Upload certificate",
+ "workflow_node.upload.help": "Upload the user's existing SSL certificate.",
"workflow_node.upload.default_name": "Uploading",
"workflow_node.upload.form.domains.label": "Domains",
"workflow_node.upload.form.domains.placholder": "Please select certificate file",
@@ -112,7 +113,21 @@
"workflow_node.upload.form.private_key.label": "Private key (PEM format)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
+ "workflow_node.monitor.label": "Monitor certificate",
+ "workflow_node.monitor.help": "Obtain the SSL certificate of the website through HTTPS protocol.",
+ "workflow_node.monitor.default_name": "Monitoring",
+ "workflow_node.monitor.form.guide": "Tips: Certimate will send a HEAD request to the target address to obtain the certificate. Please ensure that the address is accessible through HTTPS protocol.",
+ "workflow_node.monitor.form.host.label": "Host",
+ "workflow_node.monitor.form.host.placeholder": "Please enter host",
+ "workflow_node.monitor.form.port.label": "Port",
+ "workflow_node.monitor.form.port.placeholder": "Please enter port",
+ "workflow_node.monitor.form.domain.label": "Domain (Optional)",
+ "workflow_node.monitor.form.domain.placeholder": "Please enter domain name",
+ "workflow_node.monitor.form.request_path.label": "Request path (Optional)",
+ "workflow_node.monitor.form.request_path.placeholder": "Please enter request path",
+
"workflow_node.deploy.label": "Deploy certificate",
+ "workflow_node.deploy.help": "Invoke the APIs of the service provider to deploy the SSL certificate.",
"workflow_node.deploy.default_name": "Deployment",
"workflow_node.deploy.form.provider.label": "Deploy target",
"workflow_node.deploy.form.provider.placeholder": "Please select deploy target",
@@ -938,19 +953,8 @@
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "skip",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "not skip",
- "workflow_node.monitor.label": "Monitor certificate",
- "workflow_node.monitor.default_name": "Monitoring",
- "workflow_node.monitor.form.guide": "Tips: Certimate will send a HEAD request to the target address to obtain the certificate. Please ensure that the address is accessible through HTTPS protocol.",
- "workflow_node.monitor.form.host.label": "Host",
- "workflow_node.monitor.form.host.placeholder": "Please enter host",
- "workflow_node.monitor.form.port.label": "Port",
- "workflow_node.monitor.form.port.placeholder": "Please enter port",
- "workflow_node.monitor.form.domain.label": "Domain (Optional)",
- "workflow_node.monitor.form.domain.placeholder": "Please enter domain name",
- "workflow_node.monitor.form.request_path.label": "Request path (Optional)",
- "workflow_node.monitor.form.request_path.placeholder": "Please enter request path",
-
"workflow_node.notify.label": "Send notification",
+ "workflow_node.notify.help": "Invoke the APIs of the service provider to push message notifications.",
"workflow_node.notify.default_name": "Notification",
"workflow_node.notify.form.subject.label": "Subject",
"workflow_node.notify.form.subject.placeholder": "Please enter subject",
@@ -989,13 +993,11 @@
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.on": "skip",
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.off": "not skip",
- "workflow_node.end.label": "End",
- "workflow_node.end.default_name": "End",
-
"workflow_node.branch.label": "Parallel/Conditional branch",
"workflow_node.branch.default_name": "Branch",
"workflow_node.condition.label": "Branch",
+ "workflow_node.condition.help": "When the specified conditions are met, enter the corresponding branch. The failure of a node in a certain branch does not affect the continuation of parallel branch execution.",
"workflow_node.condition.default_name": "Branch",
"workflow_node.condition.default_name.template_certtest_on_expire_soon": "If the certificate will expire soon ...",
"workflow_node.condition.default_name.template_certtest_on_expired": "If the certificate has expired ...",
@@ -1021,6 +1023,9 @@
"workflow_node.condition.form.expression.value.option.false.label": "False",
"workflow_node.condition.form.expression.add_condition.button": "Add condition",
+ "workflow_node.branch_block.label": "Branch",
+ "workflow_node.branch_block.default_name": "Branch",
+
"workflow_node.execute_result_branch.label": "Execution result branch",
"workflow_node.execute_result_branch.default_name": "Branch",
@@ -1028,5 +1033,16 @@
"workflow_node.execute_success.default_name": "On Succeeded",
"workflow_node.execute_failure.label": "If the previous node failed ...",
- "workflow_node.execute_failure.default_name": "On Failed"
+ "workflow_node.execute_failure.default_name": "On Failed",
+
+ "workflow_node.try_catch.label": "Execution result branch",
+ "workflow_node.try_catch.help": "Attempt to execute subsequent nodes, and when any node fails to execute, interrupt and enter the execution failure branch.",
+ "workflow_node.try_catch.default_name": "Try...",
+
+ "workflow_node.catch_block.label": "Execution failure branch",
+ "workflow_node.catch_block.default_name": "On failed...",
+
+ "workflow_node.end.label": "End",
+ "workflow_node.end.help": "Suspend the workflow and exit. Usually the last node of a workflow.",
+ "workflow_node.end.default_name": "End"
}
diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json
index 470d5756d..591642fa3 100644
--- a/ui/src/i18n/locales/zh/nls.certificate.json
+++ b/ui/src/i18n/locales/zh/nls.certificate.json
@@ -1,9 +1,9 @@
{
"certificate.page.title": "SSL 证书",
- "certificate.page.subtitle": "SSL 证书含有网站的公钥和网站标识以及其他相关信息。它们来自于工作流的执行输出。",
+ "certificate.page.subtitle": "SSL 证书含有网站的公钥和网站标识以及其他相关信息。它们来自于工作流的运行输出。",
"certificate.nodata.title": "暂无证书",
- "certificate.nodata.description": "当前未找到任何认证。请先执行一个工作流以生成证书。",
+ "certificate.nodata.description": "当前未找到任何认证。请先运行一个工作流以生成证书。",
"certificate.nodata.button": "前往工作流",
"certificate.search.placeholder": "按证书名称或序列号搜索……",
diff --git a/ui/src/i18n/locales/zh/nls.dashboard.json b/ui/src/i18n/locales/zh/nls.dashboard.json
index f9912c35a..21a659c77 100644
--- a/ui/src/i18n/locales/zh/nls.dashboard.json
+++ b/ui/src/i18n/locales/zh/nls.dashboard.json
@@ -7,8 +7,8 @@
"dashboard.statistics.all_workflows": "所有工作流",
"dashboard.statistics.enabled_workflows": "已启用工作流",
- "dashboard.latest_workflow_runs": "最近执行的工作流",
- "dashboard.latest_workflow_runs.nodata.description": "当前未找到任何工作流执行记录。请先执行一个工作流。",
+ "dashboard.latest_workflow_runs": "最近运行的工作流",
+ "dashboard.latest_workflow_runs.nodata.description": "当前未找到任何工作流运行历史。请先运行一个工作流。",
"dashboard.latest_workflow_runs.nodata.button": "前往工作流",
"dashboard.quick_actions": "快捷操作",
diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json
index aae194a12..0c8f6eb7e 100644
--- a/ui/src/i18n/locales/zh/nls.settings.json
+++ b/ui/src/i18n/locales/zh/nls.settings.json
@@ -56,8 +56,8 @@
"settings.persistence.tab": "数据持久化",
"settings.persistence.title": "定期清理数据",
- "settings.persistence.form.workflow_runs_max_days.label": "工作流执行历史保留天数",
- "settings.persistence.form.workflow_runs_max_days.placeholder": "请输入执行历史保留天数",
+ "settings.persistence.form.workflow_runs_max_days.label": "工作流运行历史保留天数",
+ "settings.persistence.form.workflow_runs_max_days.placeholder": "请输入运行历史保留天数",
"settings.persistence.form.workflow_runs_max_days.unit": "天",
"settings.persistence.form.workflow_runs_max_days.extra": "设置为 0 表示永久保留,不会自动清理。建议设置为 180 天以上。",
"settings.persistence.form.expired_certificates_max_days.label": "证书过期后保留天数",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json
index 178379aa2..cc911d133 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.json
@@ -1,6 +1,6 @@
{
"workflow.page.title": "工作流",
- "workflow.page.subtitle": "工作流是自动化流程的节点集合。当满足触发条件时,工作流开始顺序执行各节点以完成复杂的任务。",
+ "workflow.page.subtitle": "工作流是自动化流程的节点集合。当满足触发条件时,工作流开始顺序运行各节点以完成复杂的任务。",
"workflow.nodata.title": "暂无工作流",
"workflow.nodata.description": "当前未找到工作流。请先创建。",
@@ -28,17 +28,17 @@
"workflow.action.rollback.button": "回退更改",
"workflow.action.rollback.modal.title": "回退更改",
"workflow.action.rollback.modal.content": "确定要回退到最近一次发布的版本吗?",
- "workflow.action.run.button": "立即执行",
- "workflow.action.run.modal.title": "执行工作流",
- "workflow.action.run.modal.content": "你有尚未发布的更改。确定要以最近一次发布的版本继续执行吗?",
- "workflow.action.run.prompt": "执行中……请稍后查看执行历史",
+ "workflow.action.run.button": "运行",
+ "workflow.action.run.modal.title": "运行工作流",
+ "workflow.action.run.modal.content": "你有尚未发布的更改。确定要以最近一次发布的版本继续运行吗?",
+ "workflow.action.run.prompt": "运行中……请稍后查看运行历史",
"workflow.props.name": "名称",
"workflow.props.description": "描述",
"workflow.props.trigger": "触发方式",
"workflow.props.trigger.scheduled": "定时",
"workflow.props.trigger.manual": "手动",
- "workflow.props.last_run_at": "最近执行时间",
+ "workflow.props.last_run_at": "最近运行时间",
"workflow.props.state": "是否启用",
"workflow.props.state.filter.all": "全部",
"workflow.props.state.filter.enabled": "启用",
@@ -60,8 +60,18 @@
"workflow.detail.baseinfo.name.placeholder": "请输入工作流名称",
"workflow.detail.baseinfo.description.placeholder": "请输入工作流描述",
- "workflow.detail.design.tab": "流程编排",
- "workflow.detail.design_legacy.tab": "旧版编辑器",
- "workflow.detail.design.draft.alert": "当前编排有未发布的更改。",
- "workflow.detail.runs.tab": "执行历史"
+ "workflow.detail.design_legacy.tab": "流程编排(旧版)",
+ "workflow.detail.design.tab": "流程编排 (TODO)",
+ "workflow.detail.design.toolbar.zoom": "缩放",
+ "workflow.detail.design.toolbar.zoomin": "放大",
+ "workflow.detail.design.toolbar.zoomout": "缩小",
+ "workflow.detail.design.toolbar.auto_fit": "自适应视图",
+ "workflow.detail.design.toolbar.horizontal_layout": "横向布局",
+ "workflow.detail.design.toolbar.vertical_layout": "竖向布局",
+ "workflow.detail.design.toolbar.hand_mode": "触摸模式",
+ "workflow.detail.design.toolbar.pointer_mode": "指针模式",
+ "workflow.detail.design.nodes.placeholder": "请完成配置",
+ "workflow.detail.design.unpublished_draft.alert": "当前编排有未发布的更改。",
+ "workflow.detail.design.unsaved_changes.confirm": "你有尚未保存的更改。确定要关闭面板吗?",
+ "workflow.detail.runs.tab": "运行历史"
}
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 1f0bcd2be..e78371a6e 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -9,9 +9,8 @@
"workflow_node.action.duplicate_branch": "复制分支",
"workflow_node.action.remove_branch": "删除分支",
- "workflow_node.unsaved_changes.confirm": "你有尚未保存的更改。确定要关闭面板吗?",
-
"workflow_node.start.label": "开始",
+ "workflow_node.start.help": "定义工作流启动的触发条件。一个工作流只能有一个开始节点。",
"workflow_node.start.default_name": "开始",
"workflow_node.start.form.trigger.label": "触发方式",
"workflow_node.start.form.trigger.placeholder": "请选择触发方式",
@@ -21,10 +20,11 @@
"workflow_node.start.form.trigger_cron.placeholder": "请输入 Cron 表达式",
"workflow_node.start.form.trigger_cron.errmsg.invalid": "请输入正确的 Cron 表达式",
"workflow_node.start.form.trigger_cron.tooltip": "五段式表达式,支持使用任意值(即 * )、值列表分隔符(即 , )、值的范围(即 - )、步骤值(即 / )等四种表达式。时区以服务器设置为准。",
- "workflow_node.start.form.trigger_cron.extra": "预计最近 5 次执行时间:",
+ "workflow_node.start.form.trigger_cron.extra": "预计最近 5 次运行时间:",
"workflow_node.start.form.trigger_cron.guide": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。也不要总是设置为每日零时,以免遭遇证书颁发机构的流量高峰。 参考链接: 1. Let’s Encrypt 速率限制 2. 为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机? ",
"workflow_node.apply.label": "申请签发证书",
+ "workflow_node.apply.help": "从证书颁发机构申请签发 SSL 证书。",
"workflow_node.apply.default_name": "申请",
"workflow_node.apply.form.domains.label": "域名",
"workflow_node.apply.form.domains.placeholder": "请输入域名(多个值请用半角分号隔开)",
@@ -98,11 +98,12 @@
"workflow_node.apply.form.skip_before_expiry_days.label": "重复申请",
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "请输入续期间隔",
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当上次签发的证书剩余有效期大于",
- "workflow_node.apply.form.skip_before_expiry_days.suffix": "时,再次执行工作流时跳过此申请节点。",
+ "workflow_node.apply.form.skip_before_expiry_days.suffix": "时,再次运行工作流时跳过此申请节点。",
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
"workflow_node.upload.label": "上传自有证书",
+ "workflow_node.upload.help": "上传用户已有的本地 SSL 证书。",
"workflow_node.upload.default_name": "上传",
"workflow_node.upload.form.domains.label": "域名",
"workflow_node.upload.form.domains.placeholder": "上传证书文件后显示",
@@ -111,7 +112,21 @@
"workflow_node.upload.form.private_key.label": "私钥文件(PEM 格式)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
+ "workflow_node.monitor.label": "监控网站证书",
+ "workflow_node.monitor.help": "通过 HTTPS 协议获取指定网站的 SSL 证书。",
+ "workflow_node.monitor.default_name": "监控",
+ "workflow_node.monitor.form.guide": "小贴士:Certimate 将向目标地址发送一个 HEAD 请求来获取相应的域名证书,请确保该地址可通过 HTTPS 协议访问。",
+ "workflow_node.monitor.form.host.label": "主机地址",
+ "workflow_node.monitor.form.host.placeholder": "请输入主机地址(可以是域名或 IP)",
+ "workflow_node.monitor.form.port.label": "主机端口",
+ "workflow_node.monitor.form.port.placeholder": "请输入主机端口",
+ "workflow_node.monitor.form.domain.label": "域名(可选)",
+ "workflow_node.monitor.form.domain.placeholder": "请输入域名(仅当主机地址为 IP 时可选)",
+ "workflow_node.monitor.form.request_path.label": "请求路径(可选)",
+ "workflow_node.monitor.form.request_path.placeholder": "请输入请求路径",
+
"workflow_node.deploy.label": "部署证书到 ...",
+ "workflow_node.deploy.help": "调用服务提供商相关 API,将 SSL 证书部署到指定的目标。",
"workflow_node.deploy.default_name": "部署",
"workflow_node.deploy.form.provider.label": "部署目标",
"workflow_node.deploy.form.provider.placeholder": "请选择部署目标",
@@ -931,24 +946,13 @@
"workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串",
"workflow_node.deploy.form.strategy_config.label": "执行策略",
"workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署",
- "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,再次执行工作流时",
+ "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,再次运行工作流时",
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": "此部署节点。",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过",
- "workflow_node.monitor.label": "监控网站证书",
- "workflow_node.monitor.default_name": "监控",
- "workflow_node.monitor.form.guide": "小贴士:Certimate 将向目标地址发送一个 HEAD 请求来获取相应的域名证书,请确保该地址可通过 HTTPS 协议访问。",
- "workflow_node.monitor.form.host.label": "主机地址",
- "workflow_node.monitor.form.host.placeholder": "请输入主机地址(可以是域名或 IP)",
- "workflow_node.monitor.form.port.label": "主机端口",
- "workflow_node.monitor.form.port.placeholder": "请输入主机端口",
- "workflow_node.monitor.form.domain.label": "域名(可选)",
- "workflow_node.monitor.form.domain.placeholder": "请输入域名(仅当主机地址为 IP 时可选)",
- "workflow_node.monitor.form.request_path.label": "请求路径(可选)",
- "workflow_node.monitor.form.request_path.placeholder": "请输入请求路径",
-
"workflow_node.notify.label": "推送通知",
+ "workflow_node.notify.help": "调用服务提供商相关 API,将消息通知推送到指定的目标。",
"workflow_node.notify.default_name": "通知",
"workflow_node.notify.form.subject.label": "通知主题",
"workflow_node.notify.form.subject.placeholder": "请输入通知主题",
@@ -987,13 +991,11 @@
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.on": "跳过",
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.off": "不跳过",
- "workflow_node.end.label": "结束",
- "workflow_node.end.default_name": "结束",
-
"workflow_node.branch.label": "并行/条件分支",
"workflow_node.branch.default_name": "分支",
"workflow_node.condition.label": "分支",
+ "workflow_node.condition.help": "当满足指定的条件时,进入相应分支。某一分支中的节点执行失败不影响平行分支继续执行。",
"workflow_node.condition.default_name": "分支",
"workflow_node.condition.default_name.template_certtest_on_expire_soon": "若网站证书即将到期 ...",
"workflow_node.condition.default_name.template_certtest_on_expired": "若网站证书已到期 ...",
@@ -1019,6 +1021,9 @@
"workflow_node.condition.form.expression.value.option.false.label": "假",
"workflow_node.condition.form.expression.add_condition.button": "添加条件",
+ "workflow_node.branch_block.label": "分支",
+ "workflow_node.branch_block.default_name": "分支",
+
"workflow_node.execute_result_branch.label": "执行结果分支",
"workflow_node.execute_result_branch.default_name": "分支",
@@ -1026,5 +1031,16 @@
"workflow_node.execute_success.default_name": "执行成功",
"workflow_node.execute_failure.label": "若上一节点执行失败…",
- "workflow_node.execute_failure.default_name": "执行失败"
+ "workflow_node.execute_failure.default_name": "执行失败",
+
+ "workflow_node.try_catch.label": "执行结果分支",
+ "workflow_node.try_catch.help": "尝试执行后续节点,当任一节点执行失败后,中断并进入执行失败分支。",
+ "workflow_node.try_catch.default_name": "尝试执行…",
+
+ "workflow_node.catch_block.label": "执行失败分支",
+ "workflow_node.catch_block.default_name": "若执行失败…",
+
+ "workflow_node.end.label": "结束",
+ "workflow_node.end.help": "中止工作流运行并退出。通常是一个工作流的最后一个节点。",
+ "workflow_node.end.default_name": "结束"
}
diff --git a/ui/src/i18n/locales/zh/nls.workflow.runs.json b/ui/src/i18n/locales/zh/nls.workflow.runs.json
index 5833dbfc1..29c25f5be 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.runs.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.runs.json
@@ -1,27 +1,27 @@
{
"workflow_run.action.view.button": "查看详情",
- "workflow_run.action.cancel.button": "取消执行",
- "workflow_run.action.cancel.modal.title": "取消执行",
- "workflow_run.action.cancel.modal.content": "确定要取消此执行吗?取消后仅会中止流程,但不会回滚已执行的节点。",
- "workflow_run.action.delete.button": "删除执行",
- "workflow_run.action.delete.modal.title": "删除执行「{{name}}」",
- "workflow_run.action.delete.modal.content": "确定要删除该工作流执行记录吗?删除后仅会清除日志历史,但不会影响签发的证书。 注意此操作不可撤销,请谨慎操作。",
+ "workflow_run.action.cancel.button": "取消运行",
+ "workflow_run.action.cancel.modal.title": "取消运行",
+ "workflow_run.action.cancel.modal.content": "确定要取消此运行吗?取消后仅会中止流程,但不会回滚已执行的节点。",
+ "workflow_run.action.delete.button": "删除运行历史",
+ "workflow_run.action.delete.modal.title": "删除运行「{{name}}」",
+ "workflow_run.action.delete.modal.content": "确定要删除该工作流运行历史吗?删除后仅会清除日志历史,但不会影响签发的证书。 注意此操作不可撤销,请谨慎操作。",
- "workflow_run.deletion.alert": "执行记录中包含工作流各节点的执行结果,删除后可能导致因找不到前次执行结果而触发重新申请或部署证书。如无必要请勿提前删除,建议保留至少 180 天。",
- "workflow_run.cancellation.alert": "如遇进程意外中止、服务器超时等原因,你可以手动取消长时间挂起的执行,避免阻塞后续执行。",
+ "workflow_run.deletion.alert": "运行历史中包含工作流各节点的运行结果,删除后可能导致因找不到前次运行结果而触发重新申请或部署证书。如无必要请勿提前删除,建议保留至少 180 天。",
+ "workflow_run.cancellation.alert": "如遇进程意外中止、服务器超时等原因,你可以手动取消长时间挂起的运行,避免阻塞后续运行。",
- "workflow_run.nodata.description": "当前未找到任何执行记录。请先执行此工作流。",
+ "workflow_run.nodata.description": "当前未找到任何运行历史。请先运行此工作流。",
"workflow_run.props.id": "ID",
"workflow_run.props.status": "状态",
- "workflow_run.props.status.pending": "等待执行",
- "workflow_run.props.status.running": "执行中",
+ "workflow_run.props.status.pending": "等待运行",
+ "workflow_run.props.status.running": "运行中",
"workflow_run.props.status.succeeded": "已成功",
"workflow_run.props.status.failed": "已失败",
"workflow_run.props.status.canceled": "已取消",
- "workflow_run.props.trigger": "执行方式",
- "workflow_run.props.trigger.scheduled": "定时执行",
- "workflow_run.props.trigger.manual": "手动执行",
+ "workflow_run.props.trigger": "触发方式",
+ "workflow_run.props.trigger.scheduled": "定时",
+ "workflow_run.props.trigger.manual": "手动",
"workflow_run.props.started_at": "开始时间",
"workflow_run.props.ended_at": "完成时间",
diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx
index 175bc4b5e..d4f9165ff 100644
--- a/ui/src/pages/ConsoleLayout.tsx
+++ b/ui/src/pages/ConsoleLayout.tsx
@@ -8,8 +8,8 @@ import {
IconHelpCircle,
IconHierarchy3,
IconHome,
- IconLogout,
IconMenu2,
+ IconPower,
IconSettings,
} from "@tabler/icons-react";
import { Alert, Button, Drawer, Layout, Menu, type MenuProps, theme } from "antd";
@@ -88,7 +88,7 @@ const ConsoleLayout = () => {
danger: true,
icon: (
-
+
),
label: t("common.menu.logout"),
@@ -136,7 +136,7 @@ const ConsoleLayout = () => {
} onClick={handleGitHubClick} />
- } onClick={handleLogoutClick} />
+ } onClick={handleLogoutClick} />
diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx
index 7468acb53..ffcfa9bf1 100644
--- a/ui/src/pages/accesses/AccessList.tsx
+++ b/ui/src/pages/accesses/AccessList.tsx
@@ -143,9 +143,9 @@ const AccessList = () => {
renderCell(checked, _, index, node) {
if (!checked) {
return (
-
-
{(page - 1) * pageSize + index + 1}
-
{node}
+
+
{(page - 1) * pageSize + index + 1}
+
{node}
);
}
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
index 999a3bebc..334b99c85 100644
--- a/ui/src/pages/certificates/CertificateList.tsx
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -190,9 +190,9 @@ const CertificateList = () => {
renderCell(checked, _, index, node) {
if (!checked) {
return (
-
-
{(page - 1) * pageSize + index + 1}
-
{node}
+
+
{(page - 1) * pageSize + index + 1}
+
{node}
);
}
diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx
index b4be3d1ce..baccd46f9 100644
--- a/ui/src/pages/workflows/WorkflowDetail.tsx
+++ b/ui/src/pages/workflows/WorkflowDetail.tsx
@@ -3,10 +3,11 @@ import { useTranslation } from "react-i18next";
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom";
import { IconEdit, IconHistory, IconPlayerPlay, IconRobot } from "@tabler/icons-react";
import { useSize } from "ahooks";
-import { App, Button, Input, type InputRef, Segmented, Skeleton, Space } from "antd";
+import { App, Button, Input, type InputRef, Segmented, Skeleton } from "antd";
import { startRun as startWorkflowRun } from "@/api/workflows";
import Show from "@/components/Show";
+import { isAllNodesValidated } from "@/domain/workflow";
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
@@ -19,10 +20,10 @@ const WorkflowDetail = () => {
const { t } = useTranslation();
- const { message, modal } = App.useApp();
+ const { message, modal, notification } = App.useApp();
const { id: workflowId } = useParams();
- const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy"]));
+ const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled"]));
useEffect(() => {
workflowState.init(workflowId!);
@@ -35,6 +36,7 @@ const WorkflowDetail = () => {
const divHeaderSize = useSize(divHeaderRef);
const tabs = [
+ ["design-legacy", "workflow.detail.design_legacy.tab",
], // TODO: 正式发布时移除
["design", "workflow.detail.design.tab",
],
["runs", "workflow.detail.runs.tab",
],
] satisfies [string, string, React.ReactElement][];
@@ -90,6 +92,20 @@ const WorkflowDetail = () => {
});
};
+ const handleActiveClick = async () => {
+ try {
+ if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) {
+ message.warning(t("workflow.action.enable.errmsg.uncompleted"));
+ return;
+ }
+
+ await workflowState.setEnabled(!workflow.enabled);
+ } catch (err) {
+ console.error(err);
+ notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
+ }
+ };
+
return (
@@ -117,11 +133,14 @@ const WorkflowDetail = () => {
-
- } loading={runButtonLoading} type="primary" onClick={handleRunClick}>
- {t("workflow.action.run.button")}
-
-
+
+
+ {workflow.enabled ? t("workflow.action.disable.button") : t("workflow.action.enable.button")}
+ } loading={runButtonLoading} type="primary" onClick={handleRunClick}>
+ {t("workflow.action.run.button")}
+
+
+
@@ -152,7 +171,7 @@ const WorkflowDetailBaseName = () => {
const { notification } = App.useApp();
- const { workflow, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "setBaseInfo"]));
+ const { workflow, initialized, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "setBaseInfo"]));
const inputRef = useRef(null);
@@ -194,7 +213,7 @@ const WorkflowDetailBaseName = () => {
};
return (
-
+
{
>
{workflow.name || "\u00A0"}
-
}
- type="text"
- onClick={handleEditClick}
- />
+ {initialized && (
+
}
+ type="text"
+ onClick={handleEditClick}
+ />
+ )}
{
const { notification } = App.useApp();
- const { workflow, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "setBaseInfo"]));
+ const { workflow, initialized, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "setBaseInfo"]));
const inputRef = useRef
(null);
@@ -273,7 +294,7 @@ const WorkflowDetailBaseDescription = () => {
};
return (
-
+
{
>
{workflow.description || "\u00A0"}
-
}
- type="text"
- onClick={handleEditClick}
- />
+ {initialized && (
+
}
+ type="text"
+ onClick={handleEditClick}
+ />
+ )}
{
const { t } = useTranslation();
+ const { token: themeToken } = theme.useToken();
const { message, modal, notification } = App.useApp();
const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"]));
@@ -35,6 +40,11 @@ const WorkflowDetailDesign = () => {
setAllowPublish(!isPendingOrRunning && hasChanges);
}, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]);
+ const [editorData, setEditorData] = useState
();
+ useDeepCompareEffect(() => {
+ setEditorData({ nodes: compactWorkflowDraft(workflow.draft) });
+ }, [workflow.draft]);
+
const handleRollbackClick = () => {
modal.confirm({
title: t("workflow.action.rollback.modal.title"),
@@ -75,7 +85,7 @@ const WorkflowDetailDesign = () => {
};
return (
-
+
{
},
}}
>
-
-
-
-
- {t("workflow.detail.design.draft.alert")} } showIcon type="warning" />
-
-
-
-
-
- {t("workflow.action.publish.button")}
-
- ,
- onClick: handleRollbackClick,
- },
- ],
- }}
- trigger={["click"]}
- >
- } variant="outlined" />
-
-
+
+
+
+
+
+
+
+ {t("workflow.detail.design.unpublished_draft.alert")} } showIcon type="warning" />
+
+
+
+
+ {t("workflow.action.publish.button")}
+
+ ,
+ onClick: handleRollbackClick,
+ },
+ ],
+ }}
+ trigger={["click"]}
+ >
+ } />
+
+
+
+
-
-
-
+
+
);
};
+const compactWorkflowDraft = (root: WorkflowNode | undefined) => {
+ const { t } = getI18n();
+
+ // TODO: 仅为兼容适配 v0.3.x 数据,正式上线后待删除
+ const res: FlowDocumentJSON["nodes"] = [];
+
+ if (!root) {
+ res.push({
+ id: nanoid(),
+ type: "start",
+ data: {
+ name: "Start",
+ },
+ });
+ } else {
+ const convert = (node: WorkflowNode | undefined) => {
+ const temp: typeof res = [];
+
+ let current: typeof node = node;
+ while (current) {
+ switch (current.type) {
+ case WorkflowNodeType.Start:
+ temp.push({
+ id: current.id,
+ type: "start",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.Apply:
+ temp.push({
+ id: current.id,
+ type: "bizApply",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.Upload:
+ temp.push({
+ id: current.id,
+ type: "bizUpload",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.Monitor:
+ temp.push({
+ id: current.id,
+ type: "bizMonitor",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.Deploy:
+ temp.push({
+ id: current.id,
+ type: "bizDeploy",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.Notify:
+ temp.push({
+ id: current.id,
+ type: "bizNotify",
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+
+ case WorkflowNodeType.ExecuteResultBranch: {
+ const tryNode = temp.pop()!;
+ temp.push({
+ id: current.id,
+ type: "tryCatch",
+ blocks: [
+ {
+ id: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess)?.id || nanoid(),
+ type: "tryBlock",
+ blocks: [tryNode],
+ data: {
+ name: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess)?.name,
+ },
+ },
+ {
+ id: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.id || nanoid(),
+ type: "catchBlock",
+ blocks: [
+ ...convert(current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.next),
+ {
+ id: nanoid(),
+ type: "end",
+ data: {
+ name: t("workflow_node.end.default_name"),
+ },
+ },
+ ],
+ data: {
+ name: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.name,
+ },
+ },
+ ],
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+
+ current = current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess);
+ break;
+ }
+
+ case WorkflowNodeType.Branch: {
+ temp.push({
+ id: current.id,
+ type: "condition",
+ blocks:
+ current.branches?.map((branch) => {
+ return {
+ id: branch.id,
+ type: "branchBlock",
+ blocks: convert(branch.next),
+ data: {
+ name: branch.name,
+ config: branch.config,
+ },
+ };
+ }) ?? [],
+ data: {
+ name: current.name,
+ config: current.config,
+ },
+ });
+ break;
+ }
+ }
+
+ current = current?.next;
+ }
+
+ return temp;
+ };
+
+ res.push(...convert(root));
+ }
+
+ res.push({
+ id: nanoid(),
+ type: "end",
+ data: {
+ name: t("workflow_node.end.default_name"),
+ },
+ });
+
+ return res;
+};
+
export default WorkflowDetailDesign;
diff --git a/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx b/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx
new file mode 100644
index 000000000..5cb26f08c
--- /dev/null
+++ b/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx
@@ -0,0 +1,132 @@
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { IconArrowBackUp, IconDots } from "@tabler/icons-react";
+import { Alert, App, Button, Card, Dropdown, Space } from "antd";
+import { isEqual } from "radash";
+
+import Show from "@/components/Show";
+import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer";
+import { isAllNodesValidated } from "@/domain/workflow";
+import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
+import { useZustandShallowSelector } from "@/hooks";
+import { useWorkflowStore } from "@/stores/workflow";
+import { getErrMsg } from "@/utils/error";
+
+const WorkflowDetailDesign = () => {
+ const { t } = useTranslation();
+
+ const { message, modal, notification } = App.useApp();
+
+ const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"]));
+
+ const [isPendingOrRunning, setIsPendingOrRunning] = useState(false);
+ const [allowRollback, setAllowRollback] = useState(false);
+ const [allowPublish, setAllowPublish] = useState(false);
+
+ useEffect(() => {
+ const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING;
+ setIsPendingOrRunning(pending);
+ }, [workflow]);
+
+ useEffect(() => {
+ const hasContent = !!workflow.content;
+ const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content);
+ setAllowRollback(!isPendingOrRunning && hasContent && hasChanges);
+ setAllowPublish(!isPendingOrRunning && hasChanges);
+ }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]);
+
+ const handleRollbackClick = () => {
+ modal.confirm({
+ title: t("workflow.action.rollback.modal.title"),
+ content: t("workflow.action.rollback.modal.content"),
+ onOk: async () => {
+ try {
+ await workflowState.rollback();
+
+ message.success(t("common.text.operation_succeeded"));
+ } catch (err) {
+ console.error(err);
+ notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
+ }
+ },
+ });
+ };
+
+ const handlePublishClick = () => {
+ if (!isAllNodesValidated(workflow.draft!)) {
+ message.warning(t("workflow.action.publish.errmsg.uncompleted"));
+ return;
+ }
+
+ modal.confirm({
+ title: t("workflow.action.publish.modal.title"),
+ content: t("workflow.action.publish.modal.content"),
+ onOk: async () => {
+ try {
+ await workflowState.publish();
+
+ message.success(t("common.text.operation_succeeded"));
+ } catch (err) {
+ console.error(err);
+ notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
+ }
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {t("workflow.detail.design.unpublished_draft.alert")} } showIcon type="warning" />
+
+
+
+
+ {t("workflow.action.publish.button")}
+
+ ,
+ onClick: handleRollbackClick,
+ },
+ ],
+ }}
+ trigger={["click"]}
+ >
+ } />
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WorkflowDetailDesign;
diff --git a/ui/src/pages/workflows/WorkflowDetailRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx
index f5950da80..1aa551ed0 100644
--- a/ui/src/pages/workflows/WorkflowDetailRuns.tsx
+++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx
@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { IconBrowserShare, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react";
+import { IconBrowserShare, IconDotsVertical, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react";
import { useRequest } from "ahooks";
-import { Alert, App, Button, Skeleton, Table, type TableProps, Tooltip } from "antd";
+import { Alert, App, Button, Dropdown, Skeleton, Table, type TableProps, Tooltip } from "antd";
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
@@ -98,53 +98,63 @@ const WorkflowDetailRuns = () => {
key: "$action",
align: "end",
fixed: "right",
- width: 120,
+ width: 64,
render: (_, record) => {
- const allowCancel = record.status === WORKFLOW_RUN_STATUSES.PENDING || record.status === WORKFLOW_RUN_STATUSES.RUNNING;
- const aloowDelete =
- record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED ||
- record.status === WORKFLOW_RUN_STATUSES.FAILED ||
- record.status === WORKFLOW_RUN_STATUSES.CANCELED;
+ const cancelDisabled = !([WORKFLOW_RUN_STATUSES.PENDING, WORKFLOW_RUN_STATUSES.RUNNING] as string[]).includes(record.status);
+ const deleteDisabled = !cancelDisabled;
return (
-
-
- }
- variant="text"
- onClick={(e) => {
- e.stopPropagation();
- handleRecordDetailClick(record);
- }}
- />
-
-
- }
- variant="text"
- onClick={(e) => {
- e.stopPropagation();
- handleRecordCancelClick(record);
- }}
- />
-
-
- }
- variant="text"
- onClick={(e) => {
- e.stopPropagation();
- handleRecordDeleteClick(record);
- }}
- />
-
-
+
+
+
+ ),
+ onClick: () => {
+ handleRecordDetailClick(record);
+ },
+ },
+ {
+ key: "cancel",
+ label: t("workflow_run.action.cancel.button"),
+ icon: (
+
+
+
+ ),
+ disabled: cancelDisabled,
+ onClick: () => {
+ handleRecordCancelClick(record);
+ },
+ },
+ {
+ type: "divider",
+ },
+ {
+ key: "delete",
+ label: t("workflow_run.action.delete.button"),
+ icon: (
+
+
+
+ ),
+ danger: true,
+ disabled: deleteDisabled,
+ onClick: () => {
+ handleRecordDeleteClick(record);
+ },
+ },
+ ],
+ }}
+ trigger={["click"]}
+ >
+ } type="text" />
+
);
},
onCell: () => {
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index 0c7362d90..0f7452b98 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -194,9 +194,9 @@ const WorkflowList = () => {
renderCell(checked, _, index, node) {
if (!checked) {
return (
-
-
{(page - 1) * pageSize + index + 1}
-
{node}
+
+
{(page - 1) * pageSize + index + 1}
+
{node}
);
}
diff --git a/ui/src/routers/index.tsx b/ui/src/routers/index.tsx
index 5e192a1e7..9ea8dd07a 100644
--- a/ui/src/routers/index.tsx
+++ b/ui/src/routers/index.tsx
@@ -15,6 +15,7 @@ import SettingsPersistence from "@/pages/settings/SettingsPersistence";
import SettingsSSLProvider from "@/pages/settings/SettingsSSLProvider";
import WorkflowDetail from "@/pages/workflows/WorkflowDetail";
import WorkflowDetailDesign from "@/pages/workflows/WorkflowDetailDesign";
+import WorkflowDetailDesignLegacy from "@/pages/workflows/WorkflowDetailDesignLegacy";
import WorkflowDetailRuns from "@/pages/workflows/WorkflowDetailRuns";
import WorkflowList from "@/pages/workflows/WorkflowList";
import WorkflowNew from "@/pages/workflows/WorkflowNew";
@@ -56,6 +57,10 @@ export const router = createHashRouter([
path: "/workflows/:id/design",
element:
,
},
+ {
+ path: "/workflows/:id/design-legacy",
+ element:
,
+ },
{
path: "/workflows/:id/runs",
element:
,