diff --git a/internal/certificate/service.go b/internal/certificate/service.go index 4df15799c..c844b17f9 100644 --- a/internal/certificate/service.go +++ b/internal/certificate/service.go @@ -4,7 +4,6 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" "fmt" "strings" "time" @@ -49,12 +48,11 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error { return } - var settingsContent *domain.SettingsContentAsPersistence - json.Unmarshal([]byte(settings.Content), &settingsContent) - if settingsContent != nil && settingsContent.ExpiredCertificatesMaxDaysRetention != 0 { + persistenceSettings, _ := settings.UnmarshalContentAsPersistence() + if persistenceSettings != nil && persistenceSettings.ExpiredCertificatesMaxDaysRetention != 0 { ret, err := s.certificateRepo.DeleteWhere( context.Background(), - dbx.NewExp(fmt.Sprintf("validityNotAfter=8" @@ -166,6 +167,22 @@ "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" }, + "node_modules/@ant-design/icons/node_modules/@ant-design/colors": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.0.tgz", + "integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/icons/node_modules/@ant-design/fast-color": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.0.tgz", + "integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==", + "engines": { + "node": ">=8.x" + } + }, "node_modules/@ant-design/pro-card": { "version": "2.10.0", "resolved": "https://registry.npmmirror.com/@ant-design/pro-card/-/pro-card-2.10.0.tgz", @@ -185,6 +202,25 @@ "react": ">=17.0.0" } }, + "node_modules/@ant-design/pro-card/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-components": { "version": "2.8.10", "resolved": "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-2.8.10.tgz", @@ -264,6 +300,25 @@ "react": ">=17.0.0" } }, + "node_modules/@ant-design/pro-field/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-form": { "version": "2.32.0", "resolved": "https://registry.npmmirror.com/@ant-design/pro-form/-/pro-form-2.32.0.tgz", @@ -290,6 +345,25 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-form/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-layout": { "version": "7.22.7", "resolved": "https://registry.npmmirror.com/@ant-design/pro-layout/-/pro-layout-7.22.7.tgz", @@ -317,6 +391,25 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-layout/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-list": { "version": "2.6.10", "resolved": "https://registry.npmmirror.com/@ant-design/pro-list/-/pro-list-2.6.10.tgz", @@ -340,6 +433,43 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons/node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/pro-list/node_modules/@ant-design/icons/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "node_modules/@ant-design/pro-list/node_modules/rc-util": { "version": "4.21.1", "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", @@ -419,6 +549,25 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-table/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/pro-utils": { "version": "2.18.0", "resolved": "https://registry.npmmirror.com/@ant-design/pro-utils/-/pro-utils-2.18.0.tgz", @@ -441,6 +590,25 @@ "react-dom": ">=17.0.0" } }, + "node_modules/@ant-design/pro-utils/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/@ant-design/react-slick": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", @@ -460,7 +628,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -474,7 +641,6 @@ "version": "7.27.5", "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.5.tgz", "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -483,7 +649,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -513,7 +678,6 @@ "version": "6.3.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -522,7 +686,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.0.tgz", "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", - "dev": true, "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", @@ -538,7 +701,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, "dependencies": { "@babel/types": "^7.25.9" }, @@ -550,7 +712,6 @@ "version": "7.27.2", "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -566,7 +727,6 @@ "version": "6.3.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -647,7 +807,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -669,7 +828,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -682,7 +840,6 @@ "version": "7.27.3", "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -711,7 +868,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -767,7 +923,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -776,7 +931,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -785,7 +939,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -808,7 +961,6 @@ "version": "7.27.6", "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.6.tgz", "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" @@ -821,7 +973,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dev": true, "dependencies": { "@babel/types": "^7.28.0" }, @@ -953,6 +1104,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1904,7 +2069,6 @@ "version": "7.27.2", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -1918,7 +2082,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.0.tgz", "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", @@ -1936,7 +2099,6 @@ "version": "7.28.1", "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.1.tgz", "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -2178,6 +2340,24 @@ "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", @@ -2647,18 +2827,18 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.15" @@ -2668,9 +2848,9 @@ } }, "node_modules/@eslint/css-tree": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/@eslint/css-tree/-/css-tree-3.6.2.tgz", - "integrity": "sha512-ttB4VQguJdWxPVfX/6xB0ugWVeTe6I3nv9izeR+xPcQbtFVb3lhKbcTScWTXZu+OF0PLoLavX7zBiYtLwnlB9w==", + "version": "3.6.3", + "resolved": "https://registry.npmmirror.com/@eslint/css-tree/-/css-tree-3.6.3.tgz", + "integrity": "sha512-M9iq4Brt/MG+5/B4Jrla5XZqaCgaHjfZyMSUJM3KNpBU61u8gMYg4TTaNTP/mUGR/rnRrVV7RXmh5qI4pIk0Yw==", "dev": true, "dependencies": { "mdn-data": "2.21.0", @@ -2738,9 +2918,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.33.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2759,18 +2939,901 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@flowgram.ai/background-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.3.4.tgz", + "integrity": "sha512-fGJZIIaGVQwVs0fTd5dHjRnxQEzpZqtqPaHfyh40CX33x7NjtgPIPwUoNMkVoQ7+IFJAvNxShqiVAbkTbbxdYQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/command": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.3.4.tgz", + "integrity": "sha512-Vl/eX/mt78UThjpiU0iVD7BGNU/zus3lOrlGNt+fOjdr9QsOtDu6R4jjUA/6lxWGMuiEkVhFupILEhK3kVe9zA==", + "dependencies": { + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/core": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.3.4.tgz", + "integrity": "sha512-bs0UwHNkc9pprx3R3HiOGRL9BmDuoxMCdAZH+mrJecq17axzqt2X+qUNtCnji7CHVHw/r11oJy2DsJot52iaIg==", + "dependencies": { + "@flowgram.ai/command": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "@phosphor/messaging": "^1.3.0", + "@tweenjs/tween.js": "^18", + "clsx": "^1.1.1", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/core/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@flowgram.ai/core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/document": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.3.4.tgz", + "integrity": "sha512-tLftf4Z90EGBEaweYGczE5RZLK0DGAQ6Z9QB3BANpf1QjzYeKZNcucDLqJk6f/WqInjVzYDqBt0me9O8/FpEdQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/document/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/editor": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.3.4.tgz", + "integrity": "sha512-MTcE33V48U44aJ2EDpGpsc4H/ve7s24JKy/xK16sT1RhkN8YqYgbR/W1l8ftQ/ofudMgbnrhYqUK6qVGf7HYNw==", + "dependencies": { + "@flowgram.ai/background-plugin": "0.3.4", + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/group-plugin": "0.3.4", + "@flowgram.ai/history": "0.3.4", + "@flowgram.ai/history-node-plugin": "0.3.4", + "@flowgram.ai/i18n-plugin": "0.3.4", + "@flowgram.ai/materials-plugin": "0.3.4", + "@flowgram.ai/node": "0.3.4", + "@flowgram.ai/node-core-plugin": "0.3.4", + "@flowgram.ai/node-variable-plugin": "0.3.4", + "@flowgram.ai/playground-react": "0.3.4", + "@flowgram.ai/redux-devtool-plugin": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/shortcuts-plugin": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "@flowgram.ai/variable-plugin": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-drag-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.3.4.tgz", + "integrity": "sha512-yNz6giVOFAe1fZpqyqvupmRE78mWftoQmz9L+fXTaYJjmGPgTi5KlwwncoCM8xN7pJ5Dq30VXF5AlYmELThjBQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-history-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.3.4.tgz", + "integrity": "sha512-L9nKjBH6QUZc9jIfaXmuNOt4fQXYfbB4gS+Ife7G0o2IJteLYUm0i7D02ohRt/B8OYXVL/TUqWLWtOrUcgtPBg==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/history": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/fixed-layout-core": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.3.4.tgz", + "integrity": "sha512-gFnHCBjVTJc5V0UC5Y7KNLMhQuXfpuPItpdwMQYre8xOicxuStdll4SIzBPFm+ihPnO4ymymKeWdl2cAHP1zYg==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-layout-editor": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.3.4.tgz", + "integrity": "sha512-vgCUiPHCEWdsnuM97SGAPji04ld3QurLaHJ8AexNmYXHW2vQn27RFATaSmX54rD4K8SE7/hcgIm9aJQQ8cr+Ig==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/editor": "0.3.4", + "@flowgram.ai/fixed-drag-plugin": "0.3.4", + "@flowgram.ai/fixed-history-plugin": "0.3.4", + "@flowgram.ai/fixed-layout-core": "0.3.4", + "@flowgram.ai/history": "0.3.4", + "@flowgram.ai/reactive": "0.3.4", + "@flowgram.ai/select-box-plugin": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.3.4.tgz", + "integrity": "sha512-3566t7gDGKodpDlo6tnq0IjmIxysqRgf+aq0pgVBeJemalgameHXCEqee8nq7TEemE1fqA5ivHW1q4bdbYouiw==", + "dependencies": { + "@flowgram.ai/reactive": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "fast-equals": "^2.0.0", + "lodash": "^4.17.21", + "nanoid": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form-core": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.3.4.tgz", + "integrity": "sha512-MNDO2Opl77vgybTJ9R4NDmkRSGXuMopdQei7MC4vckYIqxTQiCbn/NxplFjwa3KpXBdMABjdlRT9ROWzk8kYKA==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/free-layout-core": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.3.4.tgz", + "integrity": "sha512-DU+ZPYvtKEF0DiXJw+QSDPPhbs5Wl6vR7bKHChweveKzXqBD3glCBOYl3tWFN92Jkvequ9lj7glmngTj8KsT/Q==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/node": "0.3.4", + "@flowgram.ai/reactive": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash-es": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/free-layout-core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/group-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.3.4.tgz", + "integrity": "sha512-rqUo7PYhnaRousBIlQIP65JOjN65ssf6GoifkR1xLEVKhNimdVe6A0pGQ5QfUdCAJy/CgXW+Z1MVGLU1vGouLQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/history": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.3.4.tgz", + "integrity": "sha512-Q72BwZtAeTBS2QGx12qM+3vdx4COG+Lj8asWrebO9Af1FTMJzqyn1OgwkUfywn6XW3qowb7I+w1daedUZLnDhw==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/history-node-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.3.4.tgz", + "integrity": "sha512-yLqU7l7YVbWVvXBWRlUaGAVxDm5KyKVjIcCPfSm4roIPyS96+SElRpj4949emucW38UtstBVsNnpFZ5WV9EStw==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/history": "0.3.4", + "@flowgram.ai/node": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/history/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/i18n": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.3.4.tgz", + "integrity": "sha512-SLn7A6x/bnuLqAZo1R4QGZR/i7Pex6/48AW0rokUgair9qm3sMpnX82DCltjG4GbddiKeUPkJLv9KLb7XaAN5Q==", + "dependencies": { + "@flowgram.ai/utils": "0.3.4", + "i18n-js": "^4.5.1" + } + }, + "node_modules/@flowgram.ai/i18n-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.3.4.tgz", + "integrity": "sha512-XB5uGA+R5d2lbLT7jR8FCz8cguGXxVfE1CFk1KxkG9nCCvlv5ZT1kz8/Lcn1DQkALpK1LOElqCax2YG7vHhYXg==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/i18n": "0.3.4" + } + }, + "node_modules/@flowgram.ai/materials-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.3.4.tgz", + "integrity": "sha512-3voP3+AH0QnJdroh5GxLOI/JFajYaUVuCPioWC9paHJv8Jp3KBxjbq68HWMEhT37y5yuMFyRH7G62fp9GdMLGg==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/minimap-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/minimap-plugin/-/minimap-plugin-0.3.4.tgz", + "integrity": "sha512-iqy4VPqOee1HQwYtp659i8xWWCaHYiBK8EOOcfxhDonhSwXKTm+NxfcLFUUzTvEwG54yqMcxfgXjTX0YeDsdKA==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.3.4.tgz", + "integrity": "sha512-r/21XzWZIk7YKWkEF1vimv2jPLYQmsdh0qXfO7sYBwqIH2CrcwPAtH4nVNkvZ/EjUf2q7ZoXc1Ur6MI3IFI+EQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/node-core-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.3.4.tgz", + "integrity": "sha512-aGYp0tfSwFsz4qYTXD55Rgpdt5kUM2aus/5ZKfVbLpvNjGJis7lYbwvlLy3RwEXQActyHaEV+YEIGqcQ14hZXA==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/node": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node-variable-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.3.4.tgz", + "integrity": "sha512-u4QGhxpGKTy+2FC/fO6FzCf+JdgFbcT4WDJymlRv7KgeGWSlAOX1WC2ZgmfwBMSbRcpevJe7vUmUrP5RTSN8nA==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/form-core": "0.3.4", + "@flowgram.ai/node": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "@flowgram.ai/variable-plugin": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/playground-react": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.3.4.tgz", + "integrity": "sha512-o3+8QJC/Z/7+bcxLkGVScZZCVgP/zmJQaFUYcvAjElw+UPnYJ6gukyYwrTkNpUlXZq2dqGYjUSrIrWZlf/03/Q==", + "dependencies": { + "@flowgram.ai/background-plugin": "0.3.4", + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/shortcuts-plugin": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/reactive": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.3.4.tgz", + "integrity": "sha512-A+XrcromfG0+1HzngwanYEVcb42OltqCE7ua1JKBhr9JzvoyFf6wWyDCm8P8Q0JF20vHC/60A+CazBbyBIwATg==", + "dependencies": { + "@flowgram.ai/utils": "0.3.4" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.3.4.tgz", + "integrity": "sha512-kBjIlIJQMQj+lDSHCScpFv/9a3KLFNPTW2ssWm5RPrqxI2OF7ghXAV9Gk+AFnDCHSOy57g8XbSOIZYbBWJiOMg==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/variable-core": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/renderer": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.3.4.tgz", + "integrity": "sha512-sN/7rmC0uWpTXVY/lGYFRP+UJcNDcYoBEap5+KxhwMGCC5XbEWTp9+Y4NYJIs/Ephq+7S2um5Ue3lNhg2Lry5Q==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/i18n": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/select-box-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.3.4.tgz", + "integrity": "sha512-mqSw5Ayuk/ieBiE4Tqvz6NJW+WVT9qLpnER6MUFAwNFoQAFO5unXx/rF6UjH/42EnM1XCUEHkaJ58nuCOQzMgQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/renderer": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.3.4.tgz", + "integrity": "sha512-XeUGslbb3yHyoS9dmPno4bvOQRcCeUvmsRXeAg4I0k7Vgs2IeubJPOy1+HbA+SPkHXLOEqBUJExzrbG5+TTmmw==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/utils": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.3.4.tgz", + "integrity": "sha512-wtgYpcBciubTODOpx+Qls9B5YNmKwvVDiLksWoehhYKH9PlrFSAgnwskNC37M5I+a3AAHFKQT3HUpo9T2CZiOw==", + "dependencies": { + "clsx": "^1.1.1", + "inversify": "^6.0.1", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/utils/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@flowgram.ai/utils/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/variable-core": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.3.4.tgz", + "integrity": "sha512-qomM9Pc38Su/Kis0MymbK/zbYca3zXEfIWwIO3RpHURBUXsVsMjLKPEMjGd6sHewlHDzu+wBep3p0Yn+37fS7A==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "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.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/variable-core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/variable-layout": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.3.4.tgz", + "integrity": "sha512-6tSsUNbf9tC9DvAD1BX0gWSpxEcs2DNyexnfDqTx4I4bRHqV7wHbVZfCfgWqkORuTBHxTkr+kVW4wFwpaV5dBQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/free-layout-core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "@flowgram.ai/variable-core": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-plugin": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.3.4.tgz", + "integrity": "sha512-RJ+kyNiyJ/pP9scP0OtZ04zfnl7vTJ2KbmEOXEFQ+RGcLpfrf60L4hg6tjZWgdljG9aU0KWgY3YkyFhbERDJXQ==", + "dependencies": { + "@flowgram.ai/core": "0.3.4", + "@flowgram.ai/document": "0.3.4", + "@flowgram.ai/free-layout-core": "0.3.4", + "@flowgram.ai/utils": "0.3.4", + "@flowgram.ai/variable-core": "0.3.4", + "@flowgram.ai/variable-layout": "0.3.4", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", @@ -2828,6 +3891,28 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inversifyjs/common": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@inversifyjs/common/-/common-1.4.0.tgz", + "integrity": "sha512-qfRJ/3iOlCL/VfJq8+4o5X4oA14cZSBbpAmHsYj8EsIit1xDndoOl0xKOyglKtQD4u4gdNVxMHx4RWARk/I4QA==" + }, + "node_modules/@inversifyjs/core": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@inversifyjs/core/-/core-1.3.5.tgz", + "integrity": "sha512-B4MFXabhNTAmrfgB+yeD6wd/GIvmvWC6IQ8Rh/j2C3Ix69kmqwz9pr8Jt3E+Nho9aEHOQCZaGmrALgtqRd+oEQ==", + "dependencies": { + "@inversifyjs/common": "1.4.0", + "@inversifyjs/reflect-metadata-utils": "0.2.4" + } + }, + "node_modules/@inversifyjs/reflect-metadata-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-0.2.4.tgz", + "integrity": "sha512-u95rV3lKfG+NT2Uy/5vNzoDujos8vN8O18SSA5UyhxsGYd4GLQn/eUsGXfOsfa7m34eKrDelTKRUX1m/BcNX5w==", + "peerDependencies": { + "reflect-metadata": "0.2.2" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2844,7 +3929,6 @@ "version": "0.3.12", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -2854,7 +3938,6 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2873,14 +3956,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2979,6 +4060,28 @@ "node": ">= 8" } }, + "node_modules/@phosphor/algorithm": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@phosphor/algorithm/-/algorithm-1.2.0.tgz", + "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" + }, + "node_modules/@phosphor/collections": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@phosphor/collections/-/collections-1.2.0.tgz", + "integrity": "sha512-T9/0EjSuY6+ga2LIFRZ0xupciOR3Qnyy8Q95lhGTC0FXZUFwC8fl9e8On6IcwasCszS+1n8dtZUWSIynfgdpzw==", + "dependencies": { + "@phosphor/algorithm": "^1.2.0" + } + }, + "node_modules/@phosphor/messaging": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@phosphor/messaging/-/messaging-1.3.0.tgz", + "integrity": "sha512-k0JE+BTMKlkM335S2AmmJxoYYNRwOdW5jKBqLgjJdGRvUQkM0+2i60ahM45+J23atGJDv9esKUUBINiKHFhLew==", + "dependencies": { + "@phosphor/algorithm": "^1.2.0", + "@phosphor/collections": "^1.2.0" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.9.tgz", @@ -3112,9 +4215,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", @@ -3131,6 +4234,19 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/util": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@rc-component/util/-/util-1.2.2.tgz", + "integrity": "sha512-p3zQr9Wu8BKncqmuW23olzBoAFsN8PYMS9FaI4JwJLwknH7DvfHAr1fwbfl9aAWw4Jva64ucpenbgG4fznLUSw==", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -3702,6 +4818,11 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -3839,17 +4960,23 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "peer": true + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", - "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz", + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/type-utils": "8.38.0", - "@typescript-eslint/utils": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/type-utils": "8.39.0", + "@typescript-eslint/utils": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3863,9 +4990,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.38.0", + "@typescript-eslint/parser": "^8.39.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -3878,15 +5005,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.38.0.tgz", - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.39.0.tgz", + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4" }, "engines": { @@ -3898,17 +5025,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", "debug": "^4.3.4" }, "engines": { @@ -3919,17 +5046,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3940,9 +5067,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3952,18 +5079,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", - "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz", + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3976,13 +5103,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3993,15 +5120,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", "dev": true, "dependencies": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4017,19 +5144,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.38.0.tgz", - "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0" + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4040,16 +5167,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/types": "8.39.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4073,9 +5200,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", @@ -4099,20 +5226,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", @@ -4128,15 +5255,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": { @@ -4148,8 +5275,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": { @@ -4545,9 +5672,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", @@ -4560,7 +5687,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", @@ -4590,7 +5717,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", @@ -4620,6 +5747,25 @@ "zod": ">=3.25.0" } }, + "node_modules/antd/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", @@ -4805,12 +5951,35 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -4836,7 +6005,6 @@ "version": "4.24.4", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4945,11 +6113,18 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001706", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5049,8 +6224,7 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "1.0.2", @@ -5122,6 +6296,24 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", @@ -5178,6 +6370,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", @@ -5187,7 +6396,6 @@ "version": "4.4.1", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -5274,8 +6482,7 @@ "node_modules/electron-to-chromium": { "version": "1.5.74", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", - "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", - "dev": true + "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==" }, "node_modules/enhanced-resolve": { "version": "5.18.2", @@ -5476,25 +6683,24 @@ "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.33.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5661,18 +6867,18 @@ } }, "node_modules/eslint-plugin-better-tailwindcss": { - "version": "3.7.1", - "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.1.tgz", - "integrity": "sha512-ROTCf93dM9PcSiXVxXpfN/CK8WTAwjImi//c1DUjBfgRbMyXjkp5JPy/CAf+gJIElzSPkDPvMaFrg08NtXJ1Qg==", + "version": "3.7.4", + "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.4.tgz", + "integrity": "sha512-jlLHLoqrNbcqqROVjFojGDv1m2LGiJwFqynbARbyeRj9rc1Hmh46EeQhmYVQihhD4j+DSxG/bcwoA9PABIpLmw==", "dev": true, "dependencies": { - "@eslint/css-tree": "^3.6.2", + "@eslint/css-tree": "^3.6.3", "enhanced-resolve": "^5.18.2", - "jiti": "^2.4.2", + "jiti": "^2.5.1", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "synckit": "^0.11.11", - "tailwind-csstree": "^0.1.1", + "tailwind-csstree": "^0.1.2", "tsconfig-paths-webpack-plugin": "^4.2.0" }, "engines": { @@ -5769,9 +6975,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.3", - "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", - "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "version": "5.5.4", + "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", @@ -5976,6 +7182,11 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6105,9 +7316,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.1", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", "dev": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -6174,7 +7385,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -6393,6 +7603,19 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -6401,6 +7624,16 @@ "void-elements": "3.1.0" } }, + "node_modules/i18n-js": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/i18n-js/-/i18n-js-4.5.1.tgz", + "integrity": "sha512-n7jojFj1WC0tztgr0I8jqTXuIlY1xNzXnC3mjKX/YjJhimdM+jXM8vOmn9d3xQFNC6qDHJ4ovhdrGXrRXLIGkA==", + "dependencies": { + "bignumber.js": "*", + "lodash": "*", + "make-plural": "*" + } + }, "node_modules/i18next": { "version": "25.3.2", "resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.3.2.tgz", @@ -6501,6 +7734,18 @@ "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz", "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" }, + "node_modules/inversify": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/inversify/-/inversify-6.2.2.tgz", + "integrity": "sha512-KB836KHbZ9WrUnB8ax5MtadOwnqQYa+ZJO3KWbPFgcr4RIEnHM621VaqFZzOZd9+U7ln6upt9n0wJei7x2BNqw==", + "dependencies": { + "@inversifyjs/common": "1.4.0", + "@inversifyjs/core": "1.3.5" + }, + "peerDependencies": { + "reflect-metadata": "~0.2.2" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6696,6 +7941,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==" + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -6876,9 +8126,9 @@ "dev": true }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "dev": true, "bin": { "jiti": "lib/jiti-cli.mjs" @@ -6913,7 +8163,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -6951,7 +8200,6 @@ "version": "2.2.3", "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -7272,7 +8520,6 @@ "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -7294,6 +8541,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-plural": { + "version": "7.4.0", + "resolved": "https://registry.npmmirror.com/make-plural/-/make-plural-7.4.0.tgz", + "integrity": "sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7406,8 +8658,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "5.1.5", @@ -7450,8 +8701,7 @@ "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -7663,14 +8913,12 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -7688,9 +8936,9 @@ } }, "node_modules/pocketbase": { - "version": "0.26.1", - "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.1.tgz", - "integrity": "sha512-fjcPDpxyqTZCwqGUTPUV7vssIsNMqHxk9GxbhxYHPEf18RqX2d9cpSqbbHk7aas30jqkgptuKfG7aY/Mytjj3g==" + "version": "0.26.2", + "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.2.tgz", + "integrity": "sha512-WA8EOBc3QnSJh8rJ3iYoi9DmmPOMFIgVfAmIGux7wwruUEIzXgvrO4u0W2htfQjGIcyezJkdZOy5Xmh7SxAftw==" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -7749,8 +8997,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.11", @@ -8320,9 +9567,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", @@ -8515,9 +9762,9 @@ } }, "node_modules/react-router": { - "version": "7.7.0", - "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.7.0.tgz", - "integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==", + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.8.0.tgz", + "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -8536,11 +9783,11 @@ } }, "node_modules/react-router-dom": { - "version": "7.7.0", - "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.7.0.tgz", - "integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==", + "version": "7.8.0", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.8.0.tgz", + "integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==", "dependencies": { - "react-router": "7.7.0" + "react-router": "7.8.0" }, "engines": { "node": ">=20.0.0" @@ -8567,6 +9814,11 @@ "pify": "^2.3.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", @@ -8801,6 +10053,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -9075,7 +10335,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9200,6 +10459,98 @@ "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz", "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "peer": true, + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "peer": true + }, + "node_modules/styled-components/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "peer": true + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "peer": true + }, "node_modules/stylis": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz", @@ -9263,9 +10614,9 @@ "dev": true }, "node_modules/tailwind-csstree": { - "version": "0.1.1", - "resolved": "https://registry.npmmirror.com/tailwind-csstree/-/tailwind-csstree-0.1.1.tgz", - "integrity": "sha512-Tv5IXRHtlb7LanNXjmGg0vu84Iu+/sKDqTLJBO0kP/rggfjJDKnS/HV+e0qn8yIrRByhGg+908rSOxT/7o1diw==", + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/tailwind-csstree/-/tailwind-csstree-0.1.2.tgz", + "integrity": "sha512-SP36MjhkSGa0Yux/B863QyxljQku+WbGH5f+dqyFJXvdexemcC0Fwj/2du2DoXVkboO5Rc1Pd4i45EmFJual6w==", "dev": true, "engines": { "node": ">=18.18" @@ -9593,15 +10944,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.38.0.tgz", - "integrity": "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==", + "version": "8.39.0", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.39.0.tgz", + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.38.0", - "@typescript-eslint/parser": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/utils": "8.38.0" + "@typescript-eslint/eslint-plugin": "8.39.0", + "@typescript-eslint/parser": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0", + "@typescript-eslint/utils": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9612,7 +10963,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { @@ -9726,7 +11077,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10008,8 +11358,7 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { "version": "2.5.0", @@ -10038,17 +11387,17 @@ } }, "node_modules/zod": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.5.tgz", - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "version": "4.0.16", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.16.tgz", + "integrity": "sha512-Djo/cM339grjI7/HmN+ixYO2FzEMcWr/On50UlQ/RjrWK1I/hPpWhpC76heCptnRFpH0LMwrEbUY50HDc0V8wg==", "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 385077524..42744c9f7 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", @@ -10,17 +9,21 @@ "preview": "vite preview" }, "dependencies": { + "@ant-design/icons": "^6.0.0", "@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/document": "^0.3.4", + "@flowgram.ai/fixed-layout-editor": "^0.3.4", + "@flowgram.ai/minimap-plugin": "^0.3.4", "@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", @@ -29,19 +32,19 @@ "i18next-browser-languagedetector": "^8.2.0", "immer": "^10.1.1", "nanoid": "^5.1.5", - "pocketbase": "^0.26.1", + "pocketbase": "^0.26.2", "radash": "^12.1.1", "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", "react-i18next": "^15.6.1", - "react-router-dom": "^7.7.0", + "react-router-dom": "^7.8.0", "tailwind-merge": "^3.3.1", - "zod": "^4.0.5", - "zustand": "^5.0.6" + "zod": "^4.0.16", + "zustand": "^5.0.7" }, "devDependencies": { - "@eslint/js": "^9.31.0", + "@eslint/js": "^9.33.0", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/vite": "^4.1.11", "@types/file-saver": "^2.0.7", @@ -52,19 +55,19 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-legacy": "^6.1.1", "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.31.0", + "eslint": "^9.33.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-better-tailwindcss": "^3.7.1", + "eslint-plugin-better-tailwindcss": "^3.7.4", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "prettier": "^3.6.2", "tailwindcss": "^4.1.11", "typescript": "^5.8.3", - "typescript-eslint": "^8.38.0", + "typescript-eslint": "^8.39.0", "vite": "^6.3.5" } } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index dc75fbbea..bbd03f147 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -100,6 +100,10 @@ const RootApp = () => { ...antdTheme?.components?.Dropdown, paddingBlock: 9, }, + Form: { + ...antdTheme?.components?.Form, + itemMarginBottom: 28, + }, }, }} > diff --git a/ui/src/components/AppVersion.tsx b/ui/src/components/AppVersion.tsx index 07ea0ffa4..dbde4e39c 100644 --- a/ui/src/components/AppVersion.tsx +++ b/ui/src/components/AppVersion.tsx @@ -31,8 +31,10 @@ const AppVersionBadge = ({ className, style, children }: AppVersionBadgeProps) = {children} diff --git a/ui/src/components/CodeInput.tsx b/ui/src/components/CodeInput.tsx index a784af46c..90c1fc4a6 100644 --- a/ui/src/components/CodeInput.tsx +++ b/ui/src/components/CodeInput.tsx @@ -72,7 +72,7 @@ const CodeInput = ({ className, style, disabled, language, ...props }: CodeInput border: `1px solid ${isFocusWithin ? (themeToken.Input?.activeBorderColor ?? themeToken.colorPrimaryBorder) : themeToken.colorBorder}`, borderRadius: `${themeToken.borderRadius}px`, backgroundColor: disabled ? themeToken.colorBgContainerDisabled : themeToken.colorBgContainer, - boxShadow: isFocusWithin ? themeToken.Input?.activeShadow : undefined, + boxShadow: isFocusWithin ? themeToken.Input?.activeShadow : void 0, overflow: "hidden", }} > diff --git a/ui/src/components/DrawerForm.tsx b/ui/src/components/DrawerForm.tsx index f8386b2be..25fc6cf34 100644 --- a/ui/src/components/DrawerForm.tsx +++ b/ui/src/components/DrawerForm.tsx @@ -1,4 +1,5 @@ import { useTranslation } from "react-i18next"; +import { IconX } from "@tabler/icons-react"; import { useControllableValue } from "ahooks"; import { Button, Drawer, type DrawerProps, Flex, Form, type FormProps, type ModalProps } from "antd"; @@ -66,13 +67,14 @@ const DrawerForm = = any>({ }); const mergedFormProps: FormProps = { - clearOnDestroy: drawerProps?.destroyOnHidden ? true : undefined, + clearOnDestroy: drawerProps?.destroyOnHidden ? true : void 0, ...formProps, ...props, }; const mergedDrawerProps: DrawerProps = { ...drawerProps, + closeIcon: false, afterOpenChange: (open) => { if (!open && !mergedFormProps.preserve) { formInst.resetFields(); @@ -109,7 +111,7 @@ const DrawerForm = = any>({ + @@ -120,7 +122,21 @@ const DrawerForm = = any>({ } forceRender open={open} - title={title} + title={ + +
{title}
+ {mergedDrawerProps.closeIcon !== false && ( + + + + + + ); +}; + +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..abea0f303 --- /dev/null +++ b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx @@ -0,0 +1,62 @@ +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 nodeRenderData = node.getData(FlowNodeRenderData)!; + const nodeTransformData = node.getData(FlowNodeTransformData)!; + + const handleMouseEnter = () => { + nodeRenderData.activated = true; + }; + + const handleMouseLeave = () => { + nodeRenderData.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)} +
+
+ ); +}; + +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..ee2cba5c5 --- /dev/null +++ b/ui/src/components/workflow/designer/flowgram.css @@ -0,0 +1,26 @@ +/* 隐藏 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; + width: 0 !important; + height: 0 !important; + visibility: hidden !important; + transform: scale(0) !important; +} + +/* 控制 flowgram 线条样式 */ +/* flowgram 暂未提供相关配置项 */ +.flow-lines-container { + stroke-width: 1.5px; +} diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx new file mode 100644 index 000000000..08dede4f9 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BizApplyNodeConfigForm from "./BizApplyNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BizApplyNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BizApplyNodeConfigDrawer = (_: BizApplyNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BizApply) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizApply}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default BizApplyNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx new file mode 100644 index 000000000..76fd62bfe --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx @@ -0,0 +1,652 @@ +import { memo, useEffect, useMemo, useState } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { Link } from "react-router"; +import { QuestionCircleOutlined as IconQuestionCircleOutlined } from "@ant-design/icons"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { IconChevronRight, IconCircleMinus, IconPlus } from "@tabler/icons-react"; +import { useControllableValue } from "ahooks"; +import { type AnchorProps, AutoComplete, Button, Divider, Flex, Form, type FormInstance, Input, InputNumber, Select, Switch, Tooltip, Typography } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import AccessEditDrawer from "@/components/access/AccessEditDrawer"; +import AccessSelect from "@/components/access/AccessSelect"; +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; +import ACMEDns01ProviderSelect from "@/components/provider/ACMEDns01ProviderSelect"; +import CAProviderSelect from "@/components/provider/CAProviderSelect"; +import Show from "@/components/Show"; +import { ACCESS_USAGES, ACME_DNS01_PROVIDERS, accessProvidersMap, acmeDns01ProvidersMap, caProvidersMap } from "@/domain/provider"; +import { type WorkflowNodeConfigForApply, defaultNodeConfigForApply } from "@/domain/workflow"; +import { useAntdForm, useZustandShallowSelector } from "@/hooks"; +import { useAccessesStore } from "@/stores/access"; +import { useContactEmailsStore } from "@/stores/contact"; +import { validDomainName, validIPv4Address, validIPv6Address } from "@/utils/validators"; + +import { FormNestedFieldsContextProvider, NodeFormContextProvider } from "./_context"; +import BizApplyNodeConfigFormProviderAliyunESA from "./BizApplyNodeConfigFormProviderAliyunESA"; +import BizApplyNodeConfigFormProviderAWSRoute53 from "./BizApplyNodeConfigFormProviderAWSRoute53"; +import BizApplyNodeConfigFormProviderHuaweiCloudDNS from "./BizApplyNodeConfigFormProviderHuaweiCloudDNS"; +import BizApplyNodeConfigFormProviderJDCloudDNS from "./BizApplyNodeConfigFormProviderJDCloudDNS"; +import BizApplyNodeConfigFormProviderTencentCloudEO from "./BizApplyNodeConfigFormProviderTencentCloudEO"; +import { NodeType } from "../nodes/typings"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +export interface BizApplyNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BizApply) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizApply}`); + } + + const { i18n, t } = useTranslation(); + + const { accesses } = useAccessesStore(useZustandShallowSelector("accesses")); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForApply | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBizApplyConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); + const fieldProviderAccessId = Form.useWatch("providerAccessId", { form: formInst, preserve: true }); + const fieldCAProvider = Form.useWatch("caProvider", { form: formInst, preserve: true }); + + const NestedProviderConfigFields = useMemo(() => { + /* + 注意:如果追加新的子组件,请保持以 ASCII 排序。 + NOTICE: If you add new child component, please keep ASCII order. + */ + switch (fieldProvider) { + case ACME_DNS01_PROVIDERS.ALIYUN_ESA: + return BizApplyNodeConfigFormProviderAliyunESA; + case ACME_DNS01_PROVIDERS.AWS: + case ACME_DNS01_PROVIDERS.AWS_ROUTE53: + return BizApplyNodeConfigFormProviderAWSRoute53; + case ACME_DNS01_PROVIDERS.HUAWEICLOUD: + case ACME_DNS01_PROVIDERS.HUAWEICLOUD_DNS: + return BizApplyNodeConfigFormProviderHuaweiCloudDNS; + case ACME_DNS01_PROVIDERS.JDCLOUD: + case ACME_DNS01_PROVIDERS.JDCLOUD_DNS: + return BizApplyNodeConfigFormProviderJDCloudDNS; + case ACME_DNS01_PROVIDERS.TENCENTCLOUD_EO: + return BizApplyNodeConfigFormProviderTencentCloudEO; + } + }, [fieldProvider]); + + const [showProvider, setShowProvider] = useState(false); + useEffect(() => { + // 通常情况下每个授权信息只对应一个 DNS 提供商,此时无需显示 DNS 提供商字段; + // 如果对应多个(如 AWS 的 Route53、Lightsail,阿里云的 DNS、ESA,腾讯云的 DNS、EdgeOne 等),则显示。 + if (fieldProviderAccessId) { + const access = accesses.find((e) => e.id === fieldProviderAccessId); + const providers = Array.from(acmeDns01ProvidersMap.values()).filter((e) => e.provider === access?.provider); + setShowProvider(providers.length > 1); + } else { + setShowProvider(false); + } + }, [accesses, fieldProviderAccessId]); + + const [showCAProviderAccess, setShowCAProviderAccess] = useState(false); + useEffect(() => { + // 内置的 CA 提供商(如 Let's Encrypt)无需显示授权信息字段 + if (fieldCAProvider) { + const provider = caProvidersMap.get(fieldCAProvider); + setShowCAProviderAccess(!provider?.builtin); + } else { + setShowCAProviderAccess(false); + } + }, [fieldCAProvider]); + + const handleProviderSelect = (value: string) => { + if (fieldProvider === value) return; + + // 切换 DNS 提供商时联动授权信息 + if (initialValues?.provider === value) { + formInst.setFieldValue("providerAccessId", initialValues?.providerAccessId); + } else { + if (acmeDns01ProvidersMap.get(fieldProvider)?.provider !== acmeDns01ProvidersMap.get(value)?.provider) { + formInst.setFieldValue("providerAccessId", void 0); + } + } + }; + + const handleProviderAccessSelect = (value: string) => { + // 切换授权信息时联动 DNS 提供商 + const access = accesses.find((access) => access.id === value); + const provider = Array.from(acmeDns01ProvidersMap.values()).find((provider) => provider.provider === access?.provider); + if (fieldProvider !== provider?.type) { + formInst.setFieldValue("provider", provider?.type); + } + }; + + const handleCAProviderSelect = (value?: string | undefined) => { + // 切换 CA 提供商时联动授权信息 + if (value === "") { + setTimeout(() => { + formInst.setFieldValue("caProvider", void 0); + formInst.setFieldValue("caProviderAccessId", void 0); + }, 1); + } else if (initialValues?.caProvider === value) { + formInst.setFieldValue("caProviderAccessId", initialValues?.caProviderAccessId); + } else { + if (caProvidersMap.get(fieldCAProvider)?.provider !== caProvidersMap.get(value!)?.provider) { + formInst.setFieldValue("caProviderAccessId", void 0); + } + } + }; + + return ( + + +
+ } + > + + + + } + > + + + + + + } + > + ({ value }))} + placeholder={t("workflow_node.apply.form.acme_profile.placeholder")} + filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())} + /> + +
+ +
+ + + {t("workflow_node.apply.form_anchor.advanced.title")} + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + +
+ +
+ + + {t("workflow_node.apply.form_anchor.strategy.title")} + + + + } + > + +
{t("workflow_node.apply.form.skip_before_expiry_days.prefix")}
+ + + +
{t("workflow_node.apply.form.skip_before_expiry_days.suffix")}
+
+
+
+ +
+ ); +}; + +const EmailInput = memo( + ({ disabled, placeholder, ...props }: { disabled?: boolean; placeholder?: string; value?: string; onChange?: (value: string) => void }) => { + const { emails, fetchEmails, removeEmail } = useContactEmailsStore(); + useEffect(() => { + fetchEmails(); + }, []); + + const [value, setValue] = useControllableValue(props, { + valuePropName: "value", + defaultValuePropName: "defaultValue", + trigger: "onChange", + }); + + const [inputValue, setInputValue] = useState(); + + const renderOptionLabel = (email: string, removable: boolean = false) => ( +
+ {email} + {removable && ( +
+ ); + + const options = useMemo(() => { + const temp = emails.map((email) => ({ + label: renderOptionLabel(email, true), + value: email, + })); + + if (!!inputValue && temp.every((option) => option.value !== inputValue)) { + temp.unshift({ + label: renderOptionLabel(inputValue), + value: inputValue, + }); + } + + return temp; + }, [emails, inputValue]); + + const handleChange = (value: string) => { + setValue(value); + }; + + const handleSearch = (value: string) => { + setInputValue(value?.trim()); + }; + + return ( + + ); + } +); + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters", "certificate", "advanced", "strategy"].map((key) => ({ + key: key, + title: t(`workflow_node.apply.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return { + domains: "", + provider: "", + providerAccessId: "", + ...defaultNodeConfigForApply(), + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + domains: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + }, t("common.errmsg.domain_invalid")), + contactEmail: z.email(t("common.errmsg.email_invalid")), + challengeType: z.string().nullish(), + provider: z.string().nonempty(t("workflow_node.apply.form.provider.placeholder")), + providerAccessId: z.string().nonempty(t("workflow_node.apply.form.provider_access.placeholder")), + providerConfig: z.any().nullish(), + caProvider: z.string().nullish(), + caProviderAccessId: z.string().nullish(), + caProviderConfig: z.any().nullish(), + keyAlgorithm: z.string().nonempty(t("workflow_node.apply.form.key_algorithm.placeholder")), + nameservers: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e)); + }, t("common.errmsg.host_invalid")), + dnsPropagationWait: z.preprocess( + (v) => (v == null || v === "" ? void 0 : Number(v)), + z + .number() + .int(t("workflow_node.apply.form.dns_propagation_wait.placeholder")) + .gte(0, t("workflow_node.apply.form.dns_propagation_wait.placeholder")) + .nullish() + ), + dnsPropagationTimeout: z.preprocess( + (v) => (v == null || v === "" ? void 0 : Number(v)), + z + .number() + .int(t("workflow_node.apply.form.dns_propagation_timeout.placeholder")) + .gte(1, t("workflow_node.apply.form.dns_propagation_timeout.placeholder")) + .nullish() + ), + dnsTTL: z.preprocess( + (v) => (v == null || v === "" ? void 0 : Number(v)), + z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish() + ), + acmeProfile: z.string().nullish(), + disableFollowCNAME: z.boolean().nullish(), + disableARI: z.boolean().nullish(), + skipBeforeExpiryDays: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder")) + .gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")) + ), + }) + .superRefine((values, ctx) => { + if (values.caProvider) { + const provider = caProvidersMap.get(values.caProvider); + if (!provider?.builtin && !values.caProviderAccessId) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.apply.form.ca_provider_access.placeholder"), + path: ["caProviderAccessId"], + }); + } + } + }); +}; + +const _default = Object.assign(BizApplyNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAWSRoute53.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAWSRoute53.tsx new file mode 100644 index 000000000..82861bfef --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAWSRoute53.tsx @@ -0,0 +1,64 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizApplyNodeConfigFormProviderAWSRoute53 = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "us-east-1", + hostedZoneId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.apply.form.aws_route53_region.placeholder")), + hostedZoneId: z.string().nonempty(t("workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder")), + }); +}; + +const _default = Object.assign(BizApplyNodeConfigFormProviderAWSRoute53, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAliyunESA.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAliyunESA.tsx new file mode 100644 index 000000000..3a1e0fb0d --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderAliyunESA.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizApplyNodeConfigFormProviderAliyunESA = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "cn-hangzhou", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.apply.form.aliyun_esa_region.placeholder")), + }); +}; + +const _default = Object.assign(BizApplyNodeConfigFormProviderAliyunESA, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderHuaweiCloudDNS.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderHuaweiCloudDNS.tsx new file mode 100644 index 000000000..5dc69bd0f --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderHuaweiCloudDNS.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizApplyNodeConfigFormProviderHuaweiCloudDNS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "cn-north-1", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.apply.form.huaweicloud_dns_region.placeholder")), + }); +}; + +const _default = Object.assign(BizApplyNodeConfigFormProviderHuaweiCloudDNS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderJDCloudDNS.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderJDCloudDNS.tsx new file mode 100644 index 000000000..9214a719f --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderJDCloudDNS.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizApplyNodeConfigFormProviderJDCloudDNS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + regionId: "cn-north-1", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + regionId: z.string().nonempty(t("workflow_node.apply.form.jdcloud_dns_region_id.placeholder")), + }); +}; + +const _default = Object.assign(BizApplyNodeConfigFormProviderJDCloudDNS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderTencentCloudEO.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderTencentCloudEO.tsx new file mode 100644 index 000000000..adbcf0a10 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigFormProviderTencentCloudEO.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizApplyNodeConfigFormProviderTencentCloudEO = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + zoneId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + zoneId: z.string().nonempty(t("workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder")), + }); +}; + +const _default = Object.assign(BizApplyNodeConfigFormProviderTencentCloudEO, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx new file mode 100644 index 000000000..49151c0ca --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigDrawer.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BizDeployNodeConfigForm from "./BizDeployNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BizDeployNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BizDeployNodeConfigDrawer = (_: BizDeployNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BizDeploy) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizDeploy}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); + + return ( + + + + ); +}; + +export default BizDeployNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigForm.tsx new file mode 100644 index 000000000..4727d93c8 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigForm.tsx @@ -0,0 +1,576 @@ +import { useEffect, useMemo, useState } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { QuestionCircleOutlined as IconQuestionCircleOutlined } from "@ant-design/icons"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { IconPlus } from "@tabler/icons-react"; +import { type AnchorProps, Button, Divider, Flex, Form, type FormInstance, Select, Switch, Tooltip, Typography, theme } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import AccessEditDrawer from "@/components/access/AccessEditDrawer"; +import AccessSelect from "@/components/access/AccessSelect"; +import DeploymentProviderPicker from "@/components/provider/DeploymentProviderPicker"; +import DeploymentProviderSelect from "@/components/provider/DeploymentProviderSelect"; +import Show from "@/components/Show"; +import { ACCESS_USAGES, DEPLOYMENT_PROVIDERS, accessProvidersMap, deploymentProvidersMap } from "@/domain/provider"; +import { type WorkflowNodeConfigForDeploy, WorkflowNodeType, defaultNodeConfigForDeploy } from "@/domain/workflow"; +import { useAntdForm, useZustandShallowSelector } from "@/hooks"; +import { useWorkflowStore } from "@/stores/workflow"; + +import { FormNestedFieldsContextProvider, NodeFormContextProvider } from "./_context"; +import BizDeployNodeConfigFormProvider1PanelConsole from "./BizDeployNodeConfigFormProvider1PanelConsole"; +import BizDeployNodeConfigFormProvider1PanelSite from "./BizDeployNodeConfigFormProvider1PanelSite"; +import BizDeployNodeConfigFormProviderAliyunALB from "./BizDeployNodeConfigFormProviderAliyunALB"; +import BizDeployNodeConfigFormProviderAliyunAPIGW from "./BizDeployNodeConfigFormProviderAliyunAPIGW"; +import BizDeployNodeConfigFormProviderAliyunCAS from "./BizDeployNodeConfigFormProviderAliyunCAS"; +import BizDeployNodeConfigFormProviderAliyunCASDeploy from "./BizDeployNodeConfigFormProviderAliyunCASDeploy"; +import BizDeployNodeConfigFormProviderAliyunCDN from "./BizDeployNodeConfigFormProviderAliyunCDN"; +import BizDeployNodeConfigFormProviderAliyunCLB from "./BizDeployNodeConfigFormProviderAliyunCLB"; +import BizDeployNodeConfigFormProviderAliyunDCDN from "./BizDeployNodeConfigFormProviderAliyunDCDN"; +import BizDeployNodeConfigFormProviderAliyunDDoS from "./BizDeployNodeConfigFormProviderAliyunDDoS"; +import BizDeployNodeConfigFormProviderAliyunESA from "./BizDeployNodeConfigFormProviderAliyunESA"; +import BizDeployNodeConfigFormProviderAliyunFC from "./BizDeployNodeConfigFormProviderAliyunFC"; +import BizDeployNodeConfigFormProviderAliyunGA from "./BizDeployNodeConfigFormProviderAliyunGA"; +import BizDeployNodeConfigFormProviderAliyunLive from "./BizDeployNodeConfigFormProviderAliyunLive"; +import BizDeployNodeConfigFormProviderAliyunNLB from "./BizDeployNodeConfigFormProviderAliyunNLB"; +import BizDeployNodeConfigFormProviderAliyunOSS from "./BizDeployNodeConfigFormProviderAliyunOSS"; +import BizDeployNodeConfigFormProviderAliyunVOD from "./BizDeployNodeConfigFormProviderAliyunVOD"; +import BizDeployNodeConfigFormProviderAliyunWAF from "./BizDeployNodeConfigFormProviderAliyunWAF"; +import BizDeployNodeConfigFormProviderAPISIX from "./BizDeployNodeConfigFormProviderAPISIX"; +import BizDeployNodeConfigFormProviderAWSACM from "./BizDeployNodeConfigFormProviderAWSACM"; +import BizDeployNodeConfigFormProviderAWSCloudFront from "./BizDeployNodeConfigFormProviderAWSCloudFront"; +import BizDeployNodeConfigFormProviderAWSIAM from "./BizDeployNodeConfigFormProviderAWSIAM"; +import BizDeployNodeConfigFormProviderAzureKeyVault from "./BizDeployNodeConfigFormProviderAzureKeyVault"; +import BizDeployNodeConfigFormProviderBaiduCloudAppBLB from "./BizDeployNodeConfigFormProviderBaiduCloudAppBLB"; +import BizDeployNodeConfigFormProviderBaiduCloudBLB from "./BizDeployNodeConfigFormProviderBaiduCloudBLB"; +import BizDeployNodeConfigFormProviderBaiduCloudCDN from "./BizDeployNodeConfigFormProviderBaiduCloudCDN"; +import BizDeployNodeConfigFormProviderBaishanCDN from "./BizDeployNodeConfigFormProviderBaishanCDN"; +import BizDeployNodeConfigFormProviderBaotaPanelConsole from "./BizDeployNodeConfigFormProviderBaotaPanelConsole"; +import BizDeployNodeConfigFormProviderBaotaPanelSite from "./BizDeployNodeConfigFormProviderBaotaPanelSite"; +import BizDeployNodeConfigFormProviderBaotaWAFSite from "./BizDeployNodeConfigFormProviderBaotaWAFSite"; +import BizDeployNodeConfigFormProviderBunnyCDN from "./BizDeployNodeConfigFormProviderBunnyCDN"; +import BizDeployNodeConfigFormProviderBytePlusCDN from "./BizDeployNodeConfigFormProviderBytePlusCDN"; +import BizDeployNodeConfigFormProviderCdnfly from "./BizDeployNodeConfigFormProviderCdnfly"; +import BizDeployNodeConfigFormProviderCTCCCloudAO from "./BizDeployNodeConfigFormProviderCTCCCloudAO"; +import BizDeployNodeConfigFormProviderCTCCCloudCDN from "./BizDeployNodeConfigFormProviderCTCCCloudCDN"; +import BizDeployNodeConfigFormProviderCTCCCloudELB from "./BizDeployNodeConfigFormProviderCTCCCloudELB"; +import BizDeployNodeConfigFormProviderCTCCCloudICDN from "./BizDeployNodeConfigFormProviderCTCCCloudICDN"; +import BizDeployNodeConfigFormProviderCTCCCloudLVDN from "./BizDeployNodeConfigFormProviderCTCCCloudLVDN"; +import BizDeployNodeConfigFormProviderDogeCloudCDN from "./BizDeployNodeConfigFormProviderDogeCloudCDN"; +import BizDeployNodeConfigFormProviderEdgioApplications from "./BizDeployNodeConfigFormProviderEdgioApplications"; +import BizDeployNodeConfigFormProviderFlexCDN from "./BizDeployNodeConfigFormProviderFlexCDN"; +import BizDeployNodeConfigFormProviderGcoreCDN from "./BizDeployNodeConfigFormProviderGcoreCDN"; +import BizDeployNodeConfigFormProviderGoEdge from "./BizDeployNodeConfigFormProviderGoEdge"; +import BizDeployNodeConfigFormProviderHuaweiCloudCDN from "./BizDeployNodeConfigFormProviderHuaweiCloudCDN"; +import BizDeployNodeConfigFormProviderHuaweiCloudELB from "./BizDeployNodeConfigFormProviderHuaweiCloudELB"; +import BizDeployNodeConfigFormProviderHuaweiCloudWAF from "./BizDeployNodeConfigFormProviderHuaweiCloudWAF"; +import BizDeployNodeConfigFormProviderJDCloudALB from "./BizDeployNodeConfigFormProviderJDCloudALB"; +import BizDeployNodeConfigFormProviderJDCloudCDN from "./BizDeployNodeConfigFormProviderJDCloudCDN"; +import BizDeployNodeConfigFormProviderJDCloudLive from "./BizDeployNodeConfigFormProviderJDCloudLive"; +import BizDeployNodeConfigFormProviderJDCloudVOD from "./BizDeployNodeConfigFormProviderJDCloudVOD"; +import BizDeployNodeConfigFormProviderKong from "./BizDeployNodeConfigFormProviderKong"; +import BizDeployNodeConfigFormProviderKubernetesSecret from "./BizDeployNodeConfigFormProviderKubernetesSecret"; +import BizDeployNodeConfigFormProviderLeCDN from "./BizDeployNodeConfigFormProviderLeCDN"; +import BizDeployNodeConfigFormProviderLocal from "./BizDeployNodeConfigFormProviderLocal"; +import BizDeployNodeConfigFormProviderNetlifySite from "./BizDeployNodeConfigFormProviderNetlifySite"; +import BizDeployNodeConfigFormProviderProxmoxVE from "./BizDeployNodeConfigFormProviderProxmoxVE"; +import BizDeployNodeConfigFormProviderQiniuCDN from "./BizDeployNodeConfigFormProviderQiniuCDN"; +import BizDeployNodeConfigFormProviderQiniuKodo from "./BizDeployNodeConfigFormProviderQiniuKodo"; +import BizDeployNodeConfigFormProviderQiniuPili from "./BizDeployNodeConfigFormProviderQiniuPili"; +import BizDeployNodeConfigFormProviderRainYunRCDN from "./BizDeployNodeConfigFormProviderRainYunRCDN"; +import BizDeployNodeConfigFormProviderRatPanelSite from "./BizDeployNodeConfigFormProviderRatPanelSite"; +import BizDeployNodeConfigFormProviderSafeLine from "./BizDeployNodeConfigFormProviderSafeLine"; +import BizDeployNodeConfigFormProviderSSH from "./BizDeployNodeConfigFormProviderSSH"; +import BizDeployNodeConfigFormProviderTencentCloudCDN from "./BizDeployNodeConfigFormProviderTencentCloudCDN"; +import BizDeployNodeConfigFormProviderTencentCloudCLB from "./BizDeployNodeConfigFormProviderTencentCloudCLB"; +import BizDeployNodeConfigFormProviderTencentCloudCOS from "./BizDeployNodeConfigFormProviderTencentCloudCOS"; +import BizDeployNodeConfigFormProviderTencentCloudCSS from "./BizDeployNodeConfigFormProviderTencentCloudCSS"; +import BizDeployNodeConfigFormProviderTencentCloudECDN from "./BizDeployNodeConfigFormProviderTencentCloudECDN"; +import BizDeployNodeConfigFormProviderTencentCloudEO from "./BizDeployNodeConfigFormProviderTencentCloudEO"; +import BizDeployNodeConfigFormProviderTencentCloudGAAP from "./BizDeployNodeConfigFormProviderTencentCloudGAAP"; +import BizDeployNodeConfigFormProviderTencentCloudSCF from "./BizDeployNodeConfigFormProviderTencentCloudSCF"; +import BizDeployNodeConfigFormProviderTencentCloudSSL from "./BizDeployNodeConfigFormProviderTencentCloudSSL"; +import BizDeployNodeConfigFormProviderTencentCloudSSLDeploy from "./BizDeployNodeConfigFormProviderTencentCloudSSLDeploy"; +import BizDeployNodeConfigFormProviderTencentCloudSSLUpdate from "./BizDeployNodeConfigFormProviderTencentCloudSSLUpdate"; +import BizDeployNodeConfigFormProviderTencentCloudVOD from "./BizDeployNodeConfigFormProviderTencentCloudVOD"; +import BizDeployNodeConfigFormProviderTencentCloudWAF from "./BizDeployNodeConfigFormProviderTencentCloudWAF"; +import BizDeployNodeConfigFormProviderUCloudUCDN from "./BizDeployNodeConfigFormProviderUCloudUCDN"; +import BizDeployNodeConfigFormProviderUCloudUS3 from "./BizDeployNodeConfigFormProviderUCloudUS3"; +import BizDeployNodeConfigFormProviderUniCloudWebHost from "./BizDeployNodeConfigFormProviderUniCloudWebHost"; +import BizDeployNodeConfigFormProviderUpyunCDN from "./BizDeployNodeConfigFormProviderUpyunCDN"; +import BizDeployNodeConfigFormProviderUpyunFile from "./BizDeployNodeConfigFormProviderUpyunFile"; +import BizDeployNodeConfigFormProviderVolcEngineALB from "./BizDeployNodeConfigFormProviderVolcEngineALB"; +import BizDeployNodeConfigFormProviderVolcEngineCDN from "./BizDeployNodeConfigFormProviderVolcEngineCDN"; +import BizDeployNodeConfigFormProviderVolcEngineCertCenter from "./BizDeployNodeConfigFormProviderVolcEngineCertCenter"; +import BizDeployNodeConfigFormProviderVolcEngineCLB from "./BizDeployNodeConfigFormProviderVolcEngineCLB"; +import BizDeployNodeConfigFormProviderVolcEngineDCDN from "./BizDeployNodeConfigFormProviderVolcEngineDCDN"; +import BizDeployNodeConfigFormProviderVolcEngineImageX from "./BizDeployNodeConfigFormProviderVolcEngineImageX"; +import BizDeployNodeConfigFormProviderVolcEngineLive from "./BizDeployNodeConfigFormProviderVolcEngineLive"; +import BizDeployNodeConfigFormProviderVolcEngineTOS from "./BizDeployNodeConfigFormProviderVolcEngineTOS"; +import BizDeployNodeConfigFormProviderWangsuCDN from "./BizDeployNodeConfigFormProviderWangsuCDN"; +import BizDeployNodeConfigFormProviderWangsuCDNPro from "./BizDeployNodeConfigFormProviderWangsuCDNPro"; +import BizDeployNodeConfigFormProviderWangsuCertificate from "./BizDeployNodeConfigFormProviderWangsuCertificate"; +import BizDeployNodeConfigFormProviderWebhook from "./BizDeployNodeConfigFormProviderWebhook"; +import { NodeType } from "../nodes/typings"; + +export interface BizDeployNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BizDeployNodeConfigForm = ({ node, ...props }: BizDeployNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BizDeploy) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizDeploy}`); + } + + const { i18n, t } = useTranslation(); + + const { token: themeToken } = theme.useToken(); + + const { getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"])); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForDeploy | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBizDeployConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); + + const [showProviderAccess, setShowProviderAccess] = useState(false); + useEffect(() => { + // 内置的部署提供商(如本地部署)无需显示授权信息字段 + if (fieldProvider) { + const provider = deploymentProvidersMap.get(fieldProvider); + setShowProviderAccess(!provider?.builtin); + } else { + setShowProviderAccess(false); + } + }, [fieldProvider]); + + // TODO: 适配 flowgram + const certificateCandidates = useMemo(() => { + const previousNodes = getWorkflowOuptutBeforeId(node.id, "certificate"); + return previousNodes + .filter((node) => node.type === WorkflowNodeType.Apply || node.type === WorkflowNodeType.Upload) + .map((item) => { + return { + label: item.name, + options: (item.outputs ?? [])?.map((output) => { + return { + label: output.label, + value: `${item.id}#${output.name}`, + }; + }), + }; + }) + .filter((group) => group.options.length > 0); + }, [node.id]); + + const NestedProviderConfigFields = useMemo(() => { + /* + 注意:如果追加新的子组件,请保持以 ASCII 排序。 + NOTICE: If you add new child component, please keep ASCII order. + */ + switch (fieldProvider) { + case DEPLOYMENT_PROVIDERS["1PANEL_CONSOLE"]: + return BizDeployNodeConfigFormProvider1PanelConsole; + case DEPLOYMENT_PROVIDERS["1PANEL_SITE"]: + return BizDeployNodeConfigFormProvider1PanelSite; + case DEPLOYMENT_PROVIDERS.ALIYUN_ALB: + return BizDeployNodeConfigFormProviderAliyunALB; + case DEPLOYMENT_PROVIDERS.ALIYUN_APIGW: + return BizDeployNodeConfigFormProviderAliyunAPIGW; + case DEPLOYMENT_PROVIDERS.ALIYUN_CAS: + return BizDeployNodeConfigFormProviderAliyunCAS; + case DEPLOYMENT_PROVIDERS.ALIYUN_CAS_DEPLOY: + return BizDeployNodeConfigFormProviderAliyunCASDeploy; + case DEPLOYMENT_PROVIDERS.ALIYUN_CLB: + return BizDeployNodeConfigFormProviderAliyunCLB; + case DEPLOYMENT_PROVIDERS.ALIYUN_CDN: + return BizDeployNodeConfigFormProviderAliyunCDN; + case DEPLOYMENT_PROVIDERS.ALIYUN_DCDN: + return BizDeployNodeConfigFormProviderAliyunDCDN; + case DEPLOYMENT_PROVIDERS.ALIYUN_DDOS: + return BizDeployNodeConfigFormProviderAliyunDDoS; + case DEPLOYMENT_PROVIDERS.ALIYUN_ESA: + return BizDeployNodeConfigFormProviderAliyunESA; + case DEPLOYMENT_PROVIDERS.ALIYUN_FC: + return BizDeployNodeConfigFormProviderAliyunFC; + case DEPLOYMENT_PROVIDERS.ALIYUN_GA: + return BizDeployNodeConfigFormProviderAliyunGA; + case DEPLOYMENT_PROVIDERS.ALIYUN_LIVE: + return BizDeployNodeConfigFormProviderAliyunLive; + case DEPLOYMENT_PROVIDERS.ALIYUN_NLB: + return BizDeployNodeConfigFormProviderAliyunNLB; + case DEPLOYMENT_PROVIDERS.ALIYUN_OSS: + return BizDeployNodeConfigFormProviderAliyunOSS; + case DEPLOYMENT_PROVIDERS.ALIYUN_VOD: + return BizDeployNodeConfigFormProviderAliyunVOD; + case DEPLOYMENT_PROVIDERS.ALIYUN_WAF: + return BizDeployNodeConfigFormProviderAliyunWAF; + case DEPLOYMENT_PROVIDERS.APISIX: + return BizDeployNodeConfigFormProviderAPISIX; + case DEPLOYMENT_PROVIDERS.AWS_ACM: + return BizDeployNodeConfigFormProviderAWSACM; + case DEPLOYMENT_PROVIDERS.AWS_CLOUDFRONT: + return BizDeployNodeConfigFormProviderAWSCloudFront; + case DEPLOYMENT_PROVIDERS.AWS_IAM: + return BizDeployNodeConfigFormProviderAWSIAM; + case DEPLOYMENT_PROVIDERS.AZURE_KEYVAULT: + return BizDeployNodeConfigFormProviderAzureKeyVault; + case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_APPBLB: + return BizDeployNodeConfigFormProviderBaiduCloudAppBLB; + case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_BLB: + return BizDeployNodeConfigFormProviderBaiduCloudBLB; + case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_CDN: + return BizDeployNodeConfigFormProviderBaiduCloudCDN; + case DEPLOYMENT_PROVIDERS.BAISHAN_CDN: + return BizDeployNodeConfigFormProviderBaishanCDN; + case DEPLOYMENT_PROVIDERS.BAOTAPANEL_CONSOLE: + return BizDeployNodeConfigFormProviderBaotaPanelConsole; + case DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE: + return BizDeployNodeConfigFormProviderBaotaPanelSite; + case DEPLOYMENT_PROVIDERS.BAOTAWAF_SITE: + return BizDeployNodeConfigFormProviderBaotaWAFSite; + case DEPLOYMENT_PROVIDERS.BUNNY_CDN: + return BizDeployNodeConfigFormProviderBunnyCDN; + case DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN: + return BizDeployNodeConfigFormProviderBytePlusCDN; + case DEPLOYMENT_PROVIDERS.CDNFLY: + return BizDeployNodeConfigFormProviderCdnfly; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_AO: + return BizDeployNodeConfigFormProviderCTCCCloudAO; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_CDN: + return BizDeployNodeConfigFormProviderCTCCCloudCDN; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_ELB: + return BizDeployNodeConfigFormProviderCTCCCloudELB; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_ICDN: + return BizDeployNodeConfigFormProviderCTCCCloudICDN; + case DEPLOYMENT_PROVIDERS.CTCCCLOUD_LVDN: + return BizDeployNodeConfigFormProviderCTCCCloudLVDN; + case DEPLOYMENT_PROVIDERS.DOGECLOUD_CDN: + return BizDeployNodeConfigFormProviderDogeCloudCDN; + case DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS: + return BizDeployNodeConfigFormProviderEdgioApplications; + case DEPLOYMENT_PROVIDERS.FLEXCDN: + return BizDeployNodeConfigFormProviderFlexCDN; + case DEPLOYMENT_PROVIDERS.GCORE_CDN: + return BizDeployNodeConfigFormProviderGcoreCDN; + case DEPLOYMENT_PROVIDERS.GOEDGE: + return BizDeployNodeConfigFormProviderGoEdge; + case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_CDN: + return BizDeployNodeConfigFormProviderHuaweiCloudCDN; + case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB: + return BizDeployNodeConfigFormProviderHuaweiCloudELB; + case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF: + return BizDeployNodeConfigFormProviderHuaweiCloudWAF; + case DEPLOYMENT_PROVIDERS.JDCLOUD_ALB: + return BizDeployNodeConfigFormProviderJDCloudALB; + case DEPLOYMENT_PROVIDERS.JDCLOUD_CDN: + return BizDeployNodeConfigFormProviderJDCloudCDN; + case DEPLOYMENT_PROVIDERS.JDCLOUD_LIVE: + return BizDeployNodeConfigFormProviderJDCloudLive; + case DEPLOYMENT_PROVIDERS.JDCLOUD_VOD: + return BizDeployNodeConfigFormProviderJDCloudVOD; + case DEPLOYMENT_PROVIDERS.KONG: + return BizDeployNodeConfigFormProviderKong; + case DEPLOYMENT_PROVIDERS.KUBERNETES_SECRET: + return BizDeployNodeConfigFormProviderKubernetesSecret; + case DEPLOYMENT_PROVIDERS.LECDN: + return BizDeployNodeConfigFormProviderLeCDN; + case DEPLOYMENT_PROVIDERS.LOCAL: + return BizDeployNodeConfigFormProviderLocal; + case DEPLOYMENT_PROVIDERS.NETLIFY_SITE: + return BizDeployNodeConfigFormProviderNetlifySite; + case DEPLOYMENT_PROVIDERS.PROXMOXVE: + return BizDeployNodeConfigFormProviderProxmoxVE; + case DEPLOYMENT_PROVIDERS.QINIU_CDN: + return BizDeployNodeConfigFormProviderQiniuCDN; + case DEPLOYMENT_PROVIDERS.QINIU_KODO: + return BizDeployNodeConfigFormProviderQiniuKodo; + case DEPLOYMENT_PROVIDERS.QINIU_PILI: + return BizDeployNodeConfigFormProviderQiniuPili; + case DEPLOYMENT_PROVIDERS.RAINYUN_RCDN: + return BizDeployNodeConfigFormProviderRainYunRCDN; + case DEPLOYMENT_PROVIDERS.RATPANEL_SITE: + return BizDeployNodeConfigFormProviderRatPanelSite; + case DEPLOYMENT_PROVIDERS.SAFELINE: + return BizDeployNodeConfigFormProviderSafeLine; + case DEPLOYMENT_PROVIDERS.SSH: + return BizDeployNodeConfigFormProviderSSH; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CDN: + return BizDeployNodeConfigFormProviderTencentCloudCDN; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CLB: + return BizDeployNodeConfigFormProviderTencentCloudCLB; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_COS: + return BizDeployNodeConfigFormProviderTencentCloudCOS; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_CSS: + return BizDeployNodeConfigFormProviderTencentCloudCSS; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_ECDN: + return BizDeployNodeConfigFormProviderTencentCloudECDN; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_EO: + return BizDeployNodeConfigFormProviderTencentCloudEO; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_GAAP: + return BizDeployNodeConfigFormProviderTencentCloudGAAP; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SCF: + return BizDeployNodeConfigFormProviderTencentCloudSCF; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL: + return BizDeployNodeConfigFormProviderTencentCloudSSL; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY: + return BizDeployNodeConfigFormProviderTencentCloudSSLDeploy; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_SSL_UPDATE: + return BizDeployNodeConfigFormProviderTencentCloudSSLUpdate; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_VOD: + return BizDeployNodeConfigFormProviderTencentCloudVOD; + case DEPLOYMENT_PROVIDERS.TENCENTCLOUD_WAF: + return BizDeployNodeConfigFormProviderTencentCloudWAF; + case DEPLOYMENT_PROVIDERS.UCLOUD_UCDN: + return BizDeployNodeConfigFormProviderUCloudUCDN; + case DEPLOYMENT_PROVIDERS.UCLOUD_US3: + return BizDeployNodeConfigFormProviderUCloudUS3; + case DEPLOYMENT_PROVIDERS.UNICLOUD_WEBHOST: + return BizDeployNodeConfigFormProviderUniCloudWebHost; + case DEPLOYMENT_PROVIDERS.UPYUN_CDN: + return BizDeployNodeConfigFormProviderUpyunCDN; + case DEPLOYMENT_PROVIDERS.UPYUN_FILE: + return BizDeployNodeConfigFormProviderUpyunFile; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_ALB: + return BizDeployNodeConfigFormProviderVolcEngineALB; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_CDN: + return BizDeployNodeConfigFormProviderVolcEngineCDN; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_CERTCENTER: + return BizDeployNodeConfigFormProviderVolcEngineCertCenter; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_CLB: + return BizDeployNodeConfigFormProviderVolcEngineCLB; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_DCDN: + return BizDeployNodeConfigFormProviderVolcEngineDCDN; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_IMAGEX: + return BizDeployNodeConfigFormProviderVolcEngineImageX; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_LIVE: + return BizDeployNodeConfigFormProviderVolcEngineLive; + case DEPLOYMENT_PROVIDERS.VOLCENGINE_TOS: + return BizDeployNodeConfigFormProviderVolcEngineTOS; + case DEPLOYMENT_PROVIDERS.WANGSU_CDN: + return BizDeployNodeConfigFormProviderWangsuCDN; + case DEPLOYMENT_PROVIDERS.WANGSU_CDNPRO: + return BizDeployNodeConfigFormProviderWangsuCDNPro; + case DEPLOYMENT_PROVIDERS.WANGSU_CERTIFICATE: + return BizDeployNodeConfigFormProviderWangsuCertificate; + case DEPLOYMENT_PROVIDERS.WEBHOOK: + return BizDeployNodeConfigFormProviderWebhook; + } + }, [fieldProvider]); + + const handleProviderPick = (value: string) => { + formInst.setFieldValue("provider", value); + console.log(); + }; + + const handleProviderSelect = (value?: string | undefined) => { + // 切换部署目标时重置表单,避免其他部署目标的配置字段影响当前部署目标 + if (initialValues?.provider === value) { + formInst.resetFields(); + } else { + const oldValues = formInst.getFieldsValue(); + const newValues: Record = {}; + for (const key in oldValues) { + if (key === "provider" || key === "providerAccessId" || key === "certificate" || key === "skipOnLastSucceeded") { + newValues[key] = oldValues[key]; + } else { + delete newValues[key]; + } + } + formInst.setFieldsValue(newValues); + + if (deploymentProvidersMap.get(fieldProvider)?.provider !== deploymentProvidersMap.get(value!)?.provider) { + formInst.setFieldValue("providerAccessId", void 0); + } + } + }; + + return ( + +
+ + + + +
+
+ + + + + + + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_WEBSITE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + nodeName: z.string().nullish(), + resourceType: z.literal([RESOURCE_TYPE_WEBSITE, RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.1panel_site_resource_type.placeholder")), + websiteId: z.union([z.string(), z.number()]).nullish(), + certificateId: z.union([z.string(), z.number()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_WEBSITE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.websiteId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.1panel_site_website_id.placeholder"), + path: ["websiteId"], + }); + } + } + break; + + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.1panel_site_certificate_id.placeholder"), + path: ["websiteId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProvider1PanelSite, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAPISIX.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAPISIX.tsx new file mode 100644 index 000000000..c3c13af2b --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAPISIX.tsx @@ -0,0 +1,91 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderAPISIX = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.apisix_resource_type.placeholder")), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + if (!values.certificateId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.apisix_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAPISIX, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSACM.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSACM.tsx new file mode 100644 index 000000000..9d4d873f5 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSACM.tsx @@ -0,0 +1,63 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAWSACM = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder")), + certificateArn: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAWSACM, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSCloudFront.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSCloudFront.tsx new file mode 100644 index 000000000..dc811ae16 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSCloudFront.tsx @@ -0,0 +1,80 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAWSCloudFront = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + certificateSource: "ACM", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aws_cloudfront_region.placeholder")), + distributionId: z.string().nonempty(t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder")), + certificateSource: z.string().nonempty(t("workflow_node.deploy.form.aws_cloudfront_certificate_source.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAWSCloudFront, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSIAM.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSIAM.tsx new file mode 100644 index 000000000..a6c563c38 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAWSIAM.tsx @@ -0,0 +1,70 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAWSIAM = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + certificatePath: "/", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aws_iam_region.placeholder")), + certificatePath: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return v.startsWith("/") && v.endsWith("/"); + }, t("workflow_node.deploy.form.aws_iam_certificate_path.errmsg.invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAWSIAM, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunALB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunALB.tsx new file mode 100644 index 000000000..8dbaa5792 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunALB.tsx @@ -0,0 +1,153 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderAliyunALB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_alb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.aliyun_alb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + const res = z.string().nonempty().safeParse(values.loadbalancerId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_alb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + const res = z.string().nonempty().safeParse(values.listenerId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_alb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunALB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunAPIGW.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunAPIGW.tsx new file mode 100644 index 000000000..d087d3043 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunAPIGW.tsx @@ -0,0 +1,146 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const SERVICE_TYPE_CLOUDNATIVE = "cloudnative" as const; +const SERVICE_TYPE_TRADITIONAL = "traditional" as const; + +const BizDeployNodeConfigFormProviderAliyunAPIGW = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldServiceType = Form.useWatch([parentNamePath, "serviceType"], formInst); + + return ( + <> + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + serviceType: z.literal([SERVICE_TYPE_CLOUDNATIVE, SERVICE_TYPE_TRADITIONAL], t("workflow_node.deploy.form.aliyun_apigw_service_type.placeholder")), + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_apigw_region.placeholder")), + gatewayId: z.string().nullish(), + groupId: z.string().nullish(), + domain: z + .string() + .nonempty(t("workflow_node.deploy.form.aliyun_apigw_domain.placeholder")) + .refine((v) => validDomainName(v!, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.serviceType) { + case SERVICE_TYPE_CLOUDNATIVE: + { + if (!values.gatewayId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_apigw_gateway_id.placeholder"), + path: ["gatewayId"], + }); + } + } + break; + + case SERVICE_TYPE_TRADITIONAL: + { + if (!values.groupId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_apigw_group_id.placeholder"), + path: ["groupId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunAPIGW, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCAS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCAS.tsx new file mode 100644 index 000000000..02e8ddda6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCAS.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunCAS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_cas_region.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunCAS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCASDeploy.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCASDeploy.tsx new file mode 100644 index 000000000..2849cc0d5 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCASDeploy.tsx @@ -0,0 +1,108 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; + +import { useFormNestedFieldsContext } from "./_context"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderAliyunCASDeploy = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceIds: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder")), + resourceIds: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid")), + contactIds: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunCASDeploy, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCDN.tsx new file mode 100644 index 000000000..10b9f30e4 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCLB.tsx new file mode 100644 index 000000000..a0c2d2e9f --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunCLB.tsx @@ -0,0 +1,139 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName, validPortNumber } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderAliyunCLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + loadbalancerId: "", + listenerPort: 443, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_clb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.aliyun_clb_resource_type.placeholder")), + loadbalancerId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder")), + listenerPort: z.preprocess((v) => (v == null || v === "" ? undefined : Number(v)), z.number().nullish()), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!validPortNumber(values.listenerPort!)) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_clb_listener_port.placeholder"), + path: ["listenerPort"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunCLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDCDN.tsx new file mode 100644 index 000000000..bfda3a0f1 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunDCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunDCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDDoS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDDoS.tsx new file mode 100644 index 000000000..c883469af --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunDDoS.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunDDoS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_ddos_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunDDoS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunESA.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunESA.tsx new file mode 100644 index 000000000..0abfa1cd1 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunESA.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunESA = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + siteId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_esa_region.placeholder")), + siteId: z.union([z.string(), z.number().int()]).refine((v) => { + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.aliyun_esa_site_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunESA, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunFC.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunFC.tsx new file mode 100644 index 000000000..3b0a988eb --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunFC.tsx @@ -0,0 +1,84 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunFC = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + serviceVersion: "3.0", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_fc_region.placeholder")), + serviceVersion: z.literal(["2.0", "3.0"], t("workflow_node.deploy.form.aliyun_fc_service_version.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunFC, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunGA.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunGA.tsx new file mode 100644 index 000000000..0871b6ad6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunGA.tsx @@ -0,0 +1,126 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_ACCELERATOR = "accelerator" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderAliyunGA = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_LISTENER, + acceleratorId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal([RESOURCE_TYPE_ACCELERATOR, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.aliyun_ga_resource_type.placeholder")), + acceleratorId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_ga_accelerator_id.placeholder")), + listenerId: z.string().nullish(), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_ga_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunGA, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunLive.tsx new file mode 100644 index 000000000..be87144e5 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunLive.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunLive = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_live_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunLive, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunNLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunNLB.tsx new file mode 100644 index 000000000..b4c489701 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunNLB.tsx @@ -0,0 +1,132 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderAliyunNLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_nlb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + if (!values.loadbalancerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.aliyun_nlb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunNLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunOSS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunOSS.tsx new file mode 100644 index 000000000..1a65f7b28 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunOSS.tsx @@ -0,0 +1,78 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunOSS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + bucket: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_oss_region.placeholder")), + bucket: z.string().nonempty(t("workflow_node.deploy.form.aliyun_oss_bucket.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunOSS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunVOD.tsx new file mode 100644 index 000000000..852e6ad27 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunVOD.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunVOD = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_vod_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunVOD, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunWAF.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunWAF.tsx new file mode 100644 index 000000000..2df02aa8c --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAliyunWAF.tsx @@ -0,0 +1,97 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAliyunWAF = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + serviceVersion: "3.0", + instanceId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_region.placeholder")), + serviceVersion: z.literal("3.0", t("workflow_node.deploy.form.aliyun_waf_service_version.placeholder")), + instanceId: z.string().nonempty(t("workflow_node.deploy.form.aliyun_waf_instance_id.placeholder")), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAliyunWAF, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAzureKeyVault.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAzureKeyVault.tsx new file mode 100644 index 000000000..95ca108dc --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderAzureKeyVault.tsx @@ -0,0 +1,69 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderAzureKeyVault = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + keyvaultName: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + keyvaultName: z.string().nonempty(t("workflow_node.deploy.form.azure_keyvault_name.placeholder")), + certificateName: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return /^[a-zA-Z0-9-]{1,127}$/.test(v); + }, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderAzureKeyVault, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudAppBLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudAppBLB.tsx new file mode 100644 index 000000000..03d487e7a --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudAppBLB.tsx @@ -0,0 +1,139 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName, validPortNumber } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderBaiduCloudAppBLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + loadbalancerId: "", + listenerPort: 443, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.baiducloud_appblb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.baiducloud_appblb_resource_type.placeholder")), + loadbalancerId: z.string().nonempty(t("workflow_node.deploy.form.baiducloud_appblb_loadbalancer_id.placeholder")), + listenerPort: z.preprocess((v) => (v == null || v === "" ? undefined : Number(v)), z.number().nullish()), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!validPortNumber(values.listenerPort!)) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.baiducloud_appblb_listener_port.placeholder"), + path: ["listenerPort"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaiduCloudAppBLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudBLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudBLB.tsx new file mode 100644 index 000000000..fd60509b2 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudBLB.tsx @@ -0,0 +1,139 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName, validPortNumber } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderBaiduCloudBLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + loadbalancerId: "", + listenerPort: 443, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.baiducloud_blb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.baiducloud_blb_resource_type.placeholder")), + loadbalancerId: z.string().nonempty(t("workflow_node.deploy.form.baiducloud_blb_loadbalancer_id.placeholder")), + listenerPort: z.preprocess((v) => (v == null || v === "" ? undefined : Number(v)), z.number().nullish()), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!validPortNumber(values.listenerPort!)) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.baiducloud_blb_listener_port.placeholder"), + path: ["listenerPort"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaiduCloudBLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudCDN.tsx new file mode 100644 index 000000000..305b9b5d4 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaiduCloudCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBaiduCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaiduCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaishanCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaishanCDN.tsx new file mode 100644 index 000000000..01102bc04 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaishanCDN.tsx @@ -0,0 +1,71 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBaishanCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (!v) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaishanCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelConsole.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelConsole.tsx new file mode 100644 index 000000000..97f2f5265 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelConsole.tsx @@ -0,0 +1,51 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBaotaPanelConsole = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + autoRestart: true, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + autoRestart: z.boolean().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaotaPanelConsole, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelSite.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelSite.tsx new file mode 100644 index 000000000..4b6775209 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaPanelSite.tsx @@ -0,0 +1,129 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const SITE_TYPE_PHP = "php"; +const SITE_TYPE_OTHER = "other"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderBaotaPanelSite = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldSiteType = Form.useWatch([parentNamePath, "siteType"], formInst); + + return ( + <> + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + siteType: SITE_TYPE_OTHER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + siteType: z.literal([SITE_TYPE_PHP, SITE_TYPE_OTHER], t("workflow_node.deploy.form.baotapanel_site_type.placeholder")), + siteName: z.string().nullish(), + siteNames: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.siteType) { + case SITE_TYPE_PHP: + { + if (!values.siteName?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.baotapanel_site_name.placeholder"), + path: ["siteName"], + }); + } + } + break; + + case SITE_TYPE_OTHER: + { + if (!values.siteNames?.trim() || !values.siteNames.split(MULTIPLE_INPUT_SEPARATOR).every((e) => !!e.trim())) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.baotapanel_site_names.placeholder"), + path: ["siteNames"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaotaPanelSite, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaWAFSite.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaWAFSite.tsx new file mode 100644 index 000000000..b52d82219 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBaotaWAFSite.tsx @@ -0,0 +1,71 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, InputNumber } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validPortNumber } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBaotaWAFSite = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + siteName: "", + sitePort: 443, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.baotawaf_site_name.placeholder")), + sitePort: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("workflow_node.deploy.form.baotawaf_site_port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBaotaWAFSite, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBunnyCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBunnyCDN.tsx new file mode 100644 index 000000000..2e5ac8ed2 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBunnyCDN.tsx @@ -0,0 +1,72 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBunnyCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + pullZoneId: "", + hostname: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + pullZoneId: z.union([z.string(), z.number().int()]).refine((v) => { + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder")), + hostname: z + .string() + .nonempty(t("workflow_node.deploy.form.bunny_cdn_hostname.placeholder")) + .refine((v) => { + return validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBunnyCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBytePlusCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBytePlusCDN.tsx new file mode 100644 index 000000000..c81870afa --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderBytePlusCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderBytePlusCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderBytePlusCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudAO.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudAO.tsx new file mode 100644 index 000000000..375a2f9cd --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudAO.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderCTCCCloudAO = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCTCCCloudAO, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudCDN.tsx new file mode 100644 index 000000000..f68e927ac --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderCTCCCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCTCCCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudELB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudELB.tsx new file mode 100644 index 000000000..7e541d8db --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudELB.tsx @@ -0,0 +1,132 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderCTCCCloudELB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + regionId: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + regionId: z.string().nonempty(t("workflow_node.deploy.form.ctcccloud_elb_region_id.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.ctcccloud_elb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + if (!values.loadbalancerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ctcccloud_elb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ctcccloud_elb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCTCCCloudELB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudICDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudICDN.tsx new file mode 100644 index 000000000..bfc3a470c --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudICDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderCTCCCloudICDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCTCCCloudICDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudLVDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudLVDN.tsx new file mode 100644 index 000000000..34221b73f --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCTCCCloudLVDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderCTCCCloudLVDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCTCCCloudLVDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCdnfly.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCdnfly.tsx new file mode 100644 index 000000000..acf2d7b4c --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderCdnfly.tsx @@ -0,0 +1,122 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_SITE = "site" as const; +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderCdnfly = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_SITE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal([RESOURCE_TYPE_SITE, RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.cdnfly_resource_type.placeholder")), + siteId: z.union([z.string(), z.number().int()]).nullish(), + certificateId: z.union([z.string(), z.number().int()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_SITE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.siteId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.cdnfly_site_id.placeholder"), + path: ["siteId"], + }); + } + } + break; + + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.cdnfly_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderCdnfly, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderDogeCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderDogeCloudCDN.tsx new file mode 100644 index 000000000..35d840c2e --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderDogeCloudCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderDogeCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderDogeCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderEdgioApplications.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderEdgioApplications.tsx new file mode 100644 index 000000000..f98ab9282 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderEdgioApplications.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderEdgioApplications = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + environmentId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + environmentId: z.string().nonempty(t("workflow_node.deploy.form.edgio_applications_environment_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderEdgioApplications, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderFlexCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderFlexCDN.tsx new file mode 100644 index 000000000..eea2c9993 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderFlexCDN.tsx @@ -0,0 +1,92 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderFlexCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.flexcdn_resource_type.placeholder")), + certificateId: z.union([z.string(), z.number().int()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.flexcdn_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderFlexCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGcoreCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGcoreCDN.tsx new file mode 100644 index 000000000..406729874 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGcoreCDN.tsx @@ -0,0 +1,71 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderGcoreCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + resourceId: z.union([z.string(), z.number().int()]).refine((v) => { + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.gcore_cdn_resource_id.placeholder")), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (!v) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderGcoreCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGoEdge.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGoEdge.tsx new file mode 100644 index 000000000..16c99e6f8 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderGoEdge.tsx @@ -0,0 +1,92 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderGoEdge = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.goedge_resource_type.placeholder")), + certificateId: z.union([z.string(), z.number().int()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.goedge_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderGoEdge, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudCDN.tsx new file mode 100644 index 000000000..a3cc76177 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudCDN.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderHuaweiCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.huaweicloud_cdn_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderHuaweiCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudELB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudELB.tsx new file mode 100644 index 000000000..d58e22184 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudELB.tsx @@ -0,0 +1,164 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderHuaweiCloudELB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.huaweicloud_elb_region.placeholder")), + resourceType: z.literal( + [RESOURCE_TYPE_CERTIFICATE, RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], + t("workflow_node.deploy.form.huaweicloud_elb_resource_type.placeholder") + ), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + if (!values.loadbalancerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.huaweicloud_elb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.huaweicloud_elb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + + case RESOURCE_TYPE_CERTIFICATE: + { + if (!values.certificateId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.huaweicloud_elb_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderHuaweiCloudELB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudWAF.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudWAF.tsx new file mode 100644 index 000000000..59a4d32ff --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderHuaweiCloudWAF.tsx @@ -0,0 +1,140 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; +const RESOURCE_TYPE_CLOUDSERVER = "cloudserver" as const; +const RESOURCE_TYPE_PREMIUMHOST = "premiumhost" as const; + +const BizDeployNodeConfigFormProviderHuaweiCloudWAF = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.huaweicloud_waf_region.placeholder")), + resourceType: z.literal( + [RESOURCE_TYPE_CERTIFICATE, RESOURCE_TYPE_CLOUDSERVER, RESOURCE_TYPE_PREMIUMHOST], + t("workflow_node.deploy.form.huaweicloud_waf_resource_type.placeholder") + ), + certificateId: z.string().nullish(), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + if (!values.certificateId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.huaweicloud_waf_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + + case RESOURCE_TYPE_CLOUDSERVER: + case RESOURCE_TYPE_PREMIUMHOST: + { + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.huaweicloud_waf_domain.placeholder"), + path: ["domain"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderHuaweiCloudWAF, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudALB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudALB.tsx new file mode 100644 index 000000000..e4fd9062c --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudALB.tsx @@ -0,0 +1,151 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderJDCloudALB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + regionId: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + regionId: z.string().nonempty(t("workflow_node.deploy.form.jdcloud_alb_region_id.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.jdcloud_alb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + if (!values.loadbalancerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.jdcloud_alb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.jdcloud_alb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderJDCloudALB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudCDN.tsx new file mode 100644 index 000000000..d517064c6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderJDCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderJDCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudLive.tsx new file mode 100644 index 000000000..aaf64461a --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudLive.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderJDCloudLive = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderJDCloudLive, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudVOD.tsx new file mode 100644 index 000000000..d916cbcb9 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderJDCloudVOD.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderJDCloudVOD = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderJDCloudVOD, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKong.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKong.tsx new file mode 100644 index 000000000..36f38ef28 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKong.tsx @@ -0,0 +1,102 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderKong = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + } + > + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.kong_resource_type.placeholder")), + workspace: z.string().nullish(), + certificateId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + if (!values.certificateId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.kong_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderKong, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKubernetesSecret.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKubernetesSecret.tsx new file mode 100644 index 000000000..b49c23ea6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderKubernetesSecret.tsx @@ -0,0 +1,99 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderKubernetesSecret = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + namespace: "default", + secretType: "kubernetes.io/tls", + secretDataKeyForCrt: "tls.crt", + secretDataKeyForKey: "tls.key", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + namespace: z.string().nonempty(t("workflow_node.deploy.form.k8s_namespace.placeholder")), + secretName: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_name.placeholder")), + secretType: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_type.placeholder")), + secretDataKeyForCrt: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder")), + secretDataKeyForKey: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderKubernetesSecret, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLeCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLeCDN.tsx new file mode 100644 index 000000000..da92ba8dc --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLeCDN.tsx @@ -0,0 +1,103 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderLeCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.lecdn_resource_type.placeholder")), + certificateId: z.union([z.string(), z.number().int()]).nullish(), + clientId: z.union([z.string(), z.number().int()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.lecdn_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderLeCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLocal.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLocal.tsx new file mode 100644 index 000000000..5c68abee4 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderLocal.tsx @@ -0,0 +1,537 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { IconChevronDown } from "@tabler/icons-react"; +import { Alert, Button, Dropdown, Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import CodeInput from "@/components/CodeInput"; +import Show from "@/components/Show"; +import { CERTIFICATE_FORMATS } from "@/domain/certificate"; + +import { useFormNestedFieldsContext } from "./_context"; + +const FORMAT_PEM = CERTIFICATE_FORMATS.PEM; +const FORMAT_PFX = CERTIFICATE_FORMATS.PFX; +const FORMAT_JKS = CERTIFICATE_FORMATS.JKS; + +const SHELLENV_SH = "sh" as const; +const SHELLENV_CMD = "cmd" as const; +const SHELLENV_POWERSHELL = "powershell" as const; + +export const initPresetScript = ( + key: "sh_backup_files" | "ps_backup_files" | "sh_reload_nginx" | "ps_binding_iis" | "ps_binding_netsh" | "ps_binding_rdp", + params?: { + certPath?: string; + certPathForServerOnly?: string; + certPathForIntermediaOnly?: string; + keyPath?: string; + pfxPassword?: string; + jksAlias?: string; + jksKeypass?: string; + jksStorepass?: string; + } +) => { + switch (key) { + case "sh_backup_files": + return `# 请将以下路径替换为实际值 +cp "${params?.certPath || ""}" "${params?.certPath || ""}.bak" 2>/dev/null || : +cp "${params?.keyPath || ""}" "${params?.keyPath || ""}.bak" 2>/dev/null || : + `.trim(); + + case "ps_backup_files": + return `# 请将以下路径替换为实际值 +if (Test-Path -Path "${params?.certPath || ""}" -PathType Leaf) { + Copy-Item -Path "${params?.certPath || ""}" -Destination "${params?.certPath || ""}.bak" -Force +} +if (Test-Path -Path "${params?.keyPath || ""}" -PathType Leaf) { + Copy-Item -Path "${params?.keyPath || ""}" -Destination "${params?.keyPath || ""}.bak" -Force +} + `.trim(); + + case "sh_reload_nginx": + return `# *** 需要 root 权限 *** + +sudo service nginx reload + `.trim(); + + case "ps_binding_iis": + return `# *** 需要管理员权限 *** + +# 请将以下变量替换为实际值 +$pfxPath = "${params?.certPath || ""}" # PFX 文件路径(与表单中保持一致) +$pfxPassword = "${params?.pfxPassword || ""}" # PFX 密码(与表单中保持一致) +$siteName = "" # IIS 网站名称 +$domain = "" # 域名 +$ipaddr = "" # 绑定 IP,“*”表示所有 IP 绑定 +$port = "" # 绑定端口 + +# 导入证书到本地计算机的个人存储区 +$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable +# 获取 Thumbprint +$thumbprint = $cert.Thumbprint +# 导入 WebAdministration 模块 +Import-Module WebAdministration +# 检查是否已存在 HTTPS 绑定 +$existingBinding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -HostHeader "$domain" -ErrorAction SilentlyContinue +if (!$existingBinding) { + # 添加新的 HTTPS 绑定 + New-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain" +} +# 获取绑定对象 +$binding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain" +# 绑定 SSL 证书 +$binding.AddSslCertificate($thumbprint, "My") +# 删除目录下的证书文件 +Remove-Item -Path "$pfxPath" -Force + `.trim(); + + case "ps_binding_netsh": + return `# *** 需要管理员权限 *** + +# 请将以下变量替换为实际值 +$pfxPath = "${params?.certPath || ""}" # PFX 文件路径(与表单中保持一致) +$pfxPassword = "${params?.pfxPassword || ""}" # PFX 密码(与表单中保持一致) +$ipaddr = "" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名 +$port = "" # 绑定端口 + +# 导入证书到本地计算机的个人存储区 +$addr = $ipaddr + ":" + $port +$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable +# 获取 Thumbprint +$thumbprint = $cert.Thumbprint +# 检测端口是否绑定证书,如绑定则删除绑定 +$isExist = netsh http show sslcert ipport=$addr +if ($isExist -like "*$addr*"){ netsh http delete sslcert ipport=$addr } +# 绑定到端口 +netsh http add sslcert ipport=$addr certhash=$thumbprint +# 删除目录下的证书文件 +Remove-Item -Path "$pfxPath" -Force + `.trim(); + + case "ps_binding_rdp": + return `# *** 需要管理员权限 *** + +# 请将以下变量替换为实际值 +$pfxPath = "${params?.certPath || ""}" # PFX 文件路径(与表单中保持一致) +$pfxPassword = "${params?.pfxPassword || ""}" # PFX 密码(与表单中保持一致) + +# 导入证书到本地计算机的个人存储区 +$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable +# 获取 Thumbprint +$thumbprint = $cert.Thumbprint +# 绑定到 RDP +$rdpCertPath = "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp" +Set-ItemProperty -Path $rdpCertPath -Name "SSLCertificateSHA1Hash" -Value "$thumbprint" + `.trim(); + } +}; + +const BizDeployNodeConfigFormProviderLocal = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldFormat = Form.useWatch([parentNamePath, "format"], formInst); + const fieldCertPath = Form.useWatch([parentNamePath, "certPath"], formInst); + + const handleFormatSelect = (value: string) => { + if (fieldFormat === value) return; + + switch (value) { + case FORMAT_PEM: + { + if (/(.pfx|.jks)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.pfx|.jks)$/, ".crt")); + } + } + break; + + case FORMAT_PFX: + { + if (/(.crt|.jks)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.jks)$/, ".pfx")); + } + } + break; + + case FORMAT_JKS: + { + if (/(.crt|.pfx)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.pfx)$/, ".jks")); + } + } + break; + } + }; + + const handlePresetPreScriptClick = (key: string) => { + switch (key) { + case "sh_backup_files": + case "ps_backup_files": + { + const presetScriptParams = { + certPath: formInst.getFieldValue("certPath"), + keyPath: formInst.getFieldValue("keyPath"), + }; + formInst.setFieldValue("shellEnv", SHELLENV_SH); + formInst.setFieldValue("preCommand", initPresetScript(key, presetScriptParams)); + } + break; + } + }; + + const handlePresetPostScriptClick = (key: string) => { + switch (key) { + case "sh_reload_nginx": + { + formInst.setFieldValue("shellEnv", SHELLENV_SH); + formInst.setFieldValue("postCommand", initPresetScript(key)); + } + break; + + case "ps_binding_iis": + case "ps_binding_netsh": + case "ps_binding_rdp": + { + const presetScriptParams = { + certPath: formInst.getFieldValue("certPath"), + pfxPassword: formInst.getFieldValue("pfxPassword"), + }; + formInst.setFieldValue("shellEnv", SHELLENV_POWERSHELL); + formInst.setFieldValue("postCommand", initPresetScript(key, presetScriptParams)); + } + break; + } + }; + + return ( + <> + + } /> + + + + + + + } + > + + + + + } + > + + + + } + > + + + + } + > + + + + + + } + > + + + + + + } + > + + + + } + > + + + + } + > + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + format: FORMAT_PEM, + certPath: "/etc/ssl/certimate/cert.crt", + keyPath: "/etc/ssl/certimate/cert.key", + shellEnv: SHELLENV_SH, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + format: z.literal([FORMAT_PEM, FORMAT_PFX, FORMAT_JKS], t("workflow_node.deploy.form.local_format.placeholder")), + certPath: z + .string() + .min(1, t("workflow_node.deploy.form.local_cert_path.tooltip")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + certPathForServerOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + certPathForIntermediaOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + keyPath: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + pfxPassword: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksAlias: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksKeypass: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksStorepass: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + shellEnv: z.literal([SHELLENV_SH, SHELLENV_CMD, SHELLENV_POWERSHELL], t("workflow_node.deploy.form.local_shell_env.placeholder")), + preCommand: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + postCommand: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + }) + .superRefine((values, ctx) => { + switch (values.format) { + case FORMAT_PEM: + { + if (!values.keyPath?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_key_path.tooltip"), + path: ["keyPath"], + }); + } + } + break; + + case FORMAT_PFX: + { + if (!values.pfxPassword?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_pfx_password.tooltip"), + path: ["pfxPassword"], + }); + } + } + break; + + case FORMAT_JKS: + { + if (!values.jksAlias?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_alias.tooltip"), + path: ["jksAlias"], + }); + } + + if (!values.jksKeypass?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_keypass.tooltip"), + path: ["jksKeypass"], + }); + } + + if (!values.jksStorepass?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_storepass.tooltip"), + path: ["jksStorepass"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderLocal, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderNetlifySite.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderNetlifySite.tsx new file mode 100644 index 000000000..a8afec0da --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderNetlifySite.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderNetlifySite = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + siteId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + siteId: z.string().nonempty(t("workflow_node.deploy.form.netlify_site_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderNetlifySite, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderProxmoxVE.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderProxmoxVE.tsx new file mode 100644 index 000000000..2c51e8ab1 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderProxmoxVE.tsx @@ -0,0 +1,62 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderProxmoxVE = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + nodeName: "", + autoRestart: true, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + nodeName: z.string().nonempty(t("workflow_node.deploy.form.proxmoxve_node_name.placeholder")), + autoRestart: z.boolean().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderProxmoxVE, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuCDN.tsx new file mode 100644 index 000000000..f017bd730 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderQiniuCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderQiniuCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuKodo.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuKodo.tsx new file mode 100644 index 000000000..0ddb553bb --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuKodo.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderQiniuKodo = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderQiniuKodo, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuPili.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuPili.tsx new file mode 100644 index 000000000..634e5c984 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderQiniuPili.tsx @@ -0,0 +1,66 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderQiniuPili = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + hub: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + hub: z.string().nonempty(t("workflow_node.deploy.form.qiniu_pili_hub.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderQiniuPili, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRainYunRCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRainYunRCDN.tsx new file mode 100644 index 000000000..a4296f8ea --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRainYunRCDN.tsx @@ -0,0 +1,68 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderRainYunRCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + instanceId: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + instanceId: z.union([z.string(), z.number().int()]).refine((v) => { + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.rainyun_rcdn_instance_id.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderRainYunRCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRatPanelSite.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRatPanelSite.tsx new file mode 100644 index 000000000..68690f7c3 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderRatPanelSite.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderRatPanelSite = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + siteName: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + siteName: z.string().nonempty(t("workflow_node.deploy.form.ratpanel_site_name.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderRatPanelSite, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSSH.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSSH.tsx new file mode 100644 index 000000000..ae22ac1e6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSSH.tsx @@ -0,0 +1,576 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { IconChevronDown } from "@tabler/icons-react"; +import { Button, Dropdown, Form, Input, Select, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import CodeInput from "@/components/CodeInput"; +import Show from "@/components/Show"; +import { CERTIFICATE_FORMATS } from "@/domain/certificate"; + +import { useFormNestedFieldsContext } from "./_context"; +import { initPresetScript as _initPresetScript } from "./BizDeployNodeConfigFormProviderLocal"; + +const FORMAT_PEM = CERTIFICATE_FORMATS.PEM; +const FORMAT_PFX = CERTIFICATE_FORMATS.PFX; +const FORMAT_JKS = CERTIFICATE_FORMATS.JKS; + +const initPresetScript = ( + key: Parameters[0] | "sh_replace_synologydsm_ssl" | "sh_replace_fnos_ssl" | "sh_replace_qnap_ssl", + params?: Parameters[1] +) => { + switch (key) { + case "sh_replace_synologydsm_ssl": + return `# *** 需要 root 权限 *** +# 注意仅支持替换证书,需本身已开启过一次 HTTPS +# 脚本参考 https://github.com/catchdave/ssl-certs/blob/main/replace_synology_ssl_certs.sh + +# 请将以下变量替换为实际值 +$tmpFullchainPath = "${params?.certPath || ""}" # 证书文件路径(与表单中保持一致) +$tmpCertPath = "${params?.certPathForServerOnly || ""}" # 服务器证书文件路径(与表单中保持一致) +$tmpKeyPath = "${params?.keyPath || ""}" # 私钥文件路径(与表单中保持一致) + +DEBUG=1 +error_exit() { echo "[ERROR] $1"; exit 1; } +warn() { echo "[WARN] $1"; } +info() { echo "[INFO] $1"; } +debug() { [[ "\${DEBUG}" ]] && echo "[DEBUG] $1"; } + +certs_src_dir="/usr/syno/etc/certificate/system/default" +target_cert_dirs=( + "/usr/syno/etc/certificate/system/FQDN" + "/usr/local/etc/certificate/ScsiTarget/pkg-scsi-plugin-server/" + "/usr/local/etc/certificate/SynologyDrive/SynologyDrive/" + "/usr/local/etc/certificate/WebDAVServer/webdav/" + "/usr/local/etc/certificate/ActiveBackup/ActiveBackup/" + "/usr/syno/etc/certificate/smbftpd/ftpd/") + +# 获取证书目录 +default_dir_name=$(/dev/null && /usr/syno/bin/synopkg restart ScsiTarget +/usr/syno/bin/synopkg is_onoff SynologyDrive 1>/dev/null && /usr/syno/bin/synopkg restart SynologyDrive +/usr/syno/bin/synopkg is_onoff WebDAVServer 1>/dev/null && /usr/syno/bin/synopkg restart WebDAVServer +/usr/syno/bin/synopkg is_onoff ActiveBackup 1>/dev/null && /usr/syno/bin/synopkg restart ActiveBackup +if ! /usr/syno/bin/synow3tool --gen-all && sudo /usr/syno/bin/synosystemctl restart nginx; then + warn "nginx failed to restart" +fi + +info "Completed" + `.trim(); + + case "sh_replace_fnos_ssl": + return `# *** 需要 root 权限 *** +# 注意仅支持替换证书,需本身已开启过一次 HTTPS +# 脚本参考 https://github.com/lfgyx/fnos_certificate_update/blob/main/src/update_cert.sh + +# 请将以下变量替换为实际值 +# 飞牛证书实际存放路径请在 \`/usr/trim/etc/network_cert_all.conf\` 中查看,注意不要修改文件名 +$tmpFullchainPath = "${params?.certPath || ""}" # 证书文件路径(与表单中保持一致) +$tmpCertPath = "${params?.certPathForServerOnly || ""}" # 服务器证书文件路径(与表单中保持一致) +$tmpKeyPath = "${params?.keyPath || ""}" # 私钥文件路径(与表单中保持一致) +$fnFullchainPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/fullchain.crt" # 飞牛证书文件路径 +$fnCertPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/example.com.crt" # 飞牛服务器证书文件路径 +$fnKeyPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/example.com.key" # 飞牛私钥文件路径 +$domain = "" # 域名 + +# 复制文件 +cp -rf "$tmpFullchainPath" "$fnFullchainPath" +cp -rf "$tmpCertPath" "$fnCertPath" +cp -rf "$tmpKeyPath" "$fnKeyPath" +chmod 755 "$fnFullchainPath" +chmod 755 "$fnCertPath" +chmod 755 "$fnKeyPath" + +# 更新数据库 +NEW_EFFECT_DATE=$(openssl x509 -startdate -noout -in "$fnCertPath" | sed "s/^.*=\\(.*\\)$/\\1/") +NEW_EFFECT_TIMESTAMP=$(date -d "$NEW_EFFECT_DATE" +%s%3N) +NEW_EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$fnCertPath" | sed "s/^.*=\\(.*\\)$/\\1/") +NEW_EXPIRY_TIMESTAMP=$(date -d "$NEW_EXPIRY_DATE" +%s%3N) +psql -U postgres -d trim_connect -c "UPDATE cert SET valid_from=$NEW_EFFECT_TIMESTAMP, valid_to=$NEW_EXPIRY_TIMESTAMP WHERE domain='$domain'" + +# 重启服务 +systemctl restart webdav.service +systemctl restart smbftpd.service +systemctl restart trim_nginx.service + `.trim(); + + case "sh_replace_qnap_ssl": + return `# *** 需要 root 权限 *** +# 注意仅支持替换证书,需本身已开启过一次 HTTPS + +# 请将以下变量替换为实际值 +$tmpFullchainPath = "${params?.certPath || ""}" # 证书文件路径(与表单中保持一致) +$tmpKeyPath = "${params?.keyPath || ""}" # 私钥文件路径(与表单中保持一致) + +# 复制文件 +cp -rf "$tmpFullchainPath" /etc/stunnel/backup.cert +cp -rf "$tmpKeyPath" /etc/stunnel/backup.key +cat /etc/stunnel/backup.key > /etc/stunnel/stunnel.pem +cat /etc/stunnel/backup.cert >> /etc/stunnel/stunnel.pem +chmod 600 /etc/stunnel/backup.cert +chmod 600 /etc/stunnel/backup.key +chmod 600 /etc/stunnel/stunnel.pem + +# 重启服务 +/etc/init.d/stunnel.sh restart +/etc/init.d/reverse_proxy.sh reload + `.trim(); + } + + return _initPresetScript(key as Parameters[0], params); +}; + +const BizDeployNodeConfigFormProviderSSH = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldFormat = Form.useWatch([parentNamePath, "format"], formInst); + const fieldCertPath = Form.useWatch([parentNamePath, "certPath"], formInst); + + const handleFormatSelect = (value: string) => { + if (fieldFormat === value) return; + + switch (value) { + case FORMAT_PEM: + { + if (/(.pfx|.jks)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.pfx|.jks)$/, ".crt")); + } + } + break; + + case FORMAT_PFX: + { + if (/(.crt|.jks)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.jks)$/, ".pfx")); + } + } + break; + + case FORMAT_JKS: + { + if (/(.crt|.pfx)$/.test(fieldCertPath)) { + formInst.setFieldValue("certPath", fieldCertPath.replace(/(.crt|.pfx)$/, ".jks")); + } + } + break; + } + }; + + const handlePresetPreScriptClick = (key: string) => { + switch (key) { + case "sh_backup_files": + case "ps_backup_files": + { + const presetScriptParams = { + certPath: formInst.getFieldValue("certPath"), + keyPath: formInst.getFieldValue("keyPath"), + }; + formInst.setFieldValue("preCommand", initPresetScript(key, presetScriptParams)); + } + break; + } + }; + + const handlePresetPostScriptClick = (key: string) => { + switch (key) { + case "sh_reload_nginx": + { + formInst.setFieldValue("postCommand", initPresetScript(key)); + } + break; + + case "sh_replace_synologydsm_ssl": + case "sh_replace_fnos_ssl": + case "sh_replace_qnap_ssl": + { + const presetScriptParams = { + certPath: formInst.getFieldValue("certPath"), + certPathForServerOnly: formInst.getFieldValue("certPathForServerOnly"), + certPathForIntermediaOnly: formInst.getFieldValue("certPathForIntermediaOnly"), + keyPath: formInst.getFieldValue("keyPath"), + }; + formInst.setFieldValue("postCommand", initPresetScript(key, presetScriptParams)); + } + break; + + case "ps_binding_iis": + case "ps_binding_netsh": + case "ps_binding_rdp": + { + const presetScriptParams = { + certPath: formInst.getFieldValue("certPath"), + pfxPassword: formInst.getFieldValue("pfxPassword"), + }; + formInst.setFieldValue("postCommand", initPresetScript(key, presetScriptParams)); + } + break; + } + }; + + return ( + <> + + + + + } + > + + + + + } + > + + + + } + > + + + + } + > + + + + + + } + > + + + + + + } + > + + + + } + > + + + + } + > + + + + + + + + + + + + + + + + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + format: FORMAT_PEM, + certPath: "/etc/ssl/certimate/cert.crt", + keyPath: "/etc/ssl/certimate/cert.key", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + format: z.literal([FORMAT_PEM, FORMAT_PFX, FORMAT_JKS], t("workflow_node.deploy.form.ssh_format.placeholder")), + certPath: z + .string() + .min(1, t("workflow_node.deploy.form.ssh_cert_path.tooltip")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + keyPath: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + certPathForServerOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + certPathForIntermediaOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + pfxPassword: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksAlias: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksKeypass: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + jksStorepass: z + .string() + .max(64, t("common.errmsg.string_max", { max: 256 })) + .nullish(), + preCommand: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + postCommand: z + .string() + .max(20480, t("common.errmsg.string_max", { max: 20480 })) + .nullish(), + useSCP: z.boolean().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.format) { + case FORMAT_PEM: + { + if (!values.keyPath?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_key_path.tooltip"), + path: ["keyPath"], + }); + } + } + break; + + case FORMAT_PFX: + { + if (!values.pfxPassword?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_pfx_password.tooltip"), + path: ["pfxPassword"], + }); + } + } + break; + + case FORMAT_JKS: + { + if (!values.jksAlias?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_alias.tooltip"), + path: ["jksAlias"], + }); + } + + if (!values.jksKeypass?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_keypass.tooltip"), + path: ["jksKeypass"], + }); + } + + if (!values.jksStorepass?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.ssh_jks_storepass.tooltip"), + path: ["jksStorepass"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderSSH, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSafeLine.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSafeLine.tsx new file mode 100644 index 000000000..ea40626a7 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderSafeLine.tsx @@ -0,0 +1,92 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const BizDeployNodeConfigFormProviderSafeLine = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, t("workflow_node.deploy.form.safeline_resource_type.placeholder")), + certificateId: z.union([z.string(), z.number().int()]).nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_CERTIFICATE: + { + const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.certificateId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.safeline_certificate_id.placeholder"), + path: ["certificateId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderSafeLine, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCDN.tsx new file mode 100644 index 000000000..2ab258c62 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCDN.tsx @@ -0,0 +1,65 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCLB.tsx new file mode 100644 index 000000000..a6a6f4537 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCLB.tsx @@ -0,0 +1,203 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; +const RESOURCE_TYPE_RULEDOMAIN = "ruledomain" as const; +const RESOURCE_TYPE_VIA_SSLDEPLOY = "ssl-deploy" as const; + +const BizDeployNodeConfigFormProviderTencentCloudCLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + } + > + + + + + + + + } + > + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + loadbalancerId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + endpoint: z.string().nullish(), + resourceType: z.literal( + [RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER, RESOURCE_TYPE_RULEDOMAIN, RESOURCE_TYPE_VIA_SSLDEPLOY], + t("workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder") + ), + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_clb_region.placeholder")), + loadbalancerId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.placeholder")), + listenerId: z.string().nullish(), + domain: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + + case RESOURCE_TYPE_RULEDOMAIN: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + + if (!validDomainName(values.domain!, { allowWildcard: true })) { + ctx.addIssue({ + code: "custom", + message: t("common.errmsg.domain_invalid"), + path: ["domain"], + }); + } + } + break; + + case RESOURCE_TYPE_VIA_SSLDEPLOY: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudCLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCOS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCOS.tsx new file mode 100644 index 000000000..f011a7eb9 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCOS.tsx @@ -0,0 +1,78 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudCOS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + bucket: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_cos_region.placeholder")), + bucket: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudCOS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCSS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCSS.tsx new file mode 100644 index 000000000..4b9e4f013 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudCSS.tsx @@ -0,0 +1,65 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudCSS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudCSS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudECDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudECDN.tsx new file mode 100644 index 000000000..aeb4d8ead --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudECDN.tsx @@ -0,0 +1,65 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudECDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudECDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudEO.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudEO.tsx new file mode 100644 index 000000000..3d3fa845f --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudEO.tsx @@ -0,0 +1,90 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderTencentCloudEO = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + zoneId: "", + domains: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + zoneId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")), + domains: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + }, t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudEO, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudGAAP.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudGAAP.tsx new file mode 100644 index 000000000..343b956d9 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudGAAP.tsx @@ -0,0 +1,113 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderTencentCloudGAAP = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + } + > + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + endpoint: z.string().nullish(), + resourceType: z.literal(RESOURCE_TYPE_LISTENER, t("workflow_node.deploy.form.tencentcloud_gaap_resource_type.placeholder")), + proxyId: z.string().nullish(), + listenerId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.tencentcloud_gaap_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudGAAP, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSCF.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSCF.tsx new file mode 100644 index 000000000..f7f2e54b6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSCF.tsx @@ -0,0 +1,77 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudSCF = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_scf_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudSCF, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSL.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSL.tsx new file mode 100644 index 000000000..33211d3be --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSL.tsx @@ -0,0 +1,50 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudSSL = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudSSL, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLDeploy.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLDeploy.tsx new file mode 100644 index 000000000..151fea4bf --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLDeploy.tsx @@ -0,0 +1,110 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, AutoComplete, Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; + +import { useFormNestedFieldsContext } from "./_context"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderTencentCloudSSLDeploy = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + } + > + + + + } + > + + + + } + > + ({ value }))} + placeholder={t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_type.placeholder")} + filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())} + /> + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: "", + resourceIds: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder")), + resourceType: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_type.placeholder")), + resourceIds: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => /^[A-Za-z0-9*._\-|]+$/.test(e)); + }, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudSSLDeploy, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLUpdate.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLUpdate.tsx new file mode 100644 index 000000000..28ba33443 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudSSLUpdate.tsx @@ -0,0 +1,130 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; + +import { useFormNestedFieldsContext } from "./_context"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderTencentCloudSSLUpdate = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + certificateId: "", + resourceTypes: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + certificateId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.placeholder")), + resourceTypes: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => !!e.trim()); + }, t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.placeholder")), + resourceRegions: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => !!e.trim()); + }, t("workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.placeholder")), + isReplaced: z.boolean().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudSSLUpdate, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudVOD.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudVOD.tsx new file mode 100644 index 000000000..72ec0ea13 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudVOD.tsx @@ -0,0 +1,82 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudVOD = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + subAppId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (v == null) return true; + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.tencentcloud_vod_sub_app_id.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudVOD, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudWAF.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudWAF.tsx new file mode 100644 index 000000000..5b1b219b5 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderTencentCloudWAF.tsx @@ -0,0 +1,101 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderTencentCloudWAF = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + domain: "", + domainId: "", + instanceId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + endpoint: z.string().nullish(), + region: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_waf_region.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + domainId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_waf_domain_id.placeholder")), + instanceId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_waf_instance_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderTencentCloudWAF, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUCDN.tsx new file mode 100644 index 000000000..01a3711ea --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUCDN.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderUCloudUCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domainId: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domainId: z.string().nonempty(t("workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderUCloudUCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUS3.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUS3.tsx new file mode 100644 index 000000000..2b4e83fd8 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUCloudUS3.tsx @@ -0,0 +1,78 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderUCloudUS3 = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + bucket: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.ucloud_us3_region.placeholder")), + bucket: z.string().nonempty(t("workflow_node.deploy.form.ucloud_us3_bucket.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderUCloudUS3, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUniCloudWebHost.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUniCloudWebHost.tsx new file mode 100644 index 000000000..e5d89b5da --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUniCloudWebHost.tsx @@ -0,0 +1,86 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderUniCloudWebHost = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + + + + + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + spaceProvider: "tencent", + spaceId: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + spaceProvider: z.string().nonempty(t("workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder")), + spaceId: z.string().nonempty(t("workflow_node.deploy.form.unicloud_webhost_space_id.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderUniCloudWebHost, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunCDN.tsx new file mode 100644 index 000000000..93a023a3b --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunCDN.tsx @@ -0,0 +1,58 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderUpyunCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderUpyunCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunFile.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunFile.tsx new file mode 100644 index 000000000..f5431a489 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderUpyunFile.tsx @@ -0,0 +1,58 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderUpyunFile = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + } /> + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderUpyunFile, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineALB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineALB.tsx new file mode 100644 index 000000000..164c73e91 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineALB.tsx @@ -0,0 +1,151 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderVolcEngineALB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.volcengine_alb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.volcengine_alb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + domain: z + .string() + .nullish() + .refine((v) => { + return !v || validDomainName(v!, { allowWildcard: true }); + }, t("common.errmsg.domain_invalid")), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + if (!values.loadbalancerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.volcengine_alb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + if (!values.listenerId?.trim()) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.volcengine_alb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineALB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCDN.tsx new file mode 100644 index 000000000..465822b96 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCLB.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCLB.tsx new file mode 100644 index 000000000..f38b4d8c2 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCLB.tsx @@ -0,0 +1,134 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +import { useFormNestedFieldsContext } from "./_context"; + +const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; +const RESOURCE_TYPE_LISTENER = "listener" as const; + +const BizDeployNodeConfigFormProviderVolcEngineCLB = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst); + + return ( + <> + } + > + + + + + + + + + } + > + + + + + + } + > + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + resourceType: RESOURCE_TYPE_LISTENER, + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + region: z.string().nonempty(t("workflow_node.deploy.form.volcengine_clb_region.placeholder")), + resourceType: z.literal([RESOURCE_TYPE_LOADBALANCER, RESOURCE_TYPE_LISTENER], t("workflow_node.deploy.form.volcengine_clb_resource_type.placeholder")), + loadbalancerId: z.string().nullish(), + listenerId: z.string().nullish(), + }) + .superRefine((values, ctx) => { + switch (values.resourceType) { + case RESOURCE_TYPE_LOADBALANCER: + { + const res = z.string().nonempty().safeParse(values.loadbalancerId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.volcengine_clb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }); + } + } + break; + + case RESOURCE_TYPE_LISTENER: + { + const res = z.string().nonempty().safeParse(values.listenerId); + if (!res.success) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.deploy.form.volcengine_clb_listener_id.placeholder"), + path: ["listenerId"], + }); + } + } + break; + } + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineCLB, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCertCenter.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCertCenter.tsx new file mode 100644 index 000000000..2f1297134 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineCertCenter.tsx @@ -0,0 +1,51 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineCertCenter = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.volcengine_certcenter_region.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineCertCenter, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineDCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineDCDN.tsx new file mode 100644 index 000000000..565dee33a --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineDCDN.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineDCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineDCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineImageX.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineImageX.tsx new file mode 100644 index 000000000..4ba361774 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineImageX.tsx @@ -0,0 +1,78 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineImageX = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + serviceId: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.volcengine_imagex_region.placeholder")), + serviceId: z.string().nonempty(t("workflow_node.deploy.form.volcengine_imagex_service_id.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineImageX, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineLive.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineLive.tsx new file mode 100644 index 000000000..0ddd3903c --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineLive.tsx @@ -0,0 +1,54 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineLive = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineLive, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineTOS.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineTOS.tsx new file mode 100644 index 000000000..8b6a6f10a --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderVolcEngineTOS.tsx @@ -0,0 +1,78 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderVolcEngineTOS = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + region: "", + bucket: "", + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + region: z.string().nonempty(t("workflow_node.deploy.form.volcengine_tos_region.placeholder")), + bucket: z.string().nonempty(t("workflow_node.deploy.form.volcengine_tos_bucket.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderVolcEngineTOS, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDN.tsx new file mode 100644 index 000000000..1360c5ccd --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDN.tsx @@ -0,0 +1,68 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import MultipleSplitValueInput from "@/components/MultipleSplitValueInput"; +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const MULTIPLE_INPUT_SEPARATOR = ";"; + +const BizDeployNodeConfigFormProviderWangsuCDN = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + domains: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domains: z.string().refine((v) => { + if (!v) return false; + return String(v) + .split(MULTIPLE_INPUT_SEPARATOR) + .every((e) => validDomainName(e, { allowWildcard: true })); + }, t("workflow_node.deploy.form.wangsu_cdn_domains.placeholder")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderWangsuCDN, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDNPro.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDNPro.tsx new file mode 100644 index 000000000..b5b2579cd --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCDNPro.tsx @@ -0,0 +1,97 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const ENVIRONMENT_PRODUCTION = "production" as const; +const ENVIRONMENT_STAGING = "stating" as const; + +const BizDeployNodeConfigFormProviderWangsuCDNPro = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + + + + + } + > + + + + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + environment: ENVIRONMENT_PRODUCTION, + domain: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + environment: z.literal([ENVIRONMENT_PRODUCTION, ENVIRONMENT_STAGING], t("workflow_node.deploy.form.wangsu_cdnpro_environment.placeholder")), + domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + certificateId: z.string().nullish(), + webhookId: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderWangsuCDNPro, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCertificate.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCertificate.tsx new file mode 100644 index 000000000..7d58eea64 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWangsuCertificate.tsx @@ -0,0 +1,50 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderWangsuCertificate = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + certificateId: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderWangsuCertificate, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWebhook.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWebhook.tsx new file mode 100644 index 000000000..b4290c8a7 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFormProviderWebhook.tsx @@ -0,0 +1,86 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Alert, Form } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import CodeInput from "@/components/CodeInput"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizDeployNodeConfigFormProviderWebhook = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const handleWebhookDataBlur = () => { + const value = formInst.getFieldValue("webhookData"); + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("webhookData", json); + } catch { + return; + } + }; + + return ( + <> + } + > + + + + + } /> + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + webhookData: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")), + }); +}; + +const _default = Object.assign(BizDeployNodeConfigFormProviderWebhook, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx new file mode 100644 index 000000000..a0d6b8cdd --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BizMonitorNodeConfigForm from "./BizMonitorNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BizMonitorNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BizMonitorNodeConfigDrawer = (_: BizMonitorNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BizMonitor) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizMonitor}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default BizMonitorNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigForm.tsx new file mode 100644 index 000000000..0ac7f0b3d --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizMonitorNodeConfigForm.tsx @@ -0,0 +1,119 @@ +import { useMemo } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { type AnchorProps, Form, type FormInstance, Input, InputNumber } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Tips from "@/components/Tips"; +import { type WorkflowNodeConfigForMonitor, defaultNodeConfigForMonitor } from "@/domain/workflow"; +import { useAntdForm } from "@/hooks"; +import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators"; + +import { NodeFormContextProvider } from "./_context"; +import { NodeType } from "../nodes/typings"; + +export interface BizMonitorNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BizMonitorNodeConfigForm = ({ node, ...props }: BizMonitorNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BizMonitor) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizMonitor}`); + } + + const { i18n, t } = useTranslation(); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForMonitor | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBizMonitorConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + return ( + + +
+ + } /> + + +
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + +
+ +
+ ); +}; + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters"].map((key) => ({ + key: key, + title: t(`workflow_node.monitor.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return defaultNodeConfigForMonitor(); +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + host: z.string().refine((v) => { + return validDomainName(v) || validIPv4Address(v) || validIPv6Address(v); + }, t("common.errmsg.host_invalid")), + port: z.preprocess( + (v) => Number(v), + z + .number() + .int(t("workflow_node.monitor.form.port.placeholder")) + .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid")) + ), + domain: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validDomainName(v); + }, t("common.errmsg.domain_invalid")), + requestPath: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizMonitorNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx new file mode 100644 index 000000000..faf51db57 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BizNotifyNodeConfigForm from "./BizNotifyNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BizNotifyNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BizNotifyNodeConfigDrawer = (_: BizNotifyNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BizNotify) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizNotify}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default BizNotifyNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx new file mode 100644 index 000000000..888b441fd --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx @@ -0,0 +1,253 @@ +import { useEffect, useMemo, useState } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { IconPlus } from "@tabler/icons-react"; +import { type AnchorProps, Button, Divider, Flex, Form, type FormInstance, Input, Switch, Typography } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import AccessEditDrawer from "@/components/access/AccessEditDrawer"; +import AccessSelect from "@/components/access/AccessSelect"; +import NotificationProviderSelect from "@/components/provider/NotificationProviderSelect"; +import { ACCESS_USAGES, NOTIFICATION_PROVIDERS, accessProvidersMap, notificationProvidersMap } from "@/domain/provider"; +import { type WorkflowNodeConfigForNotify, defaultNodeConfigForNotify } from "@/domain/workflow"; +import { useAntdForm, useZustandShallowSelector } from "@/hooks"; +import { useAccessesStore } from "@/stores/access"; + +import { FormNestedFieldsContextProvider, NodeFormContextProvider } from "./_context"; +import BizNotifyNodeConfigFormProviderDiscordBot from "./BizNotifyNodeConfigFormProviderDiscordBot"; +import BizNotifyNodeConfigFormProviderEmail from "./BizNotifyNodeConfigFormProviderEmail"; +import BizNotifyNodeConfigFormProviderMattermost from "./BizNotifyNodeConfigFormProviderMattermost"; +import BizNotifyNodeConfigFormProviderSlackBot from "./BizNotifyNodeConfigFormProviderSlackBot"; +import BizNotifyNodeConfigFormProviderTelegramBot from "./BizNotifyNodeConfigFormProviderTelegramBot"; +import BizNotifyNodeConfigFormProviderWebhook from "./BizNotifyNodeConfigFormProviderWebhook"; +import { NodeType } from "../nodes/typings"; + +export interface BizNotifyNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BizNotifyNodeConfigForm = ({ node, ...props }: BizNotifyNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BizNotify) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizNotify}`); + } + + const { i18n, t } = useTranslation(); + + const { accesses } = useAccessesStore(useZustandShallowSelector("accesses")); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForNotify | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBizNotifyNodeConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const fieldProvider = Form.useWatch("provider", { form: formInst, preserve: true }); + const fieldProviderAccessId = Form.useWatch("providerAccessId", { form: formInst, preserve: true }); + + const NestedProviderConfigFields = useMemo(() => { + /* + 注意:如果追加新的子组件,请保持以 ASCII 排序。 + NOTICE: If you add new child component, please keep ASCII order. + */ + switch (fieldProvider) { + case NOTIFICATION_PROVIDERS.DISCORDBOT: + return BizNotifyNodeConfigFormProviderDiscordBot; + case NOTIFICATION_PROVIDERS.EMAIL: + return BizNotifyNodeConfigFormProviderEmail; + case NOTIFICATION_PROVIDERS.MATTERMOST: + return BizNotifyNodeConfigFormProviderMattermost; + case NOTIFICATION_PROVIDERS.SLACKBOT: + return BizNotifyNodeConfigFormProviderSlackBot; + case NOTIFICATION_PROVIDERS.TELEGRAMBOT: + return BizNotifyNodeConfigFormProviderTelegramBot; + case NOTIFICATION_PROVIDERS.WEBHOOK: + return BizNotifyNodeConfigFormProviderWebhook; + } + }, [fieldProvider]); + + const [showProvider, setShowProvider] = useState(false); + useEffect(() => { + // 通常情况下每个授权信息只对应一个消息通知提供商,此时无需显示消息通知提供商字段; + // 如果对应多个,则显示。 + if (fieldProviderAccessId) { + const access = accesses.find((e) => e.id === fieldProviderAccessId); + const providers = Array.from(notificationProvidersMap.values()).filter((e) => e.provider === access?.provider); + setShowProvider(providers.length > 1); + } else { + setShowProvider(false); + } + }, [accesses, fieldProviderAccessId]); + + const handleProviderSelect = (value: string) => { + // 切换消息通知提供商时联动授权信息 + if (initialValues?.provider === value) { + formInst.setFieldValue("providerAccessId", initialValues?.providerAccessId); + } else { + if (notificationProvidersMap.get(fieldProvider)?.provider !== notificationProvidersMap.get(value)?.provider) { + formInst.setFieldValue("providerAccessId", void 0); + } + } + }; + + const handleProviderAccessSelect = (value: string) => { + // 切换授权信息时联动消息通知提供商 + const access = accesses.find((access) => access.id === value); + const provider = Array.from(notificationProvidersMap.values()).find((provider) => provider.provider === access?.provider); + if (fieldProvider !== provider?.type) { + formInst.setFieldValue("provider", provider?.type); + } + }; + + return ( + +
+
+ + + + + + + + + + + + + + { + if (option.reserve !== "notification") return false; + + const provider = accessProvidersMap.get(option.provider); + return !!provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION); + }} + /> + + + + + {NestedProviderConfigFields && } + +
+ +
+ + + {t("workflow_node.notify.form_anchor.strategy.title")} + + + + + +
{t("workflow_node.notify.form.skip_on_all_prev_skipped.prefix")}
+ + + +
{t("workflow_node.notify.form.skip_on_all_prev_skipped.suffix")}
+
+
+
+
+
+ ); +}; + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters", "strategy"].map((key) => ({ + key: key, + title: t(`workflow_node.notify.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return { + subject: "", + message: "", + provider: "", + providerAccessId: "", + ...defaultNodeConfigForNotify(), + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + subject: z + .string() + .min(1, t("workflow_node.notify.form.subject.placeholder")) + .max(20480, t("common.errmsg.string_max", { max: 20480 })), + message: z + .string() + .min(1, t("workflow_node.notify.form.message.placeholder")) + .max(20480, t("common.errmsg.string_max", { max: 20480 })), + provider: z.string().nonempty(t("workflow_node.notify.form.provider.placeholder")), + providerAccessId: z.string().nonempty(t("workflow_node.notify.form.provider_access.placeholder")), + providerConfig: z.any().nullish(), + skipOnAllPrevSkipped: z.boolean().nullish(), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderDiscordBot.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderDiscordBot.tsx new file mode 100644 index 000000000..83bc2caec --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderDiscordBot.tsx @@ -0,0 +1,50 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderDiscordBot = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + channelId: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderDiscordBot, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderEmail.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderEmail.tsx new file mode 100644 index 000000000..de878e595 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderEmail.tsx @@ -0,0 +1,58 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validEmailAddress } from "@/utils/validators"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderEmail = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + receiverAddress: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return validEmailAddress(v); + }, t("common.errmsg.email_invalid")), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderEmail, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderMattermost.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderMattermost.tsx new file mode 100644 index 000000000..53e8692d2 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderMattermost.tsx @@ -0,0 +1,50 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderMattermost = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + channelId: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderMattermost, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderSlackBot.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderSlackBot.tsx new file mode 100644 index 000000000..761b11c67 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderSlackBot.tsx @@ -0,0 +1,50 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderSlackBot = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + channelId: z.string().nullish(), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderSlackBot, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderTelegramBot.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderTelegramBot.tsx new file mode 100644 index 000000000..1d4a5996a --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderTelegramBot.tsx @@ -0,0 +1,61 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderTelegramBot = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + chatId: z + .preprocess( + (v) => (v == null || v === "" ? undefined : Number(v)), + z + .number() + .nullish() + .refine((v) => { + if (v == null || v + "" === "") return true; + return !Number.isNaN(+v!) && +v! !== 0; + }, t("workflow_node.notify.form.telegrambot_chat_id.placeholder")) + ) + .nullish(), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderTelegramBot, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderWebhook.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderWebhook.tsx new file mode 100644 index 000000000..cccf3cff5 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFormProviderWebhook.tsx @@ -0,0 +1,87 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import CodeInput from "@/components/CodeInput"; +import Tips from "@/components/Tips"; + +import { useFormNestedFieldsContext } from "./_context"; + +const BizNotifyNodeConfigFormProviderWebhook = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); + const initialValues = getInitialValues(); + + const handleWebhookDataBlur = () => { + const value = formInst.getFieldValue("webhookData"); + try { + const json = JSON.stringify(JSON.parse(value), null, 2); + formInst.setFieldValue("webhookData", json); + } catch { + return; + } + }; + + return ( + <> + } + > + + + + + } /> + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return {}; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + webhookData: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + try { + const obj = JSON.parse(v); + return typeof obj === "object" && !Array.isArray(obj); + } catch { + return false; + } + }, t("workflow_node.notify.form.webhook_data.errmsg.json_invalid")), + }); +}; + +const _default = Object.assign(BizNotifyNodeConfigFormProviderWebhook, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx new file mode 100644 index 000000000..daf140073 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BizUploadNodeConfigForm from "./BizUploadNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BizUploadNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BizUploadNodeConfigDrawer = (_: BizUploadNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BizUpload) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizUpload}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default BizUploadNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx new file mode 100644 index 000000000..1d9bf1522 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx @@ -0,0 +1,157 @@ +import { useMemo } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { type AnchorProps, Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validateCertificate, validatePrivateKey } from "@/api/certificates"; +import TextFileInput from "@/components/TextFileInput"; +import { type WorkflowNodeConfigForUpload, defaultNodeConfigForUpload } from "@/domain/workflow"; +import { useAntdForm } from "@/hooks"; +import { getErrMsg } from "@/utils/error"; + +import { NodeFormContextProvider } from "./_context"; +import { NodeType } from "../nodes/typings"; + +export interface BizUploadNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BizUpload) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BizUpload}`); + } + + const { i18n, t } = useTranslation(); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForUpload | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBizUploadConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const handleCertificateChange = async (value: string) => { + try { + const resp = await validateCertificate(value); + formInst.setFields([ + { + name: "domains", + value: resp.data.domains, + }, + { + name: "certificate", + value: value, + }, + ]); + } catch (e) { + formInst.setFields([ + { + name: "domains", + value: "", + }, + { + name: "certificate", + value: value, + errors: [getErrMsg(e)], + }, + ]); + } + }; + + const handlePrivateKeyChange = async (value: string) => { + try { + await validatePrivateKey(value); + formInst.setFields([ + { + name: "privateKey", + value: value, + }, + ]); + } catch (e) { + formInst.setFields([ + { + name: "privateKey", + value: value, + errors: [getErrMsg(e)], + }, + ]); + } + }; + + return ( + +
+
+ + + + + + + + + + + +
+
+
+ ); +}; + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters"].map((key) => ({ + key: key, + title: t(`workflow_node.upload.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return { + certificate: "", + privateKey: "", + ...defaultNodeConfigForUpload(), + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + domains: z.string().nullish(), + certificate: z + .string() + .min(1, t("workflow_node.upload.form.certificate.placeholder")) + .max(20480, t("common.errmsg.string_max", { max: 20480 })), + privateKey: z + .string() + .min(1, t("workflow_node.upload.form.private_key.placeholder")) + .max(20480, t("common.errmsg.string_max", { max: 20480 })), + }); +}; + +const _default = Object.assign(BizUploadNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx new file mode 100644 index 000000000..f1b7d1039 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import BranchBlockNodeConfigForm from "./BranchBlockNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface BranchBlockNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const BranchBlockNodeConfigDrawer = (_: BranchBlockNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.BranchBlock) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BranchBlock}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default BranchBlockNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx new file mode 100644 index 000000000..b915388c6 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigForm.tsx @@ -0,0 +1,94 @@ +import { useMemo, useRef } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { type AnchorProps, Form, type FormInstance } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type WorkflowNodeConfigForCondition, defaultNodeConfigForCondition } from "@/domain/workflow"; +import { useAntdForm } from "@/hooks"; + +import { NodeFormContextProvider } from "./_context"; +import BranchBlockNodeConfigFormExpressionEditor, { type BranchBlockNodeConfigFormExpressionEditorInstance } from "./BranchBlockNodeConfigFormExpressionEditor"; + +import { NodeType } from "../nodes/typings"; + +export interface BranchBlockNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const BranchBlockNodeConfigForm = ({ node, ...props }: BranchBlockNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.BranchBlock) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.BranchBlock}`); + } + + const { i18n, t } = useTranslation(); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForCondition | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }).superRefine(async (values, ctx) => { + if (values.expression != null) { + try { + await exprEditorRef.current!.validate(); + } catch { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.branch_block.form.expression.errmsg.invalid"), + path: ["expression"], + }); + } + } + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeBranchBlockConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const exprEditorRef = useRef(null); + + return ( + +
+
+ + + +
+
+
+ ); +}; + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters"].map((key) => ({ + key: key, + title: t(`workflow_node.branch_block.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return defaultNodeConfigForCondition(); +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t: _ } = i18n; + + return z.object({ + expression: z.any().nullish(), + }); +}; + +const _default = Object.assign(BranchBlockNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx new file mode 100644 index 000000000..d92747a10 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigFormExpressionEditor.tsx @@ -0,0 +1,382 @@ +import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { IconCircleMinus, IconCirclePlus } from "@tabler/icons-react"; +import { useControllableValue } from "ahooks"; +import { Button, Form, Input, Radio, Select, theme } from "antd"; + +import Show from "@/components/Show"; +import type { Expr, ExprComparisonOperator, ExprLogicalOperator, ExprValue, ExprValueSelector, ExprValueType } from "@/domain/workflow"; +import { ExprType } from "@/domain/workflow"; +import { useAntdFormName, useZustandShallowSelector } from "@/hooks"; +import { useWorkflowStore } from "@/stores/workflow"; + +import { useNodeFormContext } from "./_context"; + +export interface BranchBlockNodeConfigFormExpressionEditorProps { + className?: string; + style?: React.CSSProperties; + defaultValue?: Expr; + value?: Expr; + onChange?: (value: Expr) => void; +} + +export interface BranchBlockNodeConfigFormExpressionEditorInstance { + validate: () => Promise; +} + +// 表单内部使用的扁平结构 +type ConditionItem = { + // 选择器,格式为 "${nodeId}#${outputName}#${valueType}" + // 将 [ExprValueSelector] 转为字符串形式,以便于结构化存储。 + leftSelector?: string; + // 比较运算符。 + operator?: ExprComparisonOperator; + // 值。 + // 将 [ExprValue] 转为字符串形式,以便于结构化存储。 + rightValue?: string; +}; + +type ConditionFormValues = { + conditions: ConditionItem[]; + logicalOperator: ExprLogicalOperator; +}; + +const exprToFormValues = (expr?: Expr): ConditionFormValues => { + if (!expr) return getInitialValues(); + + const conditions: ConditionItem[] = []; + let logicalOp: ExprLogicalOperator = "and"; + + const extractExpr = (expr: Expr): void => { + if (expr.type === ExprType.Comparison) { + if (expr.left.type == ExprType.Variant && expr.right.type == ExprType.Constant) { + conditions.push({ + leftSelector: expr.left.selector?.id != null ? `${expr.left.selector.id}#${expr.left.selector.name}#${expr.left.selector.type}` : undefined, + operator: expr.operator != null ? expr.operator : undefined, + rightValue: expr.right?.value != null ? String(expr.right.value) : undefined, + }); + } else { + console.warn("[certimate] invalid comparison expression: left must be a variant and right must be a constant", expr); + } + } else if (expr.type === ExprType.Logical) { + logicalOp = expr.operator || "and"; + extractExpr(expr.left); + extractExpr(expr.right); + } + }; + + extractExpr(expr); + + return { + conditions: conditions, + logicalOperator: logicalOp, + }; +}; + +const formValuesToExpr = (values: ConditionFormValues): Expr | undefined => { + const wrapExpr = (condition: ConditionItem): Expr => { + const [id, name, type] = (condition.leftSelector?.split("#") ?? ["", "", ""]) as [string, string, ExprValueType]; + const valid = !!id && !!name && !!type; + + const left: Expr = { + type: ExprType.Variant, + selector: valid + ? { + id: id, + name: name, + type: type, + } + : ({} as ExprValueSelector), + }; + + const right: Expr = { + type: ExprType.Constant, + value: condition.rightValue!, + valueType: type, + }; + + return { + type: ExprType.Comparison, + operator: condition.operator!, + left, + right, + }; + }; + + if (values.conditions.length === 0) { + return undefined; + } + + // 只有一个条件时,直接返回比较表达式 + if (values.conditions.length === 1) { + const { leftSelector, operator, rightValue } = values.conditions[0]; + if (!leftSelector || !operator || !rightValue) { + return undefined; + } + return wrapExpr(values.conditions[0]); + } + + // 多个条件时,通过逻辑运算符连接 + let expr: Expr = wrapExpr(values.conditions[0]); + for (let i = 1; i < values.conditions.length; i++) { + expr = { + type: ExprType.Logical, + operator: values.logicalOperator, + left: expr, + right: wrapExpr(values.conditions[i]), + }; + } + return expr; +}; + +const BranchBlockNodeConfigFormExpressionEditor = forwardRef( + ({ className, style, ...props }, ref) => { + const { t } = useTranslation(); + + const { token: themeToken } = theme.useToken(); + + const { getWorkflowOuptutBeforeId } = useWorkflowStore(useZustandShallowSelector(["updateNode", "getWorkflowOuptutBeforeId"])); + + const [value, setValue] = useControllableValue(props, { + valuePropName: "value", + defaultValuePropName: "defaultValue", + trigger: "onChange", + }); + + const { node } = useNodeFormContext(); + + const [formInst] = Form.useForm(); + const formName = useAntdFormName({ form: formInst, name: "workflowNodeConditionConfigFormExpressionEditorForm" }); + const [formModel, setFormModel] = useState(getInitialValues()); + + useEffect(() => { + if (value) { + const formValues = exprToFormValues(value); + formInst.setFieldsValue(formValues); + setFormModel(formValues); + } else { + formInst.resetFields(); + setFormModel(getInitialValues()); + } + }, [value]); + + // TODO: 适配 flowgram + const ciSelectorCandidates = useMemo(() => { + const previousNodes = getWorkflowOuptutBeforeId(node.id); + return previousNodes + .map((node) => { + const group = { + label: node.name, + options: Array<{ label: string; value: string }>(), + }; + + for (const output of node.outputs ?? []) { + switch (output.type) { + case "certificate": + group.options.push({ + label: `${output.label} - ${t("workflow.variables.selector.validity.label")}`, + value: `${node.id}#${output.name}.validity#boolean`, + }); + group.options.push({ + label: `${output.label} - ${t("workflow.variables.selector.days_left.label")}`, + value: `${node.id}#${output.name}.daysLeft#number`, + }); + break; + + default: + group.options.push({ + label: `${output.label}`, + value: `${node.id}#${output.name}#${output.type}`, + }); + console.warn("[certimate] invalid workflow output type in condition expressions", output); + break; + } + } + + return group; + }) + .filter((item) => item.options.length > 0); + }, [node.id]); + + const getValueTypeBySelector = (selector: string): ExprValueType | undefined => { + if (!selector) return; + + const parts = selector.split("#"); + if (parts.length >= 3) { + return parts[2].toLowerCase() as ExprValueType; + } + }; + + const getOperatorsBySelector = (selector: string): { value: ExprComparisonOperator; label: string }[] => { + const valueType = getValueTypeBySelector(selector); + return getOperatorsByValueType(valueType!); + }; + + const getOperatorsByValueType = (valueType: ExprValue): { value: ExprComparisonOperator; label: string }[] => { + switch (valueType) { + case "number": + return [ + { value: "eq", label: t("workflow_node.branch_block.form.expression.operator.option.eq.label") }, + { value: "neq", label: t("workflow_node.branch_block.form.expression.operator.option.neq.label") }, + { value: "gt", label: t("workflow_node.branch_block.form.expression.operator.option.gt.label") }, + { value: "gte", label: t("workflow_node.branch_block.form.expression.operator.option.gte.label") }, + { value: "lt", label: t("workflow_node.branch_block.form.expression.operator.option.lt.label") }, + { value: "lte", label: t("workflow_node.branch_block.form.expression.operator.option.lte.label") }, + ]; + + case "string": + return [ + { value: "eq", label: t("workflow_node.branch_block.form.expression.operator.option.eq.label") }, + { value: "neq", label: t("workflow_node.branch_block.form.expression.operator.option.neq.label") }, + ]; + + case "boolean": + return [ + { value: "eq", label: t("workflow_node.branch_block.form.expression.operator.option.eq.alias_is_label") }, + { value: "neq", label: t("workflow_node.branch_block.form.expression.operator.option.neq.alias_not_label") }, + ]; + + default: + return []; + } + }; + + const handleFormChange = (_: undefined, values: ConditionFormValues) => { + setValue(formValuesToExpr(values)); + }; + + useImperativeHandle(ref, () => { + return { + validate: async () => { + await formInst.validateFields(); + }, + } as BranchBlockNodeConfigFormExpressionEditorInstance; + }); + + return ( +
+ 1}> + + + {t("workflow_node.branch_block.form.expression.logical_operator.option.and.label")} + {t("workflow_node.branch_block.form.expression.logical_operator.option.or.label")} + + + + + + {(fields, { add, remove }) => ( +
+ {fields.map(({ key, name: index, ...rest }) => ( +
+ {/* 左:变量选择器 */} + + + + ); + }} + + + {/* 右:输入控件,根据变量类型决定组件 */} + { + return prevValues.conditions?.[index]?.leftSelector !== currentValues.conditions?.[index]?.leftSelector; + }} + > + {({ getFieldValue }) => { + const leftSelector = getFieldValue(["conditions", index, "leftSelector"]); + const valueType = getValueTypeBySelector(leftSelector); + + return ( + + {valueType === "string" ? ( + + ) : valueType === "number" ? ( + + ) : valueType === "boolean" ? ( + + ) : ( + + )} + + ); + }} + + +
+ ))} + + + + +
+ )} +
+
+ ); + } +); + +const getInitialValues = (): ConditionFormValues => { + return { + conditions: [{}], + logicalOperator: "and", + }; +}; + +export default BranchBlockNodeConfigFormExpressionEditor; diff --git a/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx b/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx new file mode 100644 index 000000000..8b59e30a3 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/StartNodeConfigDrawer.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; +import { Form } from "antd"; + +import { NodeConfigDrawer } from "./_shared"; +import StartNodeConfigForm from "./StartNodeConfigForm"; +import { NodeType } from "../nodes/typings"; + +export interface StartNodeConfigDrawerProps { + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +const StartNodeConfigDrawer = (_: StartNodeConfigDrawerProps) => { + const { node, ...props } = _; + if (node.flowNodeType !== NodeType.Start) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.Start}`); + } + + const { i18n } = useTranslation(); + + const [formInst] = Form.useForm(); + + return ( + + + + ); +}; + +export default StartNodeConfigDrawer; diff --git a/ui/src/components/workflow/designer/forms/StartNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/StartNodeConfigForm.tsx new file mode 100644 index 000000000..163768b3b --- /dev/null +++ b/ui/src/components/workflow/designer/forms/StartNodeConfigForm.tsx @@ -0,0 +1,158 @@ +import { useEffect, useMemo, useState } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { IconDice6 } from "@tabler/icons-react"; +import { type AnchorProps, Button, Form, type FormInstance, Input, Radio, Space } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import dayjs from "dayjs"; +import { z } from "zod"; + +import Show from "@/components/Show"; +import Tips from "@/components/Tips"; +import { WORKFLOW_TRIGGERS, type WorkflowNodeConfigForStart, type WorkflowTriggerType, defaultNodeConfigForStart } from "@/domain/workflow"; +import { useAntdForm } from "@/hooks"; +import { getNextCronExecutions, validCronExpression } from "@/utils/cron"; + +import { NodeFormContextProvider } from "./_context"; +import { NodeType } from "../nodes/typings"; + +export interface StartNodeConfigFormProps { + form: FormInstance; + node: FlowNodeEntity; +} + +const StartNodeConfigForm = ({ node, ...props }: StartNodeConfigFormProps) => { + if (node.flowNodeType !== NodeType.Start) { + console.warn(`[certimate] current workflow node type is not: ${NodeType.Start}`); + } + + const { i18n, t } = useTranslation(); + + const initialValues = useMemo(() => { + return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForStart | undefined; + }, [node]); + + const formSchema = getSchema({ i18n }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + form: props.form, + name: "workflowNodeStartConfigForm", + initialValues: initialValues ?? getInitialValues(), + }); + + const fieldTrigger = Form.useWatch("trigger", formInst); + const fieldTriggerCron = Form.useWatch("triggerCron", formInst); + const [fieldTriggerCronExpectedExecutions, setFieldTriggerCronExpectedExecutions] = useState([]); + useEffect(() => { + setFieldTriggerCronExpectedExecutions(getNextCronExecutions(fieldTriggerCron, 5)); + }, [fieldTriggerCron]); + + const handleTriggerChange = (value: string) => { + if (value === WORKFLOW_TRIGGERS.SCHEDULED) { + formInst.setFieldValue("triggerCron", initialValues?.triggerCron || getInitialValues().triggerCron); + } else { + formInst.setFieldValue("triggerCron", void 0); + } + }; + + const handleRandomCronClick = () => { + const m = Math.floor(Math.random() * 60); + const h = Math.floor(Math.random() * 24); + formInst.setFieldValue("triggerCron", `${m} ${h} * * *`); + }; + + return ( + +
+
+ + handleTriggerChange(e.target.value)}> + {t("workflow_node.start.form.trigger.option.manual.label")} + {t("workflow_node.start.form.trigger.option.scheduled.label")} + + + + + + + + } /> + + +
+
+
+ ); +}; + +const getAnchorItems = ({ i18n = getI18n() }: { i18n: ReturnType }): Required["items"] => { + const { t } = i18n; + + return ["parameters"].map((key) => ({ + key: key, + title: t(`workflow_node.start.form_anchor.${key}.tab`), + href: "#" + key, + })); +}; + +const getInitialValues = (): Nullish>> => { + return { + trigger: WORKFLOW_TRIGGERS.MANUAL, + ...defaultNodeConfigForStart(), + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z + .object({ + trigger: z.string().nonempty(t("workflow_node.start.form.trigger.placeholder")), + triggerCron: z.string().nullish(), + }) + .superRefine((values, ctx) => { + if (values.trigger === WORKFLOW_TRIGGERS.SCHEDULED) { + if (!validCronExpression(values.triggerCron!)) { + ctx.addIssue({ + code: "custom", + message: t("workflow_node.start.form.trigger_cron.errmsg.invalid"), + path: ["triggerCron"], + }); + } + } + }); +}; + +const _default = Object.assign(StartNodeConfigForm, { + getAnchorItems, + getSchema, +}); + +export default _default; diff --git a/ui/src/components/workflow/designer/forms/_context.ts b/ui/src/components/workflow/designer/forms/_context.ts new file mode 100644 index 000000000..9d72a5653 --- /dev/null +++ b/ui/src/components/workflow/designer/forms/_context.ts @@ -0,0 +1,40 @@ +import { createContext, useContext } from "react"; +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; + +// #region FormNestedFieldsContext +export type FormNestedFieldsContextType = { + parentNamePath: string; +}; + +export const FormNestedFieldsContext = createContext({ + parentNamePath: "", +}); + +export const FormNestedFieldsContextProvider = FormNestedFieldsContext.Provider; + +export const useFormNestedFieldsContext = () => { + const context = useContext(FormNestedFieldsContext); + if (!context) { + throw new Error("`FormNestedFieldsContext` must be used within a `FormNestedFieldsContextProvider`"); + } + return context; +}; +// #endregion + +// #region NodeFormContext +export type NodeFormContextType = { + node: FlowNodeEntity; +}; + +export const NodeFormContext = createContext({} as NodeFormContextType); + +export const NodeFormContextProvider = NodeFormContext.Provider; + +export const useNodeFormContext = () => { + const context = useContext(NodeFormContext); + if (!context) { + throw new Error("`NodeFormContext` must be used within a `NodeFormContextProvider`"); + } + return context; +}; +// #endregion diff --git a/ui/src/components/workflow/designer/forms/_shared.tsx b/ui/src/components/workflow/designer/forms/_shared.tsx new file mode 100644 index 000000000..85d90669d --- /dev/null +++ b/ui/src/components/workflow/designer/forms/_shared.tsx @@ -0,0 +1,200 @@ +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { type FlowNodeEntity, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { IconX } from "@tabler/icons-react"; +import { useControllableValue } from "ahooks"; +import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Typography } from "antd"; +import { isEqual } from "radash"; + +import { getErrMsg } from "@/utils/error"; + +import { type NodeRegistry } from "../nodes/typings"; + +export interface NodeConfigDrawerProps { + children: React.ReactNode; + anchor?: Pick, "items"> | false; + footer?: boolean; + form: FormInstance; + loading?: boolean; + node: FlowNodeEntity; + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +export const NodeConfigDrawer = (_: NodeConfigDrawerProps) => { + const { children, anchor, footer = true, form: formInst, loading, node, ...props } = _; + + const { t } = useTranslation(); + + const { modal, notification } = App.useApp(); + + const [open, setOpen] = useControllableValue(props, { + valuePropName: "open", + defaultValuePropName: "defaultOpen", + trigger: "onOpenChange", + }); + + const containerRef = useRef(null); + + const [formPending, setFormPending] = useState(false); + + const nodeRegistry = node?.getNodeRegistry(); + const NodeIcon = nodeRegistry?.meta?.icon; + const renderNodeIcon = () => + NodeIcon == null ? null : ( +
+ +
+ ); + + const handleOkClick = async () => { + if (node == null) { + setOpen(false); + return; + } + + setFormPending(true); + try { + await formInst.validateFields(); + } catch (err) { + setFormPending(false); + throw err; + } + + try { + getNodeForm(node)!.setValueIn("config", formInst.getFieldsValue(true)); + getNodeForm(node)!.validate(); + + setOpen(false); + } catch (err) { + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + + throw err; + } finally { + setFormPending(false); + } + }; + + const handleCancelClick = () => { + if (formPending) return; + + setOpen(false); + }; + + const handleClose = () => { + if (formPending) return; + + const picker = (obj: Record) => { + return Object.entries(obj).reduce( + (acc, [key, value]) => { + const isEmpty = + value === null || + value === undefined || + (typeof value === "string" && value === "") || + (Array.isArray(value) && value.length === 0) || + (typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0); + + if (!isEmpty) { + acc[key] = value; + } + + return acc; + }, + {} as Record + ); + }; + const oldValues = picker(node?.toJSON()?.data?.config ?? {}); + const newValues = picker(formInst.getFieldsValue(true)); + const changed = !isEqual(oldValues, {}) && !isEqual(oldValues, newValues); + + const { promise, resolve, reject } = Promise.withResolvers(); + if (changed) { + console.log(oldValues, newValues); + modal.confirm({ + title: t("common.text.operation_confirm"), + content: t("workflow.detail.design.unsaved_changes.confirm"), + onOk: () => resolve(void 0), + onCancel: () => reject(), + }); + } else { + resolve(void 0); + } + + promise.then(() => setOpen(false)); + }; + + return ( + + + + + ) : ( + <> + ) + } + forceRender={false} + loading={loading} + maskClosable={!formPending} + open={open} + size="large" + title={ + <> + +
{renderNodeIcon()}
+
{node?.toJSON()?.data?.name}
+
+ } + /> + ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizApply, + data: { + name: t("workflow_node.apply.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx new file mode 100644 index 000000000..d48c6192f --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/BizDeployNodeRegistry.tsx @@ -0,0 +1,85 @@ +import { getI18n } from "react-i18next"; +import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconPackage } from "@tabler/icons-react"; +import { Avatar } from "antd"; +import { nanoid } from "nanoid"; + +import { deploymentProvidersMap } from "@/domain/provider"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const BizDeployNodeRegistry: NodeRegistry = { + type: NodeType.BizDeploy, + + meta: { + helpText: getI18n().t("workflow_node.deploy.help"), + labelText: getI18n().t("workflow_node.deploy.label"), + + icon: IconPackage, + iconColor: "#fff", + iconBgColor: "#5b65f5", + + clickable: true, + }, + + formMeta: { + validate: { + ["config.provider"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.providerAccessId"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + }, + + render: () => { + const { t } = getI18n(); + + return ( + + name="config.provider"> + {({ field: { value } }) => ( + <> + {value ? ( + <> +
{t(deploymentProvidersMap.get(value)?.name ?? "")}
+ + + ) : ( + t("workflow.detail.design.editor.placeholder") + )} + + )} + +
+ } + /> + ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizMonitor, + data: { + name: t("workflow_node.deploy.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/BizMonitorNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizMonitorNodeRegistry.tsx new file mode 100644 index 000000000..80a6851b3 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/BizMonitorNodeRegistry.tsx @@ -0,0 +1,67 @@ +import { getI18n } from "react-i18next"; +import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconDeviceDesktopSearch } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const BizMonitorNodeRegistry: NodeRegistry = { + type: NodeType.BizMonitor, + + meta: { + helpText: getI18n().t("workflow_node.monitor.help"), + labelText: getI18n().t("workflow_node.monitor.label"), + + icon: IconDeviceDesktopSearch, + iconColor: "#fff", + iconBgColor: "#5b65f5", + + clickable: true, + }, + + formMeta: { + validate: { + ["config.host"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + }, + + render: () => { + const { t } = getI18n(); + + return ( + + {({ field: { value: fieldDomain } }) => ( + + {({ field: { value: fieldHost } }) => ( + <>{fieldDomain || fieldHost ? fieldDomain || fieldHost : t("workflow.detail.design.editor.placeholder")} + )} + + )} + + } + /> + ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizMonitor, + data: { + name: t("workflow_node.monitor.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/BizNotifyNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizNotifyNodeRegistry.tsx new file mode 100644 index 000000000..4cd245d69 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/BizNotifyNodeRegistry.tsx @@ -0,0 +1,101 @@ +import { getI18n } from "react-i18next"; +import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconSend } from "@tabler/icons-react"; +import { Avatar } from "antd"; +import { nanoid } from "nanoid"; + +import { notificationProvidersMap } from "@/domain/provider"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const BizNotifyNodeRegistry: NodeRegistry = { + type: NodeType.BizNotify, + + meta: { + helpText: getI18n().t("workflow_node.notify.help"), + labelText: getI18n().t("workflow_node.notify.label"), + + icon: IconSend, + iconColor: "#fff", + iconBgColor: "#0693d4", + + clickable: true, + }, + + formMeta: { + validate: { + ["config.subject"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.message"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.provider"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.providerAccessId"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + }, + + render: () => { + const { t } = getI18n(); + + return ( + + name="config.provider"> + {({ field: { value } }) => ( + <> + {value ? ( + <> +
{t(notificationProvidersMap.get(value)?.name ?? "")}
+ + + ) : ( + t("workflow.detail.design.editor.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/BizUploadNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx new file mode 100644 index 000000000..2a7b7d1e2 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx @@ -0,0 +1,65 @@ +import { getI18n } from "react-i18next"; +import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconCloudUpload } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const BizUploadNodeRegistry: NodeRegistry = { + type: NodeType.BizUpload, + + meta: { + helpText: getI18n().t("workflow_node.upload.help"), + labelText: getI18n().t("workflow_node.upload.label"), + + icon: IconCloudUpload, + iconColor: "#fff", + iconBgColor: "#5b65f5", + + clickable: true, + }, + + formMeta: { + validate: { + ["config.certificate"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.privateKey"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + }, + + render: () => { + const { t } = getI18n(); + + return ( + name="config.domains">{({ field: { value } }) => <>{value || t("workflow.detail.design.editor.placeholder")}}} + /> + ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizUpload, + data: { + name: t("workflow_node.upload.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..1f306e3c2 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx @@ -0,0 +1,160 @@ +import { getI18n } from "react-i18next"; +import { Field, FlowNodeBaseType, FlowNodeSplitType } from "@flowgram.ai/fixed-layout-editor"; +import { IconFilter, IconFilterFilled, IconSitemap } from "@tabler/icons-react"; +import { Typography } from "antd"; +import { nanoid } from "nanoid"; + +import { type Expr, ExprType } from "@/domain/workflow"; + +import { BaseNode, BranchNode } 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"), + labelText: getI18n().t("workflow_node.condition.label"), + + icon: IconSitemap, + iconColor: "#fff", + iconBgColor: "#373c43", + + clickable: false, + expandable: false, + + deleteDisable: false, + }, + + formMeta: { + 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: { + labelText: getI18n().t("workflow_node.branch_block.label"), + + icon: IconSitemap, + iconColor: "#fff", + iconBgColor: "#373c43", + + clickable: true, + + addDisable: true, + copyDisable: true, + }, + + formMeta: { + render: () => { + const { t } = getI18n(); + + return ( + +
+
+ name="config.expression"> + {({ field: { value } }) => ( + <> + {value == null ? ( + + ) : ( + + )} + + )} + +
+
+ name="name">{({ field: { value } }) => <>{value || "\u00A0"}} +
+
+
+
+ name="config.expression"> + {({ field: { value } }) => ( + + {value == null + ? t("workflow_node.branch_block.state.no") + : value.type === ExprType.Logical && value.operator === "and" + ? t("workflow_node.branch_block.state.and") + : t("workflow_node.branch_block.state.or")} + + )} + +
+
+ + } + /> + ); + }, + }, + + canAdd: () => { + return false; + }, + + canDelete: (_, node) => { + return node.parent != null && node.parent.blocks.length >= 2; + }, + + onAdd(_, from) { + const { t } = getI18n(); + + let nodeName = t("workflow_node.branch_block.default_name"); + if (from != null) { + const siblingLength = from.blocks?.find((b) => b.isInlineBlocks)?.blocks?.length; + if (siblingLength != null) { + nodeName = `${nodeName} ${siblingLength + 1}`; + } + } + + return { + id: nanoid(), + type: NodeType.BranchBlock, + data: { + name: nodeName, + }, + }; + }, +}; 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..649e56503 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/EndNode.tsx @@ -0,0 +1,77 @@ +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"), + labelText: getI18n().t("workflow_node.end.label"), + + icon: IconLogout, + iconColor: "#fff", + iconBgColor: "#336df4", + + isNodeEnd: true, + + clickable: false, + 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..6dbb4a980 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/StartNode.tsx @@ -0,0 +1,91 @@ +import { getI18n } from "react-i18next"; +import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconRocket } from "@tabler/icons-react"; + +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"), + labelText: getI18n().t("workflow_node.start.label"), + + icon: IconRocket, + iconColor: "#fff", + iconBgColor: "#ed6d0c", + + isStart: true, + + clickable: true, + expandable: false, + selectable: false, + + addDisable: true, + copyDisable: true, + deleteDisable: true, + }, + + formMeta: { + validate: { + ["config.trigger"]: ({ value }) => { + if (!value) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + ["config.triggerCron"]: ({ value, formValues }) => { + if (!value && formValues.config.trigger === WORKFLOW_TRIGGERS.SCHEDULED) { + return { + message: "required", + level: FeedbackLevel.Error, + }; + } + }, + }, + + render: () => { + const { t } = getI18n(); + + return ( + + + {({ field: { value: fieldTrigger } }) => ( + <> +
+ {fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED + ? t("workflow.props.trigger.scheduled") + : fieldTrigger === WORKFLOW_TRIGGERS.MANUAL + ? t("workflow.props.trigger.manual") + : t("workflow.detail.design.editor.placeholder")} +
+
+ + {({ field: { value: fieldTriggerCron } }) => <>{fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED ? fieldTriggerCron || "\u00A0" : ""}} + +
+ + )} +
+ + } + /> + ); + }, + }, + + 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..0dca9bdfc --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx @@ -0,0 +1,123 @@ +import { getI18n } from "react-i18next"; +import { Field } from "@flowgram.ai/fixed-layout-editor"; +import { IconArrowsSplit, IconCircleX } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode, BranchNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const TryCatchNodeRegistry: NodeRegistry = { + type: NodeType.TryCatch, + + meta: { + helpText: getI18n().t("workflow_node.try_catch.help"), + labelText: getI18n().t("workflow_node.try_catch.label"), + + icon: IconArrowsSplit, + iconColor: "#fff", + iconBgColor: "#373c43", + + clickable: false, + expandable: false, + }, + + formMeta: { + 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: { + labelText: getI18n().t("workflow_node.catch_block.label"), + + clickable: false, + draggable: false, + + addDisable: true, + }, + + formMeta: { + render: () => { + return ( + +
+ +
+
+ name="name">{({ field: { value } }) => <>{value || "\u00A0"}} +
+ + } + /> + ); + }, + }, + + canAdd: () => false, + + canDelete: (_, node) => { + return node.parent != null && node.parent.blocks.length >= 2; + }, + + onAdd() { + const { t } = getI18n(); + + return { + 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"), + }, + }; + }, +}; 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..be87a0e12 --- /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: "ifElse", + + /** + * 自定义节点扩展: + * - loop: 扩展为循环节点 + * - start: 扩展为开始节点 + * - dynamicSplit: 扩展为分支节点 + * - end: 扩展为结束节点 + * - tryCatch: 扩展为 tryCatch 节点 + * - break: 分支断开 + * - default: 扩展为普通节点 (默认) + */ + extend: "dynamicSplit", + + /** + * 节点配置信息 + */ + meta: { + isStart: false, // 是否为开始节点 + isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点 + draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽 + selectable: false, // 触发器等开始节点不能被框选 + deleteDisable: true, // 禁止删除 + copyDisable: true, // 禁止克隆 + addDisable: true, // 禁止添加 + }, + + onAdd() { + return { + id: `IfElse_${nanoid(5)}`, + type: "ifElse", + data: { + title: "IfElse", + }, + blocks: [ + { + id: nanoid(5), + type: "block", + data: { + title: "If", + }, + }, + { + id: nanoid(5), + type: "block", + data: { + title: "Else", + }, + }, + ], + }; + }, +}; 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..2c92c2e86 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/_shared.tsx @@ -0,0 +1,503 @@ +import { startTransition, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Field, + type FlowNodeEntity, + FlowNodeRenderData, + type NodeRenderReturnType, + useClientContext, + useWatchFormState, +} from "@flowgram.ai/fixed-layout-editor"; +import { IconCopy, IconDotsVertical, IconExclamationCircle, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react"; +import { Button, type ButtonProps, Card, Dropdown, Input, type InputRef, Popover, Tooltip, theme } from "antd"; +import { Immer } from "immer"; +import { nanoid } from "nanoid"; + +import { mergeCls } from "@/utils/css"; + +import { type NodeJSON, type NodeRegistry, NodeType } from "./typings"; +import { useNodeRenderContext } from "../NodeRenderContext"; + +const useInternalRenamingInput = ({ nodeRender }: { nodeRender: NodeRenderReturnType }) => { + const inputRef = useRef(null); + const [inputVisible, setInputVisible] = useState(false); + const [inputValue, setInputValue] = useState(""); + + const handleNodeRenameClick = () => { + setInputVisible(true); + setInputValue(nodeRender.data?.name); + setTimeout(() => { + inputRef.current?.focus({ cursor: "end" }); + }, 0); + }; + + const handleNodeNameChange = (value: string) => { + setInputValue(value); + }; + + const handleNodeNameConfirm = async (value: string) => { + value = value.trim(); + if (!value || value === (nodeRender.data?.name || "")) { + setInputVisible(false); + return; + } + + setInputVisible(false); + + nodeRender.updateData({ name: value }); + }; + + return { + inputRef: inputRef, + visible: inputVisible, + value: inputValue, + onClick: handleNodeRenameClick, + onChange: handleNodeNameChange, + onConfirm: handleNodeNameConfirm, + }; +}; + +const InternalNodeCard = ({ + className, + style, + children, + nodeRender, +}: { + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + nodeRender: NodeRenderReturnType; +}) => { + const nodeRenderData = nodeRender.node.getData(FlowNodeRenderData)!; + const nodeRegistry = nodeRender.node.getNodeRegistry(); + + const isActivated = useMemo(() => nodeRenderData.activated || nodeRenderData.lineActivated, [nodeRenderData.activated, nodeRenderData.lineActivated]); + const [isHovered, setIsHovered] = useState(false); + const [isInvalid, setIsInvalid] = useState(false); + + const formState = useWatchFormState(nodeRender.node); + useEffect(() => setIsInvalid(!!formState?.invalid), [formState?.invalid]); + + return ( + startTransition(() => setIsHovered(true))} + onMouseLeave={() => startTransition(() => setIsHovered(false))} + > +
{children}
+
+ + ); +}; + +const InternalNodeMenuButton = ({ + className, + style, + onMenuSelect, + ...props +}: ButtonProps & { + onMenuSelect?: (key: "rename" | "duplicate" | "remove") => void; +}) => { + const { t } = useTranslation(); + + const ctx = useClientContext(); + const { operation, playground } = ctx; + + const { node, ...nodeRender } = useNodeRenderContext(); + const nodeRegistry = node.getNodeRegistry(); + + const getLatestDuplicateDisabledState = () => { + if (nodeRegistry.meta?.copyDisable != null && nodeRegistry.meta.copyDisable) { + return true; + } + return false; + }; + const getLatestRemoveDisabledState = () => { + if (nodeRegistry.meta?.deleteDisable != null && nodeRegistry.meta.deleteDisable) { + return true; + } + if (nodeRegistry.canDelete != null) { + return !nodeRegistry.canDelete(ctx, node); + } + return false; + }; + const [menuDuplicateDisabled, setMenuDuplicateDisabled] = useState(() => getLatestDuplicateDisabledState()); + const [menuRemoveDisabled, setMenuRemoveDisabled] = useState(() => getLatestRemoveDisabledState()); + useEffect(() => { + // 这里不能使用 useMemo() 来决定 menuRemoveDisabled,因为依赖项没有发生改变(对象引用始终是同一个) + // 因此需要使用 useEffect() 来监听 node 和 node.parent 的变化,并更新 menuRemoveDisabled 的状态 + const callback = () => { + setMenuDuplicateDisabled(getLatestDuplicateDisabledState()); + setMenuRemoveDisabled(getLatestRemoveDisabledState()); + }; + const d1 = node.onEntityChange(callback); + const d2 = node.parent?.onEntityChange?.(callback); + return () => { + d1?.dispose(); + d2?.dispose(); + }; + }, []); + + const handleClickRename = () => { + onMenuSelect?.("rename"); + }; + + const handleClickDuplicate = () => { + if (menuDuplicateDisabled) { + return; + } + + const parent = node.originParent ?? node.parent; + if (parent != null) { + const nodeJSON = duplicateNodeJSON(node.toJSON() as NodeJSON); + + let block: FlowNodeEntity; + if (nodeRender.isBlockOrderIcon) { + block = operation.addBlock(parent, nodeJSON); + } else { + block = operation.addFromNode(node, nodeJSON); + } + + setTimeout(() => { + playground.scrollToView({ + bounds: block.bounds, + scrollToCenter: true, + }); + }, 1); + + onMenuSelect?.("duplicate"); + } + }; + + const handleClickRemove = () => { + if (menuRemoveDisabled) { + return; + } + + nodeRender.deleteNode(); + + onMenuSelect?.("remove"); + }; + + return playground.config.readonlyOrDisabled ? null : ( + , + onClick: handleClickRename, + }, + { + key: "duplicate", + label: nodeRender.isBlockOrderIcon ? t("workflow.detail.design.editor.duplicate_branch") : t("workflow.detail.design.editor.duplicate_node"), + icon: , + disabled: menuDuplicateDisabled, + onClick: handleClickDuplicate, + }, + { + type: "divider", + }, + { + key: "remove", + label: nodeRender.isBlockOrderIcon ? t("workflow.detail.design.editor.remove_branch") : t("workflow.detail.design.editor.remove_node"), + icon: , + danger: true, + disabled: menuRemoveDisabled, + onClick: handleClickRemove, + }, + ], + }} + overlayStyle={{ + zIndex: 10 /* 确保要比 Minimap 组件层级要高,防止被遮挡而点击不到 */, + }} + trigger={["click"]} + > +
@@ -365,7 +366,7 @@ const ApplyNodeConfigForm = forwardRef - {t("workflow_node.apply.form.certificate_config.label")} + {t("workflow_node.apply.form_anchor.certificate.title")} @@ -468,7 +469,7 @@ const ApplyNodeConfigForm = forwardRef - {t("workflow_node.apply.form.advanced_config.label")} + {t("workflow_node.apply.form_anchor.advanced.title")} @@ -556,7 +557,7 @@ const ApplyNodeConfigForm = forwardRef - {t("workflow_node.apply.form.strategy_config.label")} + {t("workflow_node.apply.form_anchor.strategy.title")} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index bc8c01e42..d27f1ec17 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -1,6 +1,7 @@ import { forwardRef, memo, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { IconHelp, IconPlus } from "@tabler/icons-react"; +import { QuestionCircleOutlined as IconQuestionCircleOutlined } from "@ant-design/icons"; +import { IconPlus } from "@tabler/icons-react"; import { Button, Divider, Flex, Form, type FormInstance, Select, Switch, Tooltip, Typography, theme } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -482,7 +483,7 @@ const DeployNodeConfigForm = forwardRef{t("workflow_node.deploy.form.provider_access.label")} - + @@ -547,7 +548,7 @@ const DeployNodeConfigForm = forwardRef - {t("workflow_node.deploy.form.params_config.label")} + {t("workflow_node.deploy.form_anchor.deployment.title")} @@ -557,7 +558,7 @@ const DeployNodeConfigForm = forwardRef - {t("workflow_node.deploy.form.strategy_config.label")} + {t("workflow_node.deploy.form_anchor.strategy.title")} diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx index 3a3036e65..65112f8a8 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx @@ -56,7 +56,6 @@ const NotifyNodeConfigForm = forwardRef - {t("workflow_node.notify.form.strategy_config.label")} + {t("workflow_node.notify.form_anchor.strategy.title")} diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx index 444f70903..72649f465 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormEmailConfig.tsx @@ -6,7 +6,6 @@ import { z } from "zod"; import { validEmailAddress } from "@/utils/validators"; type NotifyNodeConfigFormEmailConfigFieldValues = Nullish<{ - senderAddress?: string; receiverAddress?: string; }>; diff --git a/ui/src/components/workflow/node/StartNodeConfigForm.tsx b/ui/src/components/workflow/node/StartNodeConfigForm.tsx index 900bfc63a..a343c9fd6 100644 --- a/ui/src/components/workflow/node/StartNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/StartNodeConfigForm.tsx @@ -103,7 +103,7 @@ const StartNodeConfigForm = forwardRef 0}>
- {t("workflow_node.start.form.trigger_cron.extra")} + {t("workflow_node.start.form.trigger_cron.help")}
{fieldTriggerCronExpectedExecutions.map((date, index) => ( 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/domain/workflow.ts b/ui/src/domain/workflow.ts index ac2e2b19d..d8a5312b2 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -3,6 +3,7 @@ import { Immer, produce } from "immer"; import { nanoid } from "nanoid"; import i18n from "@/i18n"; +import { type WorkflowRunModel } from "./workflowRun"; export interface WorkflowModel extends BaseModel { name: string; @@ -16,6 +17,9 @@ export interface WorkflowModel extends BaseModel { lastRunRef?: string; lastRunStatus?: string; lastRunTime?: string; + expand?: { + lastRunRef?: WorkflowRunModel; + }; } export const WORKFLOW_TRIGGERS = Object.freeze({ @@ -135,7 +139,7 @@ export type WorkflowNodeConfigForStart = { export const defaultNodeConfigForStart = (): Partial => { return { - trigger: WORKFLOW_TRIGGERS.SCHEDULED, + trigger: WORKFLOW_TRIGGERS.MANUAL, triggerCron: "0 0 * * *", }; }; @@ -188,6 +192,7 @@ export type WorkflowNodeConfigForMonitor = { export const defaultNodeConfigForMonitor = (): Partial => { return { + host: "", port: 443, requestPath: "/", }; @@ -217,7 +222,10 @@ export type WorkflowNodeConfigForNotify = { }; export const defaultNodeConfigForNotify = (): Partial => { - return {}; + return { + subject: "", + message: "", + }; }; export type WorkflowNodeConfigForCondition = { diff --git a/ui/src/global.css b/ui/src/global.css index 0cf2aee9c..e0a5027f7 100644 --- a/ui/src/global.css +++ b/ui/src/global.css @@ -58,4 +58,15 @@ svg.icon { line-height: 1; } + + /* Fix antd drawer title overflow */ + .ant-drawer .ant-drawer-title { + overflow: hidden; + } +} + +@layer components { + .container { + @apply mx-auto max-w-320; + } } diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 0fef135ef..a9fbe7aaf 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -1,6 +1,6 @@ { "access.page.title": "Credentials", - "access.page.subtitle": "Credentials store authentication information (username and password, API key, OAuth tokens, etc.) to connect with specific third-party apps and services.", + "access.page.subtitle": "Credentials store authentication information (username and password, API key, tokens, etc.) to connect with specific third-party apps and services.", "access.nodata": "No credentials", "access.nodata.title": "No credentials", @@ -40,7 +40,7 @@ "access.form.name.placeholder": "Please enter credential name", "access.form.provider.label": "Provider", "access.form.provider.placeholder": "Please select a provider", - "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", + "access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.
Hosting provider: The provider that hosts your servers or cloud services for deploying certificates.", "access.form.provider.search.placeholder": "Search provider ...", "access.form.1panel_server_url.label": "1Panel server URL", "access.form.1panel_server_url.placeholder": "Please enter 1Panel server URL", @@ -146,7 +146,7 @@ "access.form.cloudflare_dns_api_token.tooltip": "For more information, see https://developers.cloudflare.com/fundamentals/api/get-started/create-token/", "access.form.cloudflare_zone_api_token.label": "Cloudflare Zone API token (Optional)", "access.form.cloudflare_zone_api_token.placeholder": "Please enter Cloudflare Zone API token", - "access.form.cloudflare_zone_api_token.tooltip": "For more information, see https://developers.cloudflare.com/fundamentals/api/get-started/create-token/

Only required when you scope the DNS API token to specific zones. PLease scope the Zone API token to all zones with Zone/Zone/Read permission.", + "access.form.cloudflare_zone_api_token.tooltip": "For more information, see https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
Only required when you scope the DNS API token to specific zones. PLease scope the Zone API token to all zones with Zone/Zone/Read permission.", "access.form.cloudns_auth_id.label": "ClouDNS API user ID", "access.form.cloudns_auth_id.placeholder": "Please enter ClouDNS API user ID", "access.form.cloudns_auth_id.tooltip": "For more information, see https://www.cloudns.net/wiki/article/42/", @@ -293,7 +293,7 @@ "access.form.jdcloud_access_key_secret.tooltip": "For more information, see https://docs.jdcloud.com/en/account-management/accesskey-management", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file", - "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", + "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
Leave it blank to use the Pod's ServiceAccount.", "access.form.kong_server_url.label": "Kong admin API server URL", "access.form.kong_server_url.placeholder": "Please enter Kong admin API server URL", "access.form.kong_api_token.label": "Kong admin API token (Optional)", @@ -492,14 +492,14 @@ "access.form.webhook_headers.label": "Webhook request headers (Optional)", "access.form.webhook_headers.placeholder": "Please enter Webhook request headers", "access.form.webhook_headers.errmsg.invalid": "Please enter a valid request headers", - "access.form.webhook_headers.tooltip": "Format:
key1: val2
key2: val2


Example:
Content-Type: application/json
User-Agent: certimate
", + "access.form.webhook_headers.tooltip": "Example:
Content-Type: application/json
User-Agent: certimate
", "access.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", "access.form.webhook_data_for_deployment.label": "Webhook data for deployment (Optional)", "access.form.webhook_data_for_deployment.placeholder": "Please enter the default Webhook data (can be overrided in the workflows)", - "access.form.webhook_data_for_deployment.guide": "Tips: The Webhook data should be in JSON format.

The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
  1. ${DOMAIN}: The primary domain of the certificate (CommonName).
  2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
  3. ${CERTIFICATE}: The PEM format content of the certificate file.
  4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
  5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
  6. ${PRIVATE_KEY}: The PEM format content of the private key file.

When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
  1. application/json (default).
  2. application/x-www-form-urlencoded: Nested data is not supported.
  3. multipart/form-data: Nested data is not supported.
  4. ", + "access.form.webhook_data_for_deployment.guide": "The Webhook data should be in JSON format.

    The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
    1. ${DOMAIN}: The primary domain of the certificate (CommonName).
    2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
    3. ${CERTIFICATE}: The PEM format content of the certificate file.
    4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
    5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
    6. ${PRIVATE_KEY}: The PEM format content of the private key file.

    When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
    1. application/json (default).
    2. application/x-www-form-urlencoded: Nested data is not supported.
    3. multipart/form-data: Nested data is not supported.
    4. ", "access.form.webhook_data_for_notification.label": "Webhook data for notification (Optional)", "access.form.webhook_data_for_notification.placeholder": "Please enter the default Webhook data (can be overrided in the workflows)", - "access.form.webhook_data_for_notification.guide": "Tips: The Webhook data should be in JSON format.

      The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
      1. ${SUBJECT}: The subject of notification.
      2. ${MESSAGE}: The message of notification.

      When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
      1. application/json (default).
      2. application/x-www-form-urlencoded: Nested data is not supported.
      3. multipart/form-data: Nested data is not supported.
      4. ", + "access.form.webhook_data_for_notification.guide": "The Webhook data should be in JSON format.

        The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
        1. ${SUBJECT}: The subject of notification.
        2. ${MESSAGE}: The message of notification.

        When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
        1. application/json (default).
        2. application/x-www-form-urlencoded: Nested data is not supported.
        3. multipart/form-data: Nested data is not supported.
        4. ", "access.form.webhook_preset_data.button": "Use preset template", "access.form.webhook_preset_data.option.bark.label": "Bark", "access.form.webhook_preset_data.option.gotify.label": "Gotify", diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 728462478..3981b4daa 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -35,33 +35,15 @@ "settings.sslprovider.ca.tips": "You can use different CAs for each workflow as well. If you want to do this, please visit the credentials page.
          The certificate validity lifetime, certificate algorithm, domain names count, and support for wildcard domain names are allowed may vary among different providers. After switching service providers, please check whether the configuration of the workflows needs to be adjusted.", "settings.sslprovider.form.provider.label": "ACME CA provider", "settings.sslprovider.form.letsencryptstaging_alert": "The staging environment can reduce the chance of your running up against rate limits.

          Learn more:
          https://letsencrypt.org/docs/staging-environment/", - "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", - "settings.sslprovider.form.zerossl_eab_kid.placeholder": "Please enter EAB KID", - "settings.sslprovider.form.zerossl_eab_kid.tooltip": "For more information, see https://zerossl.com/documentation/acme/", - "settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC key", - "settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "Please enter EAB HMAC key", - "settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "For more information, see https://zerossl.com/documentation/acme/", - "settings.sslprovider.form.googletrustservices_eab_kid.label": "EAB KID", - "settings.sslprovider.form.googletrustservices_eab_kid.placeholder": "Please enter EAB KID", - "settings.sslprovider.form.googletrustservices_eab_kid.tooltip": "For more information, see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.label": "EAB HMAC key", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder": "Please enter EAB HMAC key", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.tooltip": "For more information, see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", - "settings.sslprovider.form.sslcom_eab_kid.label": "EAB KID", - "settings.sslprovider.form.sslcom_eab_kid.placeholder": "Please enter EAB KID", - "settings.sslprovider.form.sslcom_eab_kid.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", - "settings.sslprovider.form.sslcom_eab_hmac_key.label": "EAB HMAC key", - "settings.sslprovider.form.sslcom_eab_hmac_key.placeholder": "Please enter EAB HMAC key", - "settings.sslprovider.form.sslcom_eab_hmac_key.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", "settings.persistence.tab": "Persistence", "settings.persistence.title": "Data retention", "settings.persistence.form.workflow_runs_max_days.label": "Max days retention of workflow history runs", "settings.persistence.form.workflow_runs_max_days.placeholder": "Please enter the maximum retention days of workflow history runs", "settings.persistence.form.workflow_runs_max_days.unit": "days", - "settings.persistence.form.workflow_runs_max_days.extra": "Set to 0 to disable cleanup workflow history runs. It is recommended to set it to 180 days or more.", + "settings.persistence.form.workflow_runs_max_days.help": "Set to 0 to disable cleanup workflow history runs. It is recommended to set it to 180 days or more.", "settings.persistence.form.expired_certificates_max_days.label": "Max days retention of expired certificates", "settings.persistence.form.expired_certificates_max_days.placeholder": "Please enter the maximum retention days of expired certificates", "settings.persistence.form.expired_certificates_max_days.unit": "days", - "settings.persistence.form.expired_certificates_max_days.extra": "Set to 0 to disable cleanup expired certificates." + "settings.persistence.form.expired_certificates_max_days.help": "Set to 0 to disable cleanup expired certificates." } diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index 426a58f09..e991aee7b 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -21,16 +21,9 @@ "workflow.action.enable.button": "Activate", "workflow.action.enable.errmsg.uncompleted": "Please complete the orchestration and publish the changes first", "workflow.action.disable.button": "Deactivate", - "workflow.action.release.button": "Release", - "workflow.action.release.modal.title": "Release changes", - "workflow.action.release.modal.content": "Are you sure to release your changes?", - "workflow.action.release.errmsg.uncompleted": "Please complete the orchestration first", - "workflow.action.discard.button": "Discard", - "workflow.action.discard.modal.title": "Discard changes", - "workflow.action.discard.modal.content": "Are you sure to discard your changes?", "workflow.action.run.button": "Run", "workflow.action.run.modal.title": "Run workflow", - "workflow.action.run.modal.content": "You have unreleased changes. Do you really want to run this workflow based on the latest released version?", + "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", "workflow.props.name": "Name", @@ -55,18 +48,39 @@ "workflow.new.templates.template.certtest.description": "A monitoring operating procedure that includes monitoring, and notification steps.", "workflow.new.templates.template.empty.title": "Empty template", "workflow.new.templates.template.empty.description": "Customize all the contents of the workflow from the beginning.", - "workflow.new.modal.title": "Create workflow", - "workflow.new.modal.form.name.label": "Name", - "workflow.new.modal.form.name.placeholder": "Please enter workflow name", - "workflow.new.modal.form.description.label": "Description (Optional)", - "workflow.new.modal.form.description.placeholder": "Please enter workflow description", + "workflow.new.templates.default_name": "Untitled workflow", + "workflow.new.templates.default_description": "Created at {{date}}", - "workflow.detail.baseinfo.modal.title": "Workflow base information", - "workflow.detail.baseinfo.form.name.label": "Name", - "workflow.detail.baseinfo.form.name.placeholder": "Please enter workflow name", - "workflow.detail.baseinfo.form.description.label": "Description (Optional)", - "workflow.detail.baseinfo.form.description.placeholder": "Please enter workflow description", - "workflow.detail.orchestration.tab": "Orchestration", - "workflow.detail.orchestration.draft.alert": "The orchestration is not released yet.", + "workflow.detail.baseinfo.name.placeholder": "Please enter workflow name", + "workflow.detail.baseinfo.description.placeholder": "Please enter workflow description", + "workflow.detail.design_legacy.tab": "Orchestration (legacy)", + "workflow.detail.design.tab": "Orchestration", + "workflow.detail.design.editor.placeholder": "Please configure", + "workflow.detail.design.editor.add_node": "Add node", + "workflow.detail.design.editor.rename_node": "Rename node", + "workflow.detail.design.editor.duplicate_node": "Duplicate node", + "workflow.detail.design.editor.remove_node": "Delete node", + "workflow.detail.design.editor.add_branch": "Add branch", + "workflow.detail.design.editor.rename_branch": "Rename branch", + "workflow.detail.design.editor.duplicate_branch": "Duplicate branch", + "workflow.detail.design.editor.remove_branch": "Delete branch", + "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.minimap": "Minimap", + "workflow.detail.design.toolbar.hand_mode": "Hand mode", + "workflow.detail.design.toolbar.pointer_mode": "Pointer mode", + "workflow.detail.design.drawer.node_id.label": "Node ID: ", + "workflow.detail.design.action.publish.button": "Publish", + "workflow.detail.design.action.publish.modal.title": "Publish changes", + "workflow.detail.design.action.publish.modal.content": "Are you sure to publish your changes?", + "workflow.detail.design.action.rollback.button": "Rollback", + "workflow.detail.design.action.rollback.modal.title": "Rollback changes", + "workflow.detail.design.action.rollback.modal.content": "Are you sure to rollback your changes to the last published version?", + "workflow.detail.design.uncompleted_design.alert": "The orchestration is not ready. Please check if there are any nodes not configured.", + "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..55410a27a 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -9,10 +9,10 @@ "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_anchor.parameters.tab": "Parameters", "workflow_node.start.form.trigger.label": "Trigger", "workflow_node.start.form.trigger.placeholder": "Please select trigger", "workflow_node.start.form.trigger.option.scheduled.label": "Scheduled", @@ -21,11 +21,19 @@ "workflow_node.start.form.trigger_cron.placeholder": "Please enter cron expression", "workflow_node.start.form.trigger_cron.errmsg.invalid": "Please enter a valid cron expression", "workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments. Time zone is based on the server.", - "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.start.form.trigger_cron.help": "Expected execution time for the last 5 times:", + "workflow_node.start.form.trigger_cron.guide": "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_anchor.parameters.tab": "Parameters", + "workflow_node.apply.form_anchor.certificate.tab": "CA Config", + "workflow_node.apply.form_anchor.certificate.title": "Certificate settings", + "workflow_node.apply.form_anchor.advanced.tab": "Advanced", + "workflow_node.apply.form_anchor.advanced.title": "Advanced settings", + "workflow_node.apply.form_anchor.strategy.tab": "Strategy", + "workflow_node.apply.form_anchor.strategy.title": "Strategy settings", "workflow_node.apply.form.domains.label": "Domains", "workflow_node.apply.form.domains.placeholder": "Please enter domains (separated by semicolons)", "workflow_node.apply.form.domains.tooltip": "Wildcard domain: *.example.com", @@ -60,7 +68,6 @@ "workflow_node.apply.form.tencentcloud_eo_zone_id.label": "Tencent Cloud EdgeOne zone ID", "workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "Please enter Tencent Cloud EdgeOne zone ID", "workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", - "workflow_node.apply.form.certificate_config.label": "Certificate settings", "workflow_node.apply.form.ca_provider.label": "Certificate authority (Optional)", "workflow_node.apply.form.ca_provider.placeholder": "Please select a certificate authority", "workflow_node.apply.form.ca_provider.tooltip": "Used to issue SSL certificates.", @@ -73,7 +80,6 @@ "workflow_node.apply.form.acme_profile.label": "ACME certificate profile (Optional)", "workflow_node.apply.form.acme_profile.placeholder": "Please enter ACME certificate profile", "workflow_node.apply.form.acme_profile.tooltip": "It determines the ACME profile which will be used to affect issuance of the certificate requested. If you don't understand this option, just keep it by default.Learn more.", - "workflow_node.apply.form.advanced_config.label": "Advanced settings", "workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)", "workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)", "workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 challenge. If you don't understand this option, just keep it by default. Learn more.", @@ -86,16 +92,15 @@ "workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation checks timeout (Optional)", "workflow_node.apply.form.dns_propagation_timeout.placeholder": "Please enter DNS propagation checks timeout", "workflow_node.apply.form.dns_propagation_timeout.unit": "seconds", - "workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the timeout for DNS propagation checks during ACME DNS-01 challenge. If you don't understand this option, just keep it by default.

          Leave it blank to use the default value provided by the provider.", + "workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the timeout for DNS propagation checks during ACME DNS-01 challenge. If you don't understand this option, just keep it by default.
          Leave it blank to use the default value provided by the provider.", "workflow_node.apply.form.dns_ttl.label": "DNS TTL (Optional)", "workflow_node.apply.form.dns_ttl.placeholder": "Please enter DNS TTL", "workflow_node.apply.form.dns_ttl.unit": "seconds", - "workflow_node.apply.form.dns_ttl.tooltip": "It determines the TTL for DNS record during ACME DNS-01 challenge. If you don't understand this option, just keep it by default.

          Leave it blank to use the default value provided by the provider.", + "workflow_node.apply.form.dns_ttl.tooltip": "It determines the TTL for DNS record during ACME DNS-01 challenge. If you don't understand this option, just keep it by default.
          Leave it blank to use the default value provided by the provider.", "workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following", "workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 challenge. If you don't understand this option, just keep it by default. Learn more.", "workflow_node.apply.form.disable_ari.label": "Disable ARI", "workflow_node.apply.form.disable_ari.tooltip": "It determines whether to disable ARI (ACME Renewal Information). If you don't understand this option, just keep it by default. Learn more.", - "workflow_node.apply.form.strategy_config.label": "Strategy settings", "workflow_node.apply.form.skip_before_expiry_days.label": "Repeated application", "workflow_node.apply.form.skip_before_expiry_days.placeholder": "Please enter renewal interval", "workflow_node.apply.form.skip_before_expiry_days.prefix": "If the last certificate expiration time exceeds", @@ -104,7 +109,9 @@ "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_anchor.parameters.tab": "Parameters", "workflow_node.upload.form.domains.label": "Domains", "workflow_node.upload.form.domains.placholder": "Please select certificate file", "workflow_node.upload.form.certificate.label": "Certificate (PEM format)", @@ -112,8 +119,28 @@ "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_anchor.parameters.tab": "Parameters", + "workflow_node.monitor.form.guide": "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_anchor.parameters.tab": "Parameters", + "workflow_node.deploy.form_anchor.deployment.tab": "Deployment", + "workflow_node.deploy.form_anchor.deployment.title": "Deployment settings", + "workflow_node.deploy.form_anchor.strategy.tab": "Strategy", + "workflow_node.deploy.form_anchor.strategy.title": "Strategy settings", "workflow_node.deploy.form.provider.label": "Deploy target", "workflow_node.deploy.form.provider.placeholder": "Please select deploy target", "workflow_node.deploy.form.provider.search.placeholder": "Search deploy target ...", @@ -124,7 +151,6 @@ "workflow_node.deploy.form.certificate.label": "Certificate", "workflow_node.deploy.form.certificate.placeholder": "Please select certificate", "workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.", - "workflow_node.deploy.form.params_config.label": "Parameter settings", "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel node name (Optional)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name", @@ -181,13 +207,13 @@ "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.label": "Alibaba Cloud resource IDs", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.placeholder": "Please enter Alibaba Cloud resource IDs (separated by semicolons)", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid": "Please enter a valid Alibaba Cloud resource ID", - "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources

          Supports Alibaba Cloud products only.", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources
          Supports Alibaba Cloud products only.", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title": "Change Alibaba Cloud resource IDs", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.placeholder": "Please enter Alibaba Cloud resouce ID", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.label": "Alibaba Cloud contact IDs (Optional)", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.placeholder": "Please enter Alibaba Cloud contact IDs (separated by semicolons)", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid": "Please enter a valid Alibaba Cloud contact ID", - "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

          Leave it blank to use the first system contact.", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact
          Leave it blank to use the first system contact.", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "Change Alibaba Cloud contact IDs", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "Please enter Alibaba Cloud contact ID", "workflow_node.deploy.form.aliyun_clb_region.label": "Alibaba Cloud CLB region", @@ -547,8 +573,8 @@ "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "You can find it on LeCDN dashboard.", "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN user ID (Optional)", "workflow_node.deploy.form.lecdn_client_id.placeholder": "Please enter LeCDN user ID", - "workflow_node.deploy.form.lecdn_client_id.tooltip": "You can find it on LeCDN dashboard.

          Required when using administrator's authorization. It Must be the same as the user to which the certificate belongs.", - "workflow_node.deploy.form.local.guide": "Tips: If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "You can find it on LeCDN dashboard.
          Required when using administrator's authorization. It Must be the same as the user to which the certificate belongs.", + "workflow_node.deploy.form.local.guide": "If you are running Certimate in Docker, the \"Local\" refers to the container rather than the host.", "workflow_node.deploy.form.local_format.label": "File format", "workflow_node.deploy.form.local_format.placeholder": "Please select file format", "workflow_node.deploy.form.local_format.option.pem.label": "PEM (*.pem, *.crt, *.key)", @@ -671,7 +697,7 @@ "workflow_node.deploy.form.ssh_preset_scripts.option.ps_binding_netsh.label": "PowerShell - Binding netsh", "workflow_node.deploy.form.ssh_preset_scripts.option.ps_binding_rdp.label": "PowerShell - Binding RDP", "workflow_node.deploy.form.ssh_use_scp.label": "Fallback to use SCP", - "workflow_node.deploy.form.ssh_use_scp.tooltip": "If the remote server does not support SFTP, please enable this option to fallback to SCP.", + "workflow_node.deploy.form.ssh_use_scp.tooltip": "If the remote server does not support SFTP, please check this option to fallback to SCP.", "workflow_node.deploy.form.tencentcloud_cdn_endpoint.label": "Tencent Cloud CDN API endpoint (Optional)", "workflow_node.deploy.form.tencentcloud_cdn_endpoint.placeholder": "Please enter Tencent Cloud CDN API endpoint (e.g. cdn.intl.tencentcloudapi.com)", "workflow_node.deploy.form.tencentcloud_cdn_endpoint.tooltip": "
          • cdn.intl.tencentcloudapi.com for Tencent Cloud International
          • cdn.tencentcloudapi.com for Tencent Cloud in China
          ", @@ -698,7 +724,7 @@ "workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip": "For more information, see https://console.tencentcloud.com/clb", "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "Tencent Cloud CLB SNI domain (Optional)", "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "Please enter Tencent Cloud CLB SNI domain name", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "For more information, see https://console.tencentcloud.com/clb

          It is optional. If you want to deploy multiple certificates on the same CLB listener, you can fill in this field.", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "For more information, see https://console.tencentcloud.com/clb
          It is optional. If you want to deploy multiple certificates on the same CLB listener, you can fill in this field.", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "Tencent Cloud CLB domain", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "Please enter Tencent Cloud CLB domain name", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "For more information, see https://console.tencentcloud.com/clb", @@ -829,7 +855,7 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "UCloud US3 custom domain", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 bucket custom domain name", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see https://console.ucloud-global.com/ufile", - "workflow_node.deploy.form.unicloud_webhost.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the uniCloud, please create a GitHub Issue.", + "workflow_node.deploy.form.unicloud_webhost.guide": "This uses webpage simulator login and does not guarantee stability. If there are any changes to the uniCloud, please create a GitHub Issue.", "workflow_node.deploy.form.unicloud_webhost_space_provider.label": "uniCloud space provider", "workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder": "Please select uniCloud space provider", "workflow_node.deploy.form.unicloud_webhost_space_provider.option.aliyun.label": "Alibaba Cloud", @@ -839,11 +865,11 @@ "workflow_node.deploy.form.unicloud_webhost_space_id.tooltip": "For more information, see https://doc.dcloud.net.cn/uniCloud/concepts/space.html", "workflow_node.deploy.form.unicloud_webhost_domain.label": "uniCloud Web host domain", "workflow_node.deploy.form.unicloud_webhost_domain.placeholder": "uniCloud Web host domain", - "workflow_node.deploy.form.upyun_cdn.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", + "workflow_node.deploy.form.upyun_cdn.guide": "This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "For more information, see https://console.upyun.com/services/cdn/", - "workflow_node.deploy.form.upyun_file.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", + "workflow_node.deploy.form.upyun_file.guide": "This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", "workflow_node.deploy.form.upyun_file_domain.label": "UPYUN USS custom domain", "workflow_node.deploy.form.upyun_file_domain.placeholder": "Please enter UPYUN USS bucket custom domain name", "workflow_node.deploy.form.upyun_file_domain.tooltip": "For more information, see https://console.upyun.com/services/file/", @@ -931,27 +957,18 @@ "workflow_node.deploy.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the credential.", "workflow_node.deploy.form.webhook_data.guide": "
          Supported variables:
          1. ${DOMAIN}: The primary domain of the certificate (CommonName).
          2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
          3. ${CERTIFICATE}: The PEM format content of the certificate file.
          4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
          5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
          6. ${PRIVATE_KEY}: The PEM format content of the private key file.

          Please visit the credentials page for addtional notes.", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", - "workflow_node.deploy.form.strategy_config.label": "Strategy settings", "workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ", "workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.", "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_anchor.parameters.tab": "Parameters", + "workflow_node.notify.form_anchor.strategy.tab": "Strategy", + "workflow_node.notify.form_anchor.strategy.title": "Strategy settings", "workflow_node.notify.form.subject.label": "Subject", "workflow_node.notify.form.subject.placeholder": "Please enter subject", "workflow_node.notify.form.message.label": "Message", @@ -982,23 +999,61 @@ "workflow_node.notify.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the credential.", "workflow_node.notify.form.webhook_data.guide": "
          Supported variables:
          1. ${SUBJECT}: The subject of notification.
          2. ${MESSAGE}: The message of notification.

          Please visit the credentials page for addtional notes.", "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", - "workflow_node.notify.form.strategy_config.label": "Strategy settings", "workflow_node.notify.form.skip_on_all_prev_skipped.label": "Silent behavior", "workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "If all the previous nodes were skipped, ", "workflow_node.notify.form.skip_on_all_prev_skipped.suffix": " to notify.", "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.default_name": "Branch", + "workflow_node.condition.label": "Parallel/Conditional 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": "Parallel", "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 ...", + + "workflow_node.branch_block.label": "Branch", + "workflow_node.branch_block.default_name": "Branch", + "workflow_node.branch_block.state.no": "Enter Unconditionally", + "workflow_node.branch_block.state.or": "Enter when any condition is met", + "workflow_node.branch_block.state.and": "Enter when all conditions are met", + "workflow_node.branch_block.form_anchor.parameters.tab": "Parameters", + "workflow_node.branch_block.form.expression.label": "Conditions to enter the branch", + "workflow_node.branch_block.form.expression.errmsg.invalid": "Please enter a valid expression", + "workflow_node.branch_block.form.expression.logical_operator.errmsg": "Please select logical operator of conditions", + "workflow_node.branch_block.form.expression.logical_operator.option.and.label": "Meeting all of the conditions (AND)", + "workflow_node.branch_block.form.expression.logical_operator.option.or.label": "Meeting any of the conditions (OR)", + "workflow_node.branch_block.form.expression.variable.placeholder": "Please select", + "workflow_node.branch_block.form.expression.variable.errmsg": "Please select variable", + "workflow_node.branch_block.form.expression.operator.placeholder": "Please select", + "workflow_node.branch_block.form.expression.operator.errmsg": "Please select operator", + "workflow_node.branch_block.form.expression.operator.option.eq.label": "equal to", + "workflow_node.branch_block.form.expression.operator.option.eq.alias_is_label": "is", + "workflow_node.branch_block.form.expression.operator.option.neq.label": "not equal to", + "workflow_node.branch_block.form.expression.operator.option.neq.alias_not_label": "is not", + "workflow_node.branch_block.form.expression.operator.option.gt.label": "greater than", + "workflow_node.branch_block.form.expression.operator.option.gte.label": "greater than or equal to", + "workflow_node.branch_block.form.expression.operator.option.lt.label": "less than", + "workflow_node.branch_block.form.expression.operator.option.lte.label": "less than or equal to", + "workflow_node.branch_block.form.expression.value.placeholder": "Please enter", + "workflow_node.branch_block.form.expression.value.errmsg": "Please enter value", + "workflow_node.branch_block.form.expression.value.option.true.label": "True", + "workflow_node.branch_block.form.expression.value.option.false.label": "False", + "workflow_node.branch_block.form.expression.add_condition.button": "Add condition", + + "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", + + "@deprecated": "TODO: remove in v0.4.x below", + "workflow_node.branch.label": "Parallel/Conditional branch", + "workflow_node.branch.default_name": "Branch", "workflow_node.condition.form.expression.label": "Conditions to enter the branch", "workflow_node.condition.form.expression.logical_operator.errmsg": "Please select logical operator of conditions", "workflow_node.condition.form.expression.logical_operator.option.and.label": "Meeting all of the conditions (AND)", @@ -1020,13 +1075,10 @@ "workflow_node.condition.form.expression.value.option.true.label": "True", "workflow_node.condition.form.expression.value.option.false.label": "False", "workflow_node.condition.form.expression.add_condition.button": "Add condition", - "workflow_node.execute_result_branch.label": "Execution result branch", "workflow_node.execute_result_branch.default_name": "Branch", - "workflow_node.execute_success.label": "If the previous node succeeded ...", "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" } diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 8bc462f77..3e4c71958 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -1,6 +1,6 @@ { "access.page.title": "授权凭据", - "access.page.subtitle": "授权凭据中存储有用于访问特定第三方应用程序或服务的身份验证信息(如账号密码、API 密钥、OAuth 令牌等)。", + "access.page.subtitle": "授权凭据中存储有用于访问特定第三方应用程序或服务的身份验证信息(如账号密码、接口密钥、API 令牌等)。", "access.nodata.title": "暂无授权", "access.nodata.description": "当前未找到授权信息。请先创建。", @@ -39,7 +39,7 @@ "access.form.name.placeholder": "请输入授权名称", "access.form.provider.label": "提供商", "access.form.provider.placeholder": "请选择提供商", - "access.form.provider.tooltip": "提供商分为两种类型:
          【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理你的域名解析记录。
          【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。

          该字段保存后不可修改。", + "access.form.provider.tooltip": "提供商分为两种类型:
          【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理你的域名解析记录。
          【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。", "access.form.provider.search.placeholder": "搜索提供商……", "access.form.common_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", "access.form.common_allow_insecure_conns.switch.on": "允许", @@ -145,7 +145,7 @@ "access.form.cloudflare_dns_api_token.tooltip": "这是什么?请参阅 https://developers.cloudflare.com/fundamentals/api/get-started/create-token/", "access.form.cloudflare_zone_api_token.label": "Cloudflare Zone API 令牌(可选)", "access.form.cloudflare_zone_api_token.placeholder": "请输入 Cloudflare Zone API 令牌", - "access.form.cloudflare_zone_api_token.tooltip": "这是什么?请参阅 https://developers.cloudflare.com/fundamentals/api/get-started/create-token/

          仅当你将 DNS API 令牌范围指定为特定域时需要填写,请将 Zone API 令牌范围指定为全部域,并分配 Zone/Zone/Read 权限。", + "access.form.cloudflare_zone_api_token.tooltip": "这是什么?请参阅 https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
          仅当你将 DNS API 令牌范围指定为特定域时需要填写,请将 Zone API 令牌范围指定为全部域,并分配 Zone/Zone/Read 权限。", "access.form.cloudns_auth_id.label": "ClouDNS API 用户 ID", "access.form.cloudns_auth_id.placeholder": "请输入 ClouDNS API 用户 ID", "access.form.cloudns_auth_id.tooltip": "这是什么?请参阅 https://www.cloudns.net/wiki/article/42/", @@ -292,7 +292,7 @@ "access.form.jdcloud_access_key_secret.tooltip": "这是什么?请参阅 https://docs.jdcloud.com/cn/account-management/accesskey-management", "access.form.k8s_kubeconfig.label": "KubeConfig", "access.form.k8s_kubeconfig.placeholder": "请输入 KubeConfig 文件内容", - "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/

          为空时,将使用 Pod 的 ServiceAccount 作为凭证。", + "access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/
          为空时,将使用 Pod 的 ServiceAccount 作为凭证。", "access.form.kong_server_url.label": "Kong Admin API 服务地址", "access.form.kong_server_url.placeholder": "请输入 Kong Admin API 服务地址", "access.form.kong_api_token.label": "Kong Admin API Token(可选)", @@ -459,10 +459,10 @@ "access.form.unicloud_password.placeholder": "请输入 uniCloud 控制台密码", "access.form.upyun_username.label": "又拍云子账号用户名", "access.form.upyun_username.placeholder": "请输入又拍云子账号用户名", - "access.form.upyun_username.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/

          请关闭该账号的二次登录验证。", + "access.form.upyun_username.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/
          请关闭该账号的二次登录验证。", "access.form.upyun_password.label": "又拍云子账号密码", "access.form.upyun_password.placeholder": "请输入又拍云子账号密码", - "access.form.upyun_password.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/

          请关闭该账号的二次登录验证。", + "access.form.upyun_password.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/
          请关闭该账号的二次登录验证。", "access.form.vercel_api_access_token.label": "Vercel API Access Token", "access.form.vercel_api_access_token.placeholder": "请输入 Vercel API Access Token", "access.form.vercel_api_access_token.tooltip": "这是什么?请参阅 https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token", @@ -491,14 +491,14 @@ "access.form.webhook_headers.label": "Webhook 请求标头(可选)", "access.form.webhook_headers.placeholder": "请输入 Webhook 请求标头", "access.form.webhook_headers.errmsg.invalid": "请输入有效的请求标头", - "access.form.webhook_headers.tooltip": "格式:
          key1: val2
          key2: val2


          示例:
          Content-Type: application/json
          User-Agent: certimate
          ", + "access.form.webhook_headers.tooltip": "示例:
          Content-Type: application/json
          User-Agent: certimate
          ", "access.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "access.form.webhook_data_for_deployment.label": "Webhook 部署证书回调数据(可选)", "access.form.webhook_data_for_deployment.placeholder": "请输入默认的 Webhook 回调数据(可在工作流中单独设置)", - "access.form.webhook_data_for_deployment.guide": "小贴士:回调数据是一个 JSON 格式的数据。

          其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
          1. ${DOMAIN}:证书的主域名(即 CommonName)。
          2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
          3. ${CERTIFICATE}:证书文件 PEM 格式内容。
          4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
          5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
          6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

          当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
          1. application/json(默认)。
          2. application/x-www-form-urlencoded:不支持嵌套数据。
          3. multipart/form-data:不支持嵌套数据。
          4. ", + "access.form.webhook_data_for_deployment.guide": "回调数据是一个 JSON 格式的数据。

            其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
            1. ${DOMAIN}:证书的主域名(即 CommonName)。
            2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
            3. ${CERTIFICATE}:证书文件 PEM 格式内容。
            4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
            5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
            6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

            当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
            1. application/json(默认)。
            2. application/x-www-form-urlencoded:不支持嵌套数据。
            3. multipart/form-data:不支持嵌套数据。
            4. ", "access.form.webhook_data_for_notification.label": "Webhook 推送通知回调数据(可选)", "access.form.webhook_data_for_notification.placeholder": "请输入默认的 Webhook 回调数据(可在工作流中单独设置)", - "access.form.webhook_data_for_notification.guide": "小贴士:回调数据是一个 JSON 格式的数据。

              其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
              1. ${SUBJECT}:通知主题。
              2. ${MESSAGE}:通知内容。

              当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
              1. application/json(默认)。
              2. application/x-www-form-urlencoded:不支持嵌套数据。
              3. multipart/form-data:不支持嵌套数据。
              4. ", + "access.form.webhook_data_for_notification.guide": "回调数据是一个 JSON 格式的数据。

                其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
                1. ${SUBJECT}:通知主题。
                2. ${MESSAGE}:通知内容。

                当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
                1. application/json(默认)。
                2. application/x-www-form-urlencoded:不支持嵌套数据。
                3. multipart/form-data:不支持嵌套数据。
                4. ", "access.form.webhook_preset_data.button": "使用预设模板", "access.form.webhook_preset_data.option.bark.label": "Bark", "access.form.webhook_preset_data.option.gotify.label": "Gotify", diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json index 6ea7260a3..591642fa3 100644 --- a/ui/src/i18n/locales/zh/nls.certificate.json +++ b/ui/src/i18n/locales/zh/nls.certificate.json @@ -1,6 +1,6 @@ { "certificate.page.title": "SSL 证书", - "certificate.page.subtitle": "SSL 证书含有网站的公钥和网站标识以及其他相关信息。它们来自于工作流的执行输出。", + "certificate.page.subtitle": "SSL 证书含有网站的公钥和网站标识以及其他相关信息。它们来自于工作流的运行输出。", "certificate.nodata.title": "暂无证书", "certificate.nodata.description": "当前未找到任何认证。请先运行一个工作流以生成证书。", diff --git a/ui/src/i18n/locales/zh/nls.dashboard.json b/ui/src/i18n/locales/zh/nls.dashboard.json index 53ef62e72..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 022e87634..3b9261cf9 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -32,36 +32,18 @@ "settings.sslprovider.tab": "证书颁发机构", "settings.sslprovider.ca.title": "全局证书颁发机构", - "settings.sslprovider.ca.tips": "如果你希望为在每个工作流中使用不同的证书颁发机构,请前往授权凭据页面。
                  不同服务商所支持的证书有效期、证书算法、多域名数量上限、是否允许泛域名等可能不同,切换服务商后请注意检查已有工作流的配置是否需要调整。", + "settings.sslprovider.ca.tips": "如果你希望在每个工作流中选择不同的证书颁发机构,请前往授权凭据页面。
                  不同服务商所支持的证书有效期、证书算法、多域名数量上限、是否允许泛域名等可能不同,切换服务商后请注意检查已有工作流的配置是否需要调整。", "settings.sslprovider.form.provider.label": "证书颁发机构提供商", "settings.sslprovider.form.letsencryptstaging_alert": "测试环境比生产环境有更宽松的速率限制,可进行测试性部署。

                  点击下方链接了解更多:
                  https://letsencrypt.org/zh-cn/docs/staging-environment/", - "settings.sslprovider.form.zerossl_eab_kid.label": "EAB KID", - "settings.sslprovider.form.zerossl_eab_kid.placeholder": "请输入 EAB KID", - "settings.sslprovider.form.zerossl_eab_kid.tooltip": "这是什么?请参阅 https://zerossl.com/documentation/acme/", - "settings.sslprovider.form.zerossl_eab_hmac_key.label": "EAB HMAC Key", - "settings.sslprovider.form.zerossl_eab_hmac_key.placeholder": "请输入 EAB HMAC Key", - "settings.sslprovider.form.zerossl_eab_hmac_key.tooltip": "这是什么?请参阅 https://zerossl.com/documentation/acme/", - "settings.sslprovider.form.googletrustservices_eab_kid.label": "EAB KID", - "settings.sslprovider.form.googletrustservices_eab_kid.placeholder": "请输入 EAB KID", - "settings.sslprovider.form.googletrustservices_eab_kid.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.label": "EAB HMAC Key", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.placeholder": "请输入 EAB HMAC Key", - "settings.sslprovider.form.googletrustservices_eab_hmac_key.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial", - "settings.sslprovider.form.sslcom_eab_kid.label": "EAB KID", - "settings.sslprovider.form.sslcom_eab_kid.placeholder": "请输入 EAB KID", - "settings.sslprovider.form.sslcom_eab_kid.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", - "settings.sslprovider.form.sslcom_eab_hmac_key.label": "EAB HMAC Key", - "settings.sslprovider.form.sslcom_eab_hmac_key.placeholder": "请输入 EAB HMAC Key", - "settings.sslprovider.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", "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.workflow_runs_max_days.help": "设置为 0 表示永久保留,不会自动清理。建议设置为 180 天以上。", "settings.persistence.form.expired_certificates_max_days.label": "证书过期后保留天数", "settings.persistence.form.expired_certificates_max_days.placeholder": "请输入过期证书保留天数", "settings.persistence.form.expired_certificates_max_days.unit": "天", - "settings.persistence.form.expired_certificates_max_days.extra": "设置为 0 表示永久保留,不会自动清理。" + "settings.persistence.form.expired_certificates_max_days.help": "设置为 0 表示永久保留,不会自动清理。" } diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index ca74474b9..b28e7b81a 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": "当前未找到工作流。请先创建。", @@ -21,25 +21,18 @@ "workflow.action.enable.button": "启用", "workflow.action.enable.errmsg.uncompleted": "请先完成流程编排并发布更改", "workflow.action.disable.button": "停用", - "workflow.action.release.button": "发布更改", - "workflow.action.release.modal.title": "发布更改", - "workflow.action.release.modal.content": "确定要发布更改吗?", - "workflow.action.release.errmsg.uncompleted": "流程编排未完成,请检查是否有节点未配置", - "workflow.action.discard.button": "撤销更改", - "workflow.action.discard.modal.title": "撤销更改", - "workflow.action.discard.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.state": "启用", + "workflow.props.last_run_at": "最近运行时间", + "workflow.props.state": "是否启用", "workflow.props.state.filter.all": "全部", "workflow.props.state.filter.enabled": "启用", "workflow.props.state.filter.disabled": "未启用", @@ -55,18 +48,39 @@ "workflow.new.templates.template.certtest.description": "一个包含证书监控 + 消息通知步骤的工作流程。", "workflow.new.templates.template.empty.title": "空白模板", "workflow.new.templates.template.empty.description": "从零开始自定义工作流的任务内容。", - "workflow.new.modal.title": "新建工作流", - "workflow.new.modal.form.name.label": "名称", - "workflow.new.modal.form.name.placeholder": "请输入工作流名称", - "workflow.new.modal.form.description.label": "描述(可选)", - "workflow.new.modal.form.description.placeholder": "请输入工作流描述", + "workflow.new.templates.default_name": "未命名工作流", + "workflow.new.templates.default_description": "创建于 {{date}}", - "workflow.detail.baseinfo.modal.title": "编辑基本信息", - "workflow.detail.baseinfo.form.name.label": "名称", - "workflow.detail.baseinfo.form.name.placeholder": "请输入工作流名称", - "workflow.detail.baseinfo.form.description.label": "描述(可选)", - "workflow.detail.baseinfo.form.description.placeholder": "请输入工作流描述", - "workflow.detail.orchestration.tab": "流程编排", - "workflow.detail.orchestration.draft.alert": "当前编排有未发布的更改。", - "workflow.detail.runs.tab": "执行历史" + "workflow.detail.baseinfo.name.placeholder": "请输入工作流名称", + "workflow.detail.baseinfo.description.placeholder": "请输入工作流描述", + "workflow.detail.design_legacy.tab": "流程编排(旧版)", + "workflow.detail.design.tab": "流程编排", + "workflow.detail.design.editor.placeholder": "请完成配置", + "workflow.detail.design.editor.add_node": "添加节点", + "workflow.detail.design.editor.rename_node": "重命名", + "workflow.detail.design.editor.duplicate_node": "复制节点", + "workflow.detail.design.editor.remove_node": "删除节点", + "workflow.detail.design.editor.add_branch": "添加分支", + "workflow.detail.design.editor.rename_branch": "重命名", + "workflow.detail.design.editor.duplicate_branch": "复制分支", + "workflow.detail.design.editor.remove_branch": "删除分支", + "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.minimap": "小地图", + "workflow.detail.design.toolbar.hand_mode": "触摸模式", + "workflow.detail.design.toolbar.pointer_mode": "指针模式", + "workflow.detail.design.drawer.node_id.label": "节点 ID:", + "workflow.detail.design.action.publish.button": "发布更改", + "workflow.detail.design.action.publish.modal.title": "发布更改", + "workflow.detail.design.action.publish.modal.content": "确定要发布更改吗?", + "workflow.detail.design.action.rollback.button": "回退更改", + "workflow.detail.design.action.rollback.modal.title": "回退更改", + "workflow.detail.design.action.rollback.modal.content": "确定要回退到最近一次发布的版本吗?", + "workflow.detail.design.uncompleted_design.alert": "流程编排未就绪,请检查是否有节点未完成配置。", + "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..6b9cc896e 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -9,10 +9,10 @@ "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_anchor.parameters.tab": "参数设置", "workflow_node.start.form.trigger.label": "触发方式", "workflow_node.start.form.trigger.placeholder": "请选择触发方式", "workflow_node.start.form.trigger.option.scheduled.label": "定时触发", @@ -21,11 +21,19 @@ "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.guide": "小贴士:如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。也不要总是设置为每日零时,以免遭遇证书颁发机构的流量高峰。

                  参考链接:
                  1. Let’s Encrypt 速率限制
                  2. 为什么我的 Let’s Encrypt (ACME) 客户端启动时间应当随机?", + "workflow_node.start.form.trigger_cron.help": "预计最近 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_anchor.parameters.tab": "参数设置", + "workflow_node.apply.form_anchor.certificate.tab": "证书设置", + "workflow_node.apply.form_anchor.certificate.title": "证书设置", + "workflow_node.apply.form_anchor.advanced.tab": "高级设置", + "workflow_node.apply.form_anchor.advanced.title": "高级设置", + "workflow_node.apply.form_anchor.strategy.tab": "执行策略", + "workflow_node.apply.form_anchor.strategy.title": "执行策略", "workflow_node.apply.form.domains.label": "域名", "workflow_node.apply.form.domains.placeholder": "请输入域名(多个值请用半角分号隔开)", "workflow_node.apply.form.domains.tooltip": "泛域名表示形式为:*.example.com", @@ -60,7 +68,6 @@ "workflow_node.apply.form.tencentcloud_eo_zone_id.label": "腾讯云 EdgeOne 站点 ID", "workflow_node.apply.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID", "workflow_node.apply.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", - "workflow_node.apply.form.certificate_config.label": "证书设置", "workflow_node.apply.form.ca_provider.label": "证书颁发机构(可选)", "workflow_node.apply.form.ca_provider.placeholder": "请选择证书颁发机构", "workflow_node.apply.form.ca_provider.button": "设置", @@ -72,7 +79,6 @@ "workflow_node.apply.form.acme_profile.label": "ACME 证书配置(可选)", "workflow_node.apply.form.acme_profile.placeholder": "请输入 ACME 证书配置", "workflow_node.apply.form.acme_profile.tooltip": "表示证书颁发时使用的 ACME 证书配置。如果你不了解该选项的用途,保持默认即可。点此了解更多。", - "workflow_node.apply.form.advanced_config.label": "高级设置", "workflow_node.apply.form.nameservers.label": "DNS 递归服务器(可选)", "workflow_node.apply.form.nameservers.placeholder": "请输入 DNS 递归服务器(多个值请用半角分号隔开)", "workflow_node.apply.form.nameservers.tooltip": "表示在 ACME DNS-01 质询时使用自定义的 DNS 递归服务器。如果你不了解该选项的用途,保持默认即可。点此了解更多。", @@ -85,25 +91,26 @@ "workflow_node.apply.form.dns_propagation_timeout.label": "DNS 传播检查超时时间(可选)", "workflow_node.apply.form.dns_propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间", "workflow_node.apply.form.dns_propagation_timeout.unit": "秒", - "workflow_node.apply.form.dns_propagation_timeout.tooltip": "表示在 ACME DNS-01 质询时 DNS 传播检查的超时时间。如果你不了解此选项的用途,保持默认即可。

                  不填写时,将使用提供商提供的默认值。", + "workflow_node.apply.form.dns_propagation_timeout.tooltip": "表示在 ACME DNS-01 质询时 DNS 传播检查的超时时间。如果你不了解此选项的用途,保持默认即可。
                  不填写时,将使用提供商提供的默认值。", "workflow_node.apply.form.dns_ttl.label": "DNS 解析记录 TTL(可选)", "workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析记录 TTL", "workflow_node.apply.form.dns_ttl.unit": "秒", - "workflow_node.apply.form.dns_ttl.tooltip": "表示在 ACME DNS-01 质询时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。

                  不填写时,将使用提供商提供的默认值。", + "workflow_node.apply.form.dns_ttl.tooltip": "表示在 ACME DNS-01 质询时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。
                  不填写时,将使用提供商提供的默认值。", "workflow_node.apply.form.disable_follow_cname.label": "阻止 CNAME 跟随", "workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 质询时是否阻止 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。点此了解更多。", "workflow_node.apply.form.disable_ari.label": "阻止 ARI 续期", "workflow_node.apply.form.disable_ari.tooltip": "在 ACME 证书续期时是否阻止 ARI(ACME Renewal Information)。如果你不了解该选项的用途,保持默认即可。点此了解更多。", - "workflow_node.apply.form.strategy_config.label": "执行策略", "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_anchor.parameters.tab": "参数设置", "workflow_node.upload.form.domains.label": "域名", "workflow_node.upload.form.domains.placeholder": "上传证书文件后显示", "workflow_node.upload.form.certificate.label": "证书文件(PEM 格式)", @@ -111,8 +118,28 @@ "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_anchor.parameters.tab": "参数设置", + "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_anchor.parameters.tab": "参数设置", + "workflow_node.deploy.form_anchor.deployment.tab": "部署设置", + "workflow_node.deploy.form_anchor.deployment.title": "部署设置", + "workflow_node.deploy.form_anchor.strategy.tab": "执行策略", + "workflow_node.deploy.form_anchor.strategy.title": "执行策略", "workflow_node.deploy.form.provider.label": "部署目标", "workflow_node.deploy.form.provider.placeholder": "请选择部署目标", "workflow_node.deploy.form.provider.search.placeholder": "搜索部署目标……", @@ -123,11 +150,10 @@ "workflow_node.deploy.form.certificate.label": "待部署证书", "workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书", "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请或上传节点。如果选项为空请先确保前序节点配置正确。", - "workflow_node.deploy.form.params_config.label": "参数设置", "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_node_name.label": "1Panel 子节点名称(可选)", "workflow_node.deploy.form.1panel_site_node_name.placeholder": "请输入 1Panel 子节点名称", - "workflow_node.deploy.form.1panel_site_node_name.tooltip": "请登录 1Panel 面板查看。仅 1Panel v2+ 需要。

                  不填写时,将替换主控节点证书;否则,将替换被控节点证书。", + "workflow_node.deploy.form.1panel_site_node_name.tooltip": "请登录 1Panel 面板查看。仅 1Panel v2+ 需要。
                  不填写时,将替换主控节点证书;否则,将替换被控节点证书。", "workflow_node.deploy.form.1panel_site_resource_type.label": "证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "替换指定网站的证书", @@ -153,7 +179,7 @@ "workflow_node.deploy.form.aliyun_alb_listener_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb", "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_apigw_service_type.label": "阿里云 API 网关服务类型", "workflow_node.deploy.form.aliyun_apigw_service_type.placeholder": "请选择阿里云 API 网关服务类型", "workflow_node.deploy.form.aliyun_apigw_service_type.option.cloudnative.label": "云原生 API 网关", @@ -173,20 +199,20 @@ "workflow_node.deploy.form.aliyun_cas_region.label": "阿里云 CAS 服务地域", "workflow_node.deploy.form.aliyun_cas_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_cas_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints", - "workflow_node.deploy.form.aliyun_cas_deploy.guide": "小贴士:将通过阿里云 OpenAPI CreateDeploymentJob 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", + "workflow_node.deploy.form.aliyun_cas_deploy.guide": "将通过阿里云 OpenAPI CreateDeploymentJob 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "阿里云 CAS 服务地域", "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_cas_deploy_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.label": "阿里云云产品资源 ID", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.placeholder": "请输入阿里云云产品资源 ID(多个值请用半角分号隔开)", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid": "请输入正确的阿里云云产品资源 ID", - "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources

                  仅支持阿里云产品,注意与各产品本身的实例 ID 区分。", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources
                  仅支持阿里云产品,注意与各产品本身的实例 ID 区分。", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title": "修改阿里云云产品资源 ID", "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.placeholder": "请输入阿里云云产品资源 ID", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.label": "阿里云联系人 ID(可选)", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.placeholder": "请输入阿里云联系人 ID(多个值请用半角分号隔开)", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid": "请输入正确的阿里云联系人 ID", - "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

                  不填写时,将使用系统联系人列表中的第一个。", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact
                  不填写时,将使用系统联系人列表中的第一个。", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "修改阿里云联系人 ID", "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "请输入阿里云联系人 ID", "workflow_node.deploy.form.aliyun_clb_region.label": "阿里云 CLB 服务地域", @@ -204,7 +230,7 @@ "workflow_node.deploy.form.aliyun_clb_listener_port.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", "workflow_node.deploy.form.aliyun_clb_snidomain.label": "阿里云 CLB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 https://cdn.console.aliyun.com", @@ -243,7 +269,7 @@ "workflow_node.deploy.form.aliyun_ga_listener_id.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com", "workflow_node.deploy.form.aliyun_ga_snidomain.label": "阿里云全球加速扩展域名(可选)", "workflow_node.deploy.form.aliyun_ga_snidomain.placeholder": "请输入阿里云全球加速扩展域名", - "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_ga_snidomain.tooltip": "这是什么?请参阅 https://ga.console.aliyun.com
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_live_region.label": "阿里云视频直播服务地域", "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云视频直播服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", @@ -285,10 +311,10 @@ "workflow_node.deploy.form.aliyun_waf_service_version.placeholder": "请选择阿里云 WAF 服务版本", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", - "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com

                  仅支持 CNAME 接入。", + "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com
                  仅支持 CNAME 接入。", "workflow_node.deploy.form.aliyun_waf_domain.label": "阿里云 WAF 接入域名(可选)", "workflow_node.deploy.form.aliyun_waf_domain.placeholder": "请输入阿里云 WAF 接入域名(支持泛域名)", - "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "这是什么?请参阅 waf.console.aliyun.com

                  不填写时,将替换实例的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.aliyun_waf_domain.tooltip": "这是什么?请参阅 waf.console.aliyun.com
                  不填写时,将替换实例的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.apisix_resource_type.label": "证书部署方式", "workflow_node.deploy.form.apisix_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.apisix_resource_type.option.certificate.label": "替换指定证书", @@ -338,7 +364,7 @@ "workflow_node.deploy.form.baiducloud_appblb_listener_port.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/appblb/list", "workflow_node.deploy.form.baiducloud_appblb_snidomain.label": "百度智能云 BLB 扩展域名(可选)", "workflow_node.deploy.form.baiducloud_appblb_snidomain.placeholder": "请输入百度智能云 BLB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.baiducloud_appblb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/appblb/list

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.baiducloud_appblb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/appblb/list
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.baiducloud_blb_region.label": "百度智能云 BLB 服务地域", "workflow_node.deploy.form.baiducloud_blb_region.placeholder": "请输入百度智能云 BLB 服务地域(例如:bj)", "workflow_node.deploy.form.baiducloud_blb_region.tooltip": "这是什么?请参阅 https://cloud.baidu.com/doc/BLB/s/cjwvxnzix", @@ -354,7 +380,7 @@ "workflow_node.deploy.form.baiducloud_blb_listener_port.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/blb/list", "workflow_node.deploy.form.baiducloud_blb_snidomain.label": "百度智能云 BLB 扩展域名(可选)", "workflow_node.deploy.form.baiducloud_blb_snidomain.placeholder": "请输入百度智能云 BLB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.baiducloud_blb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/blb/list

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.baiducloud_blb_snidomain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/blb/#/blb/list
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn", @@ -363,7 +389,7 @@ "workflow_node.deploy.form.baishan_cdn_domain.tooltip": "这是什么?请参阅 https://cdnx.console.baishan.com", "workflow_node.deploy.form.baishan_cdn_certificate_id.label": "白山云 CDN 原证书 ID(可选)", "workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder": "请输入白山云 CDN 原证书 ID", - "workflow_node.deploy.form.baishan_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdnx.console.baishan.com/#/cdn/cert

                  不填写时,将上传新证书;否则,将替换原证书。", + "workflow_node.deploy.form.baishan_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdnx.console.baishan.com/#/cdn/cert
                  不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.baotapanel_console_auto_restart.label": "部署后自动重启宝塔面板服务", "workflow_node.deploy.form.baotapanel_site_type.label": "宝塔面板网站类型", "workflow_node.deploy.form.baotapanel_site_type.placeholder": "请选择宝塔面板网站类型", @@ -444,7 +470,7 @@ "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", "workflow_node.deploy.form.gcore_cdn_certificate_id.label": "Gcore CDN 原证书 ID(可选)", "workflow_node.deploy.form.gcore_cdn_certificate_id.placeholder": "请输入 Gcore CDN 原证书 ID", - "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/ssl

                  不填写时,将上传新证书;否则,将替换原证书。", + "workflow_node.deploy.form.gcore_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/ssl
                  不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.goedge_resource_type.label": "证书部署方式", "workflow_node.deploy.form.goedge_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", @@ -503,7 +529,7 @@ "workflow_node.deploy.form.jdcloud_alb_listener_id.tooltip": "这是什么?请参阅 https://cns-console.jdcloud.com/host/loadBalance/list", "workflow_node.deploy.form.jdcloud_alb_snidomain.label": "京东云 ALB 扩展域名(可选)", "workflow_node.deploy.form.jdcloud_alb_snidomain.placeholder": "请输入京东云 ALB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.jdcloud_alb_snidomain.tooltip": "这是什么?请参阅 https://cns-console.jdcloud.com/host/loadBalance/list

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.jdcloud_alb_snidomain.tooltip": "这是什么?请参阅 https://cns-console.jdcloud.com/host/loadBalance/list
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.jdcloud_cdn_domain.label": "京东云 CDN 加速域名", "workflow_node.deploy.form.jdcloud_cdn_domain.placeholder": "请输入京东云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.jdcloud_cdn_domain.tooltip": "这是什么?请参阅 https://cdn-console.jdcloud.com", @@ -545,8 +571,8 @@ "workflow_node.deploy.form.lecdn_certificate_id.tooltip": "请登录 LeCDN 控制台查看。", "workflow_node.deploy.form.lecdn_client_id.label": "LeCDN 客户 ID(可选)", "workflow_node.deploy.form.lecdn_client_id.placeholder": "请输入 LeCDN 客户 ID", - "workflow_node.deploy.form.lecdn_client_id.tooltip": "请登录 LeCDN 控制台查看。

                  使用的是系统管理员的授权信息时必填,需与证书所属客户相同。", - "workflow_node.deploy.form.local.guide": "小贴士:如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。", + "workflow_node.deploy.form.lecdn_client_id.tooltip": "请登录 LeCDN 控制台查看。
                  使用的是系统管理员的授权信息时必填,需与证书所属客户相同。", + "workflow_node.deploy.form.local.guide": "如果你正在使用 Docker 运行 Certimate,“本地”指的是容器内而非宿主机。", "workflow_node.deploy.form.local_format.label": "文件格式", "workflow_node.deploy.form.local_format.placeholder": "请选择文件格式", "workflow_node.deploy.form.local_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)", @@ -560,10 +586,10 @@ "workflow_node.deploy.form.local_key_path.tooltip": "注意,路径需包含完整的文件名,而不是仅目录。", "workflow_node.deploy.form.local_servercert_path.label": "服务器证书文件保存路径(可选)", "workflow_node.deploy.form.local_servercert_path.placeholder": "请输入服务器证书文件保存路径", - "workflow_node.deploy.form.local_servercert_path.tooltip": "不填写时将不会保存服务器证书。

                  注意,路径需包含完整的文件名,而不是仅目录。", + "workflow_node.deploy.form.local_servercert_path.tooltip": "不填写时将不会保存服务器证书。
                  注意,路径需包含完整的文件名,而不是仅目录。", "workflow_node.deploy.form.local_intermediacert_path.label": "中间证书文件保存路径(可选)", "workflow_node.deploy.form.local_intermediacert_path.placeholder": "请输入中间证书文件保存路径", - "workflow_node.deploy.form.local_intermediacert_path.tooltip": "不填写时将不会保存服务器证书。

                  注意,路径需包含完整的文件名,而不是仅目录。", + "workflow_node.deploy.form.local_intermediacert_path.tooltip": "不填写时将不会保存服务器证书。
                  注意,路径需包含完整的文件名,而不是仅目录。", "workflow_node.deploy.form.local_pfx_password.label": "PFX 导出密码", "workflow_node.deploy.form.local_pfx_password.placeholder": "请输入 PFX 导出密码", "workflow_node.deploy.form.local_pfx_password.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/personal-information-exchange---pfx--files", @@ -639,10 +665,10 @@ "workflow_node.deploy.form.ssh_pfx_password.label": "PFX 导出密码", "workflow_node.deploy.form.ssh_servercert_path.label": "服务器证书文件上传路径(可选)", "workflow_node.deploy.form.ssh_servercert_path.placeholder": "请输入服务器证书文件上传路径", - "workflow_node.deploy.form.ssh_servercert_path.tooltip": "不填写时将不上传服务器证书。

                  注意,路径需包含完整的文件名,而不是仅目录。", + "workflow_node.deploy.form.ssh_servercert_path.tooltip": "不填写时将不上传服务器证书。
                  注意,路径需包含完整的文件名,而不是仅目录。", "workflow_node.deploy.form.ssh_intermediacert_path.label": "中间证书文件上传路径(可选)", "workflow_node.deploy.form.ssh_intermediacert_path.placeholder": "请输入中间证书文件上传路径", - "workflow_node.deploy.form.ssh_intermediacert_path.tooltip": "不填写时将不上传服务器证书。

                  注意,路径需包含完整的文件名,而不是仅目录。", + "workflow_node.deploy.form.ssh_intermediacert_path.tooltip": "不填写时将不上传服务器证书。
                  注意,路径需包含完整的文件名,而不是仅目录。", "workflow_node.deploy.form.ssh_pfx_password.placeholder": "请输入 PFX 导出密码", "workflow_node.deploy.form.ssh_pfx_password.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/personal-information-exchange---pfx--files", "workflow_node.deploy.form.ssh_jks_alias.label": "JKS 别名", @@ -672,13 +698,13 @@ "workflow_node.deploy.form.ssh_use_scp.tooltip": "如果你的远程服务器不支持 SFTP,请开启此选项回退为 SCP。", "workflow_node.deploy.form.tencentcloud_cdn_endpoint.label": "腾讯云 CDN 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_cdn_endpoint.placeholder": "请输入腾讯云 CDN 接口端点(例如:cdn.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_cdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/228/30976

                  国际站用户请填写 cdn.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_cdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/228/30976
                  国际站用户请填写 cdn.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名", "workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn", "workflow_node.deploy.form.tencentcloud_clb_endpoint.label": "腾讯云 CLB 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_clb_endpoint.placeholder": "请输入腾讯云 CLB 接口端点(例如:clb.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_clb_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669

                  国际站用户请填写 clb.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_clb_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669
                  国际站用户请填写 clb.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_clb_region.label": "腾讯云 CLB 产品地域", "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "请输入腾讯云 CLB 服务地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_clb_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/33415", @@ -696,7 +722,7 @@ "workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "腾讯云 CLB SNI 域名(可选)", "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "请输入腾讯云 CLB SNI 域名", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "请输入腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", @@ -711,19 +737,19 @@ "workflow_node.deploy.form.tencentcloud_cos_domain.tooltip": "这是什么?请参阅 see https://console.cloud.tencent.com/cos", "workflow_node.deploy.form.tencentcloud_css_endpoint.label": "腾讯云云直播接口端点(可选)", "workflow_node.deploy.form.tencentcloud_css_endpoint.placeholder": "请输入腾讯云云直播接口端点(例如:live.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_css_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/267/20458

                  国际站用户请填写 live.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_css_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/267/20458
                  国际站用户请填写 live.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_css_domain.label": "腾讯云云直播播放域名", "workflow_node.deploy.form.tencentcloud_css_domain.placeholder": "请输入腾讯云云直播播放域名", "workflow_node.deploy.form.tencentcloud_css_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/live", "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.label": "腾讯云 ECDN 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.placeholder": "请输入腾讯云 ECDN 接口端点(例如:cdn.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669

                  国际站用户请填写 cdn.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_ecdn_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/30669
                  国际站用户请填写 cdn.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ecdn_domain.label": "腾讯云 ECDN 加速域名", "workflow_node.deploy.form.tencentcloud_ecdn_domain.placeholder": "请输入腾讯云 ECDN 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_ecdn_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/cdn", "workflow_node.deploy.form.tencentcloud_eo_endpoint.label": "腾讯云 EdgeOne 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_eo_endpoint.placeholder": "请输入腾讯云 EdgeOne 接口端点(例如:teo.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_eo_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/1552/80723

                  国际站用户请填写 teo.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_eo_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/1552/80723
                  国际站用户请填写 teo.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_eo_zone_id.label": "腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder": "请输入腾讯云 EdgeOne 站点 ID", "workflow_node.deploy.form.tencentcloud_eo_zone_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", @@ -734,7 +760,7 @@ "workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "请输入腾讯云 EdgeOne 加速域名(支持泛域名)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "腾讯云 GAAP 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/608/36934

                  国际站用户请填写 gaap.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/608/36934
                  国际站用户请填写 gaap.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.label": "证书部署方式", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.tencentcloud_gaap_resource_type.option.listener.label": "替换指定监听器的证书", @@ -746,7 +772,7 @@ "workflow_node.deploy.form.tencentcloud_gaap_listener_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/gaap", "workflow_node.deploy.form.tencentcloud_scf_endpoint.label": "腾讯云 SCF 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_scf_endpoint.placeholder": "请输入腾讯云 SCF 接口端点(例如:scf.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_scf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17237

                  国际站用户请填写 scf.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_scf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17237
                  国际站用户请填写 scf.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_scf_region.label": "腾讯云 SCF 产品地域", "workflow_node.deploy.form.tencentcloud_scf_region.placeholder": "输入腾讯云 SCF 产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_scf_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17299", @@ -755,11 +781,11 @@ "workflow_node.deploy.form.tencentcloud_scf_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/scf", "workflow_node.deploy.form.tencentcloud_ssl_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", - "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "小贴士:将通过腾讯云 OpenAPI DeployCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", + "workflow_node.deploy.form.tencentcloud_ssl_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659
                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "将通过腾讯云 OpenAPI DeployCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659
                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "腾讯云云产品地域", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "请输入腾讯云云产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659", @@ -769,19 +795,19 @@ "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.label": "腾讯云云产品资源 ID", "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.placeholder": "请输入腾讯云云产品资源 ID(多个值请用半角分号隔开)", "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid": "请输入正确的腾讯云云产品资源 ID", - "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91667

                  注意与各产品本身的实例 ID 区分。", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91667
                  注意与各产品本身的实例 ID 区分。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title": "修改腾讯云云产品资源 ID", "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.placeholder": "请输入腾讯云云产品资源 ID", - "workflow_node.deploy.form.tencentcloud_ssl_update.guide": "小贴士:将通过腾讯云 OpenAPI UpdateCertificateInstance 或 UploadUpdateCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", + "workflow_node.deploy.form.tencentcloud_ssl_update.guide": "将通过腾讯云 OpenAPI UpdateCertificateInstance 或 UploadUpdateCertificateInstance 接口创建异步部署任务。此部署目标若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.label": "腾讯云 SSL 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.placeholder": "请输入腾讯云 SSL 接口端点(例如:ssl.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659

                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_ssl_update_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/41659
                  国际站用户请填写 ssl.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.label": "腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.placeholder": "请输入腾讯云原证书 ID", "workflow_node.deploy.form.tencentcloud_ssl_update_certificate_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/certoverview", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.label": "腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.placeholder": "请输入腾讯云云产品资源类型(多个值请用半角分号隔开)", - "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649 或 https://cloud.tencent.com/document/product/400/119791

                  注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。", + "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/400/91649 或 https://cloud.tencent.com/document/product/400/119791
                  注意,这两个接口的所支持的云产品资源类型有所不同,具体请查看腾讯云官方文档。", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.title": "修改腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_types.multiple_input_modal.placeholder": "请输入腾讯云云产品资源类型", "workflow_node.deploy.form.tencentcloud_ssl_update_resource_regions.label": "腾讯云云产品部署地域(可选)", @@ -793,7 +819,7 @@ "workflow_node.deploy.form.tencentcloud_ssl_update_is_replaced.tooltip": "不勾选时,将调用腾讯云 OpenAPI UpdateCertificateInstance 接口;否则,将调用腾讯云 OpenAPI UploadUpdateCertificateInstance 接口。", "workflow_node.deploy.form.tencentcloud_vod_endpoint.label": "腾讯云云点播接口端点(可选)", "workflow_node.deploy.form.tencentcloud_vod_endpoint.placeholder": "请输入腾讯云云点播接口端点(例如:vod.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/266/31755

                  国际站用户请填写 vod.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_vod_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/266/31755
                  国际站用户请填写 vod.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.label": "腾讯云云点播应用 ID", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.placeholder": "请输入腾讯云云点播应用 ID", "workflow_node.deploy.form.tencentcloud_vod_sub_app_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/vod", @@ -802,7 +828,7 @@ "workflow_node.deploy.form.tencentcloud_vod_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/vod", "workflow_node.deploy.form.tencentcloud_waf_endpoint.label": "腾讯云 WAF 接口端点(可选)", "workflow_node.deploy.form.tencentcloud_waf_endpoint.placeholder": "请输入腾讯云 WAF 接口端点(例如:waf.tencentcloudapi.com)", - "workflow_node.deploy.form.tencentcloud_waf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/53611

                  国际站用户请填写 waf.intl.tencentcloudapi.com。", + "workflow_node.deploy.form.tencentcloud_waf_endpoint.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/53611
                  国际站用户请填写 waf.intl.tencentcloudapi.com。", "workflow_node.deploy.form.tencentcloud_waf_region.label": "腾讯云 WAF 产品地域", "workflow_node.deploy.form.tencentcloud_waf_region.placeholder": "请输入腾讯云 WAF 产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_waf_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/627/47525", @@ -827,7 +853,7 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", - "workflow_node.deploy.form.unicloud_webhost.guide": "小贴士:由于 uniCloud 未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇 uniCloud 接口变更,请到 GitHub 发起 Issue 告知。", + "workflow_node.deploy.form.unicloud_webhost.guide": "由于 uniCloud 未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇 uniCloud 接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.unicloud_webhost_space_provider.label": "uniCloud 服务空间提供商", "workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder": "请选择 uniCloud 服务空间提供商", "workflow_node.deploy.form.unicloud_webhost_space_provider.option.aliyun.label": "阿里云", @@ -837,11 +863,11 @@ "workflow_node.deploy.form.unicloud_webhost_space_id.tooltip": "这是什么?请参阅 https://doc.dcloud.net.cn/uniCloud/concepts/space.html", "workflow_node.deploy.form.unicloud_webhost_domain.label": "uniCloud 前端网页托管网站域名", "workflow_node.deploy.form.unicloud_webhost_domain.placeholder": "请输入 uniCloud 前端网页托管网站域名", - "workflow_node.deploy.form.upyun_cdn.guide": "小贴士:由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", + "workflow_node.deploy.form.upyun_cdn.guide": "由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/cdn/", - "workflow_node.deploy.form.upyun_file.guide": "小贴士:由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", + "workflow_node.deploy.form.upyun_file.guide": "由于又拍云未公开相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储自定义域名", "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储自定义域名", "workflow_node.deploy.form.upyun_file_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/file/", @@ -860,7 +886,7 @@ "workflow_node.deploy.form.volcengine_alb_listener_id.tooltip": "这是什么?请参阅 https://console.volcengine.com/alb", "workflow_node.deploy.form.volcengine_alb_snidomain.label": "火山引擎 ALB 扩展域名(可选)", "workflow_node.deploy.form.volcengine_alb_snidomain.placeholder": "请输入火山引擎 ALB 扩展域名(支持泛域名)", - "workflow_node.deploy.form.volcengine_alb_snidomain.tooltip": "这是什么?请参阅 https://console.volcengine.com/alb

                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", + "workflow_node.deploy.form.volcengine_alb_snidomain.tooltip": "这是什么?请参阅 https://console.volcengine.com/alb
                  不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/cdn/homepage", @@ -917,39 +943,30 @@ "workflow_node.deploy.form.wangsu_cdnpro_domain.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/properties", "workflow_node.deploy.form.wangsu_cdnpro_certificate_id.label": "网宿云 CDN Pro 原证书 ID(可选)", "workflow_node.deploy.form.wangsu_cdnpro_certificate_id.placeholder": "请输入网宿云 CDN Pro 原证书 ID", - "workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate

                  不填写时,将上传新证书;否则,将替换原证书。", + "workflow_node.deploy.form.wangsu_cdnpro_certificate_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate
                  不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.label": "网宿云 CDN Pro 部署任务 Webhook ID(可选)", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.placeholder": "请输入网宿云 CDN Pro 部署任务 Webhook ID", "workflow_node.deploy.form.wangsu_cdnpro_webhook_id.tooltip": "这是什么?请参阅 https://cdnpro.console.wangsu.com/v2/index/#/certificate", "workflow_node.deploy.form.wangsu_certificate_id.label": "网宿云证书 ID(可选)", "workflow_node.deploy.form.wangsu_certificate_id.placeholder": "请输入网宿云证书 ID", - "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list

                  不填写时,将上传新证书;否则,将替换原证书。", + "workflow_node.deploy.form.wangsu_certificate_id.tooltip": "这是什么?请参阅 https://cdn.console.wangsu.com/v2/index#/certificate/list
                  不填写时,将上传新证书;否则,将替换原证书。", "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.deploy.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", "workflow_node.deploy.form.webhook_data.guide": "
                  支持的变量:
                  1. ${DOMAIN}:证书的主域名(即 CommonName)。
                  2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
                  3. ${CERTIFICATE}:证书文件 PEM 格式内容。
                  4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
                  5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
                  6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

                  其他注意事项请前往授权凭据页面查看。", "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_anchor.parameters.tab": "参数设置", + "workflow_node.notify.form_anchor.strategy.tab": "执行策略", + "workflow_node.notify.form_anchor.strategy.title": "执行策略", "workflow_node.notify.form.subject.label": "通知主题", "workflow_node.notify.form.subject.placeholder": "请输入通知主题", "workflow_node.notify.form.message.label": "通知内容", @@ -980,23 +997,61 @@ "workflow_node.notify.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", "workflow_node.notify.form.webhook_data.guide": "
                  支持的变量:
                  1. ${SUBJECT}:通知主题。
                  2. ${MESSAGE}:通知内容。

                  其他注意事项请前往授权凭据页面查看。", "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", - "workflow_node.notify.form.strategy_config.label": "执行策略", "workflow_node.notify.form.skip_on_all_prev_skipped.label": "静默行为", "workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "当前序申请、上传、部署等节点均已跳过执行时,", "workflow_node.notify.form.skip_on_all_prev_skipped.suffix": "此通知节点。", "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.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": "若网站证书已到期 ...", + + "workflow_node.branch_block.label": "分支", + "workflow_node.branch_block.default_name": "分支", + "workflow_node.branch_block.state.no": "无条件进入", + "workflow_node.branch_block.state.or": "满足任一条件时进入", + "workflow_node.branch_block.state.and": "满足所有条件时进入", + "workflow_node.branch_block.form_anchor.parameters.tab": "参数设置", + "workflow_node.branch_block.form.expression.label": "分支进入条件", + "workflow_node.branch_block.form.expression.errmsg.invalid": "请输入有效的条件", + "workflow_node.branch_block.form.expression.logical_operator.errmsg": "请选择条件组合方式", + "workflow_node.branch_block.form.expression.logical_operator.option.and.label": "满足以下所有条件 (AND)", + "workflow_node.branch_block.form.expression.logical_operator.option.or.label": "满足以下任一条件 (OR)", + "workflow_node.branch_block.form.expression.variable.placeholder": "请选择", + "workflow_node.branch_block.form.expression.variable.errmsg": "请选择变量", + "workflow_node.branch_block.form.expression.operator.placeholder": "请选择", + "workflow_node.branch_block.form.expression.operator.errmsg": "请选择运算符", + "workflow_node.branch_block.form.expression.operator.option.eq.label": "等于", + "workflow_node.branch_block.form.expression.operator.option.eq.alias_is_label": "为", + "workflow_node.branch_block.form.expression.operator.option.neq.label": "不等于", + "workflow_node.branch_block.form.expression.operator.option.neq.alias_not_label": "不为", + "workflow_node.branch_block.form.expression.operator.option.gt.label": "大于", + "workflow_node.branch_block.form.expression.operator.option.gte.label": "大于等于", + "workflow_node.branch_block.form.expression.operator.option.lt.label": "小于", + "workflow_node.branch_block.form.expression.operator.option.lte.label": "小于等于", + "workflow_node.branch_block.form.expression.value.placeholder": "请输入", + "workflow_node.branch_block.form.expression.value.errmsg": "请输入值", + "workflow_node.branch_block.form.expression.value.option.true.label": "真", + "workflow_node.branch_block.form.expression.value.option.false.label": "假", + "workflow_node.branch_block.form.expression.add_condition.button": "添加条件", + + "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": "结束", + + "@deprecated": "TODO: remove in v0.4.x below", + "workflow_node.branch.label": "并行/条件分支", + "workflow_node.branch.default_name": "分支", "workflow_node.condition.form.expression.label": "分支进入条件", "workflow_node.condition.form.expression.logical_operator.errmsg": "请选择条件组合方式", "workflow_node.condition.form.expression.logical_operator.option.and.label": "满足以下所有条件 (AND)", @@ -1018,13 +1073,10 @@ "workflow_node.condition.form.expression.value.option.true.label": "真", "workflow_node.condition.form.expression.value.option.false.label": "假", "workflow_node.condition.form.expression.add_condition.button": "添加条件", - "workflow_node.execute_result_branch.label": "执行结果分支", "workflow_node.execute_result_branch.default_name": "分支", - "workflow_node.execute_success.label": "若上一节点执行成功…", "workflow_node.execute_success.default_name": "执行成功", - "workflow_node.execute_failure.label": "若上一节点执行失败…", "workflow_node.execute_failure.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 af5ba358c..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/index.css b/ui/src/index.css index f8fbe4238..b37b38024 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -16,5 +16,5 @@ body { min-width: 360px; min-height: 540px; min-height: 100vh; - min-height: calc(min(360px, 100vh)); + min-height: calc(min(540px, 100vh)); } diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx index a8e628d5c..28702d373 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"; @@ -69,7 +69,7 @@ const ConsoleLayout = () => {
                  -
                  +
                  { danger: true, icon: ( - + ), label: t("common.menu.logout"), @@ -136,7 +136,7 @@ const ConsoleLayout = () => {
                  @@ -198,7 +198,7 @@ const SiderMenu = memo(({ onSelect }: { onSelect?: (key: string) => void }) => { if (item) { setMenuSelectedKey(item.key as string); } else { - setMenuSelectedKey(undefined); + setMenuSelectedKey(void 0); } }, [location.pathname]); diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index f42481209..7015766f9 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { IconCirclePlus, IconCopy, IconDotsVertical, IconEdit, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; +import { IconCirclePlus, IconCopy, IconDots, IconEdit, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; import { App, Avatar, Button, Dropdown, Input, Skeleton, Table, type TableProps, Tabs, Typography, theme } from "antd"; import dayjs from "dayjs"; @@ -125,7 +125,7 @@ const AccessList = () => { }} trigger={["click"]} > -
diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 0c22af811..b7e4ae522 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -16,22 +16,29 @@ const Settings = () => { ["ssl-provider", "settings.sslprovider.tab", ], ["persistence", "settings.persistence.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [menuKey, setMenuValue] = useState("account"); + const [menuKey, setMenuKey] = useState(() => location.pathname.split("/")[2]); useEffect(() => { - const path = location.pathname.split("/")[2]; - if (!path) { + const subpath = location.pathname.split("/")[2]; + if (!subpath) { navigate("/settings/account"); return; } - setMenuValue(path); + setMenuKey(subpath); }, [location.pathname]); + const handleMenuClick = ({ key }: { key: string }) => { + setMenuKey(key); + navigate(`/settings/${key}`); + }; + return (
-
+

{t("settings.page.title")}

+
+
{ ), }))} - onClick={({ key }) => { - setMenuValue(key); - navigate(`/settings/${key}`); - }} + onClick={handleMenuClick} />
@@ -68,10 +72,7 @@ const Settings = () => { ), }))} - onClick={({ key }) => { - setMenuValue(key); - navigate(`/settings/${key}`); - }} + onClick={handleMenuClick} />
diff --git a/ui/src/pages/settings/SettingsAccount.tsx b/ui/src/pages/settings/SettingsAccount.tsx index 08f3c3635..0555a38e5 100644 --- a/ui/src/pages/settings/SettingsAccount.tsx +++ b/ui/src/pages/settings/SettingsAccount.tsx @@ -38,7 +38,7 @@ const SettingsAccountUsername = ({ className, style }: { className?: string; sty const { message, notification } = App.useApp(); const formSchema = z.object({ - username: z.email(t("common.errmsg.email_invalid")), + username: z.email(t("common.errmsg.email_invalid")).max(256, t("common.errmsg.string_max", { max: 256 })), }); const formRule = createSchemaFieldRule(formSchema); const { @@ -127,8 +127,14 @@ const SettingsAccountPassword = ({ className, style }: { className?: string; sty const { message, notification } = App.useApp(); const formSchema = z.object({ - oldPassword: z.string().min(10, t("settings.account.password.form.email.password.errmsg.invalid")), - newPassword: z.string().min(10, t("settings.account.password.form.email.password.errmsg.invalid")), + oldPassword: z + .string() + .min(10, t("settings.account.password.form.email.password.errmsg.invalid")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + newPassword: z + .string() + .min(10, t("settings.account.password.form.email.password.errmsg.invalid")) + .max(256, t("common.errmsg.string_max", { max: 256 })), confirmPassword: z.string().refine((v) => v === formInst.getFieldValue("newPassword"), { error: t("settings.account.password.form.email.password.errmsg.not_matched"), }), diff --git a/ui/src/pages/settings/SettingsAppearance.tsx b/ui/src/pages/settings/SettingsAppearance.tsx index 6db58c741..9c29c7755 100644 --- a/ui/src/pages/settings/SettingsAppearance.tsx +++ b/ui/src/pages/settings/SettingsAppearance.tsx @@ -47,7 +47,7 @@ const SettingsAppearanceTheme = ({ className, style }: { className?: string; sty return (
- +
{themeItems.map((item) => ( @@ -84,7 +84,7 @@ const SettingsAppearanceLanguage = ({ className, style }: { className?: string; return (
- + + } + tooltip={} > - + @@ -225,12 +226,12 @@ const SSLProviderEditFormSSLComConfig = () => { const formSchema = z.object({ eabKid: z - .string(t("settings.sslprovider.form.sslcom_eab_kid.placeholder")) - .min(1, t("settings.sslprovider.form.sslcom_eab_kid.placeholder")) + .string(t("access.form.sslcom_eab_kid.placeholder")) + .min(1, t("access.form.sslcom_eab_kid.placeholder")) .max(256, t("common.errmsg.string_max", { max: 256 })), eabHmacKey: z - .string(t("settings.sslprovider.form.sslcom_eab_hmac_key.placeholder")) - .min(1, t("settings.sslprovider.form.sslcom_eab_hmac_key.placeholder")) + .string(t("access.form.sslcom_eab_hmac_key.placeholder")) + .min(1, t("access.form.sslcom_eab_hmac_key.placeholder")) .max(256, t("common.errmsg.string_max", { max: 256 })), }); const formRule = createSchemaFieldRule(formSchema); @@ -263,20 +264,20 @@ const SSLProviderEditFormSSLComConfig = () => { } + tooltip={} > - + } + tooltip={} > - + @@ -295,12 +296,12 @@ const SSLProviderEditFormZeroSSLConfig = () => { const formSchema = z.object({ eabKid: z - .string(t("settings.sslprovider.form.zerossl_eab_kid.placeholder")) - .min(1, t("settings.sslprovider.form.zerossl_eab_kid.placeholder")) + .string(t("access.form.zerossl_eab_kid.placeholder")) + .min(1, t("access.form.zerossl_eab_kid.placeholder")) .max(256, t("common.errmsg.string_max", { max: 256 })), eabHmacKey: z - .string(t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder")) - .min(1, t("settings.sslprovider.form.zerossl_eab_hmac_key.placeholder")) + .string(t("access.form.zerossl_eab_hmac_key.placeholder")) + .min(1, t("access.form.zerossl_eab_hmac_key.placeholder")) .max(256, t("common.errmsg.string_max", { max: 256 })), }); const formRule = createSchemaFieldRule(formSchema); @@ -333,20 +334,20 @@ const SSLProviderEditFormZeroSSLConfig = () => { } + tooltip={} > - + } + tooltip={} > - + diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index 66e404080..c404e11ec 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -1,25 +1,21 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useNavigate, useParams } from "react-router-dom"; -import { IconArrowBackUp, IconChevronDown, IconDots, IconHistory, IconPlayerPlay, IconRobot, IconTrash } from "@tabler/icons-react"; -import { Alert, App, Button, Card, Dropdown, Flex, Form, Input, Segmented, Space } from "antd"; -import { createSchemaFieldRule } from "antd-zod"; -import { isEqual } from "radash"; -import { z } from "zod"; +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 } from "antd"; import { startRun as startWorkflowRun } from "@/api/workflows"; -import ModalForm from "@/components/ModalForm"; import Show from "@/components/Show"; -import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; -import WorkflowRuns from "@/components/workflow/WorkflowRuns"; import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; -import { useAntdForm, useZustandShallowSelector } from "@/hooks"; -import { remove as removeWorkflow, subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow"; +import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; +import { mergeCls } from "@/utils/css"; import { getErrMsg } from "@/utils/error"; const WorkflowDetail = () => { + const location = useLocation(); const navigate = useNavigate(); const { t } = useTranslation(); @@ -27,9 +23,7 @@ const WorkflowDetail = () => { const { message, modal, notification } = App.useApp(); const { id: workflowId } = useParams(); - const { workflow, initialized, ...workflowState } = useWorkflowStore( - useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"]) - ); + const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled"])); useEffect(() => { workflowState.init(workflowId!); @@ -38,119 +32,36 @@ const WorkflowDetail = () => { }; }, [workflowId]); - const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration"); - - const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); - const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); - - const [allowDiscard, setAllowDiscard] = useState(false); - const [allowRelease, setAllowRelease] = useState(false); - const [allowRun, setAllowRun] = useState(false); + const divHeaderRef = useRef(null); + 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][]; + const [tabValue, setTabValue] = useState(() => location.pathname.split("/")[3]); useEffect(() => { - setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); - }, [lastRunStatus]); - - useEffect(() => { - if (!!workflowId && isPendingOrRunning) { - subscribeWorkflow(workflowId, (cb) => { - if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeWorkflow(workflowId); - } - }); - - return () => { - unsubscribeWorkflow(workflowId); - }; - } - }, [workflowId, isPendingOrRunning]); - - useEffect(() => { - const hasReleased = !!workflow.content; - const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); - setAllowDiscard(!isPendingOrRunning && hasReleased && hasChanges); - setAllowRelease(!isPendingOrRunning && hasChanges); - setAllowRun(hasReleased); - }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]); - - const handleEnableChange = async () => { - if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) { - message.warning(t("workflow.action.enable.errmsg.uncompleted")); + const subpath = location.pathname.split("/")[3]; + if (!subpath) { + navigate(`/workflows/${workflowId}/${tabs[0][0]}`, { replace: true }); return; } - try { - await workflowState.setEnabled(!workflow.enabled); - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } + setTabValue(subpath); + }, [location.pathname, workflowId]); + + const handleTabChange = (value: string) => { + setTabValue(value); + navigate(`/workflows/${workflowId}/${value}`); }; - const handleDeleteClick = () => { - modal.confirm({ - title: {t("workflow.action.delete.modal.title", { name: workflow.name })}, - content: , - icon: ( - - - - ), - okText: t("common.button.confirm"), - okButtonProps: { danger: true }, - onOk: async () => { - try { - const resp = await removeWorkflow(workflow); - if (resp) { - navigate("/workflows", { replace: true }); - } - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }, - }); - }; - - const handleDiscardClick = () => { - modal.confirm({ - title: t("workflow.action.discard.modal.title"), - content: t("workflow.action.discard.modal.content"), - onOk: async () => { - try { - await workflowState.discard(); - - message.success(t("common.text.operation_succeeded")); - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }, - }); - }; - - const handleReleaseClick = () => { - if (!isAllNodesValidated(workflow.draft!)) { - message.warning(t("workflow.action.release.errmsg.uncompleted")); - return; - } - - modal.confirm({ - title: t("workflow.action.release.modal.title"), - content: t("workflow.action.release.modal.content"), - onOk: async () => { - try { - await workflowState.release(); - - message.success(t("common.text.operation_succeeded")); - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }, - }); - }; + const runButtonDisabled = useMemo(() => !workflow?.content, [workflow]); + const [runButtonLoading, setRunButtonLoading] = useState(false); + useEffect(() => { + const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING; + setRunButtonLoading(pending); + }, [workflow.lastRunStatus]); const handleRunClick = () => { const { promise, resolve, reject } = Promise.withResolvers(); @@ -166,25 +77,14 @@ const WorkflowDetail = () => { } promise.then(async () => { - let unsubscribeFn: Awaited> | undefined = undefined; - try { - setIsPendingOrRunning(true); + setRunButtonLoading(true); - // subscribe before running workflow - unsubscribeFn = await subscribeWorkflow(workflowId!, (e) => { - if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - } - }); - - await startWorkflowRun(workflowId!); + await startWorkflowRun(workflow.id); message.info(t("workflow.action.run.prompt")); } catch (err) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); + setRunButtonLoading(false); console.error(err); message.warning(t("common.text.operation_failed")); @@ -192,210 +92,232 @@ 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 (
-
-
-
-
-

{workflow.name || "\u00A0"}

-

{workflow.description || "\u00A0"}

-
- - {initialized - ? [ - {t("common.button.edit")}} />, - , - , - onClick: () => { - handleDeleteClick(); - }, - }, - ], - }} - trigger={["click"]} - > - - , - ] - : []} - -
+
+
+
+ + -
- {t("workflow.detail.orchestration.tab")}, +
+ ({ + value: key, + label: {t(label)}, icon: ( - + {icon} ), - }, - { - value: "runs", - label: {t("workflow.detail.runs.tab")}, - icon: ( - - - - ), - }, - ]} - size="large" - value={tabValue} - defaultValue="orchestration" - onChange={(value) => { - setTabValue(value as typeof tabValue); - }} - /> + }))} + size="large" + value={tabValue} + onChange={handleTabChange} + /> +
+
+
+ +
+ + +
+
- -
- -
-
- - {t("workflow.detail.orchestration.draft.alert")}
} showIcon type="warning" /> - -
-
- - - - - , - onClick: handleDiscardClick, - }, - ], - }} - trigger={["click"]} - > -
+
+ +
- - -
-
-
- - -
-
- -
-
-
+ } + > + + +
); }; -const WorkflowBaseInfoModal = ({ trigger }: { trigger?: React.ReactNode }) => { +const WorkflowDetailBaseName = () => { const { t } = useTranslation(); const { notification } = App.useApp(); - const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "setBaseInfo"])); + const { workflow, initialized, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "setBaseInfo"])); - const formSchema = z.object({ - name: z - .string(t("workflow.detail.baseinfo.form.name.placeholder")) - .min(1, t("workflow.detail.baseinfo.form.name.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })), - description: z - .string(t("workflow.detail.baseinfo.form.description.placeholder")) - .max(256, t("common.errmsg.string_max", { max: 256 })) - .nullish(), - }); - const formRule = createSchemaFieldRule(formSchema); - const { - form: formInst, - formPending, - formProps, - submit: submitForm, - } = useAntdForm>({ - initialValues: { name: workflow.name, description: workflow.description }, - onSubmit: async (values) => { - try { - await workflowState.setBaseInfo(values.name!, values.description!); - } catch (err) { - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + const inputRef = useRef(null); + const [editing, setEditing] = useState(false); + const [value, setValue] = useState(""); - throw err; - } - }, - }); + useEffect(() => { + setEditing(false); + }, [workflow.id]); - const handleFormFinish = async () => { - return submitForm(); + const handleEditClick = () => { + setEditing(true); + setValue(workflow.name); + setTimeout(() => { + inputRef.current?.focus({ cursor: "all" }); + }, 0); + }; + + const handleValueChange = (value: string) => { + setValue(value); + }; + + const handleValueConfirm = async (value: string) => { + value = value.trim(); + if (!value || value === (workflow.name || "")) { + setEditing(false); + return; + } + + setEditing(false); + + try { + await setWorkflowBaseInfo(value, workflow.description!); + } catch (err) { + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + + throw err; + } }; return ( - <> - - - - +
+

+ + {workflow.name || t("workflow.detail.baseinfo.name.placeholder")} + +

+ +
+ ); +}; - - - -
- +const WorkflowDetailBaseDescription = () => { + const { t } = useTranslation(); + + const { notification } = App.useApp(); + + const { workflow, initialized, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "setBaseInfo"])); + + const inputRef = useRef(null); + const [editing, setEditing] = useState(false); + const [value, setValue] = useState(""); + + useEffect(() => { + setEditing(false); + }, [workflow.id]); + + const handleEditClick = () => { + setEditing(true); + setValue(workflow.description || ""); + setTimeout(() => { + inputRef.current?.focus({ cursor: "all" }); + }, 0); + }; + + const handleValueChange = (value: string) => { + setValue(value); + }; + + const handleValueConfirm = async (value: string) => { + value = value.trim(); + if (!value || value === (workflow.description || "")) { + setEditing(false); + return; + } + + setEditing(false); + + try { + await setWorkflowBaseInfo(workflow.name!, value); + } catch (err) { + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + + throw err; + } + }; + + return ( +
+

+ + {workflow.description || t("workflow.detail.baseinfo.description.placeholder")} + +

+ +
); }; diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx new file mode 100644 index 000000000..f46d01c7d --- /dev/null +++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx @@ -0,0 +1,352 @@ +import { useEffect, useRef, useState } from "react"; +import { getI18n, useTranslation } from "react-i18next"; +import { type FlowDocumentJSON } from "@flowgram.ai/document"; +import { IconArrowBackUp, IconDots } from "@tabler/icons-react"; +import { useDeepCompareEffect } from "ahooks"; +import { Alert, App, Button, Card, Dropdown, Space, theme } from "antd"; +import { nanoid } from "nanoid"; +import { debounce, isEqual } from "radash"; + +import Show from "@/components/Show"; +import WorkflowEditor, { type EditorInstance as WorkflowEditorInstance } from "@/components/workflow/designer/Editor"; +import WorkflowNodeDrawer from "@/components/workflow/designer/NodeDrawer"; +import WorkflowToolbar from "@/components/workflow/designer/Toolbar"; +import { type WorkflowNode, WorkflowNodeType } 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 { token: themeToken } = theme.useToken(); + const { message, modal, notification } = App.useApp(); + + const { workflow } = useWorkflowStore(useZustandShallowSelector(["workflow", "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 editorRef = useRef(null); + const [editorData, setEditorData] = useState(); + useDeepCompareEffect(() => { + const data = { nodes: compactWorkflowDraft(workflow.draft) }; + editorRef.current?.document?.fromJSON(data); + setEditorData(data); + }, [workflow.draft]); + + const onEditorDocumentChange = debounce({ delay: 300 }, () => { + if (!editorRef.current || editorRef.current.document.disposed) return; + + console.log("document changed", editorRef.current!.document.toJSON()); + }); + + useEffect(() => { + const disposable = editorRef.current?.document?.originTree?.onTreeChange(onEditorDocumentChange); + return () => disposable?.dispose(); + }, []); + + const { setNode: setDrawerNode, setOpen: setNodeDrawerOpen, ...nodeDrawerProps } = WorkflowNodeDrawer.useProps(); + + const handleRollbackClick = () => { + modal.confirm({ + title: t("workflow.detail.design.action.rollback.modal.title"), + content: t("workflow.detail.design.action.rollback.modal.content"), + onOk: async () => { + try { + alert("TODO: 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 = async () => { + if (!(await editorRef.current!.validateAllNodes())) { + message.warning(t("workflow.detail.design.uncompleted_design.alert")); + return; + } + + modal.confirm({ + title: t("workflow.detail.design.action.publish.modal.title"), + content: t("workflow.detail.design.action.publish.modal.content"), + onOk: async () => { + try { + alert("TODO: 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 ( +
+ + { + setDrawerNode(node); + setNodeDrawerOpen(true); + }} + > +
+
+
+
+
+ + {t("workflow.detail.design.unpublished_draft.alert")}
} showIcon type="warning" /> + +
+ + + , + 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: t("workflow_node.start.default_name"), + }, + }); + } 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..3bd52ac10 --- /dev/null +++ b/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx @@ -0,0 +1,136 @@ +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"; + +/** + * + * @deprecated + */ +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.detail.design.action.rollback.modal.title"), + content: t("workflow.detail.design.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.detail.design.uncompleted_design.alert")); + return; + } + + modal.confirm({ + title: t("workflow.detail.design.action.publish.modal.title"), + content: t("workflow.detail.design.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" /> + +
+ + + , + onClick: handleRollbackClick, + }, + ], + }} + trigger={["click"]} + > +
+
+
+
+ + +
+ +
+ ); +}; + +export default WorkflowDetailDesign; diff --git a/ui/src/components/workflow/WorkflowRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx similarity index 61% rename from ui/src/components/workflow/WorkflowRuns.tsx rename to ui/src/pages/workflows/WorkflowDetailRuns.tsx index 72892bc57..fe79ba889 100644 --- a/ui/src/components/workflow/WorkflowRuns.tsx +++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx @@ -1,39 +1,37 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { IconBrowserShare, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react"; +import { IconBrowserShare, IconDots, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; -import { Alert, App, Button, Skeleton, Table, type TableProps, Tooltip } from "antd"; +import { App, Button, Dropdown, Skeleton, Table, type TableProps } from "antd"; import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; import { cancelRun as cancelWorkflowRun } from "@/api/workflows"; import Empty from "@/components/Empty"; +import Tips from "@/components/Tips"; +import WorkflowRunDetailDrawer from "@/components/workflow/WorkflowRunDetailDrawer"; import WorkflowStatusTag from "@/components/workflow/WorkflowStatusTag"; import { WORKFLOW_TRIGGERS } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun"; -import { useAppSettings } from "@/hooks"; +import { useAppSettings, useZustandShallowSelector } from "@/hooks"; import { list as listWorkflowRuns, remove as removeWorkflowRun, subscribe as subscribeWorkflowRun, unsubscribe as unsubscribeWorkflowRun, } from "@/repository/workflowRun"; +import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; -import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer"; -export interface WorkflowRunsProps { - className?: string; - style?: React.CSSProperties; - workflowId: string; -} - -const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { +const WorkflowDetailRuns = () => { const { t } = useTranslation(); const { modal, notification } = App.useApp(); const { appSettings: globalAppSettings } = useAppSettings(); + const { workflow } = useWorkflowStore(useZustandShallowSelector(["workflow"])); + const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(globalAppSettings.defaultPerPage!); @@ -101,53 +99,63 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { 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 ( -
- -
+ + +
+ ), + 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"]} + > +