mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
Merge pull request #935 from fudiwei/dev
This commit is contained in:
@@ -5,8 +5,8 @@ go 1.24.0
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0
|
||||
github.com/Edgio/edgio-api v0.0.0-workspace
|
||||
github.com/G-Core/gcorelabscdn-go v1.0.32
|
||||
@@ -14,9 +14,9 @@ require (
|
||||
github.com/alibabacloud-go/apig-20240327/v3 v3.2.2
|
||||
github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.6
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.1
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.7
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.2
|
||||
github.com/alibabacloud-go/esa-20240910/v2 v2.36.0
|
||||
github.com/alibabacloud-go/fc-20230330/v4 v4.3.6
|
||||
github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12
|
||||
@@ -24,21 +24,21 @@ require (
|
||||
github.com/alibabacloud-go/live-20161101 v1.1.1
|
||||
github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3
|
||||
github.com/alibabacloud-go/slb-20140515/v4 v4.0.10
|
||||
github.com/alibabacloud-go/tea v1.3.10
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4
|
||||
github.com/alibabacloud-go/tea v1.3.11
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.9.2
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.6.0
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.33.1
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.47.0
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.43.1
|
||||
github.com/baidubce/bce-sdk-go v0.9.235
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.36.0
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.50.0
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.1
|
||||
github.com/baidubce/bce-sdk-go v0.9.240
|
||||
github.com/blinkbean/dingtalk v1.1.3
|
||||
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.53
|
||||
github.com/go-acme/lego/v4 v4.25.1
|
||||
github.com/go-acme/lego/v4 v4.25.2
|
||||
github.com/go-lark/lark v1.16.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.163
|
||||
github.com/jdcloud-api/jdcloud-sdk-go v1.64.0
|
||||
github.com/kong/go-kong v0.67.0
|
||||
github.com/libdns/dynv6 v1.0.0
|
||||
@@ -47,36 +47,36 @@ require (
|
||||
github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0
|
||||
github.com/pkg/sftp v1.13.9
|
||||
github.com/pocketbase/dbx v1.11.0
|
||||
github.com/pocketbase/pocketbase v0.29.0
|
||||
github.com/pocketbase/pocketbase v0.29.3
|
||||
github.com/povsister/scp v0.0.0-20250701154629-777cf82de5df
|
||||
github.com/qiniu/go-sdk/v7 v7.25.4
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1207
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1212
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1213
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.1.5
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.1.14
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.14
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1200
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1208
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1209
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1211
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1212
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1210
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1213
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.1.3
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.1.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.1.10
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.1.7
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.1.13
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.1.11
|
||||
github.com/ucloud/ucloud-sdk-go v0.22.45
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.18
|
||||
github.com/volcengine/volc-sdk-golang v1.0.216
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.23
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.21
|
||||
github.com/volcengine/volc-sdk-golang v1.0.217
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.28
|
||||
gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1
|
||||
gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||
k8s.io/api v0.33.3
|
||||
k8s.io/apimachinery v0.33.3
|
||||
k8s.io/client-go v0.33.3
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||
k8s.io/api v0.33.4
|
||||
k8s.io/apimachinery v0.33.4
|
||||
k8s.io/client-go v0.33.4
|
||||
software.sslmate.com/src/go-pkcs12 v0.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||
@@ -105,9 +105,10 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-yaml v1.9.8 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
@@ -140,6 +141,7 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.2 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.14.4 // indirect
|
||||
@@ -164,19 +166,19 @@ require (
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.6 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.71
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 // indirect
|
||||
github.com/aws/smithy-go v1.22.4 // indirect
|
||||
github.com/aws/smithy-go v1.22.5 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
@@ -192,7 +194,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -207,22 +209,22 @@ require (
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/image v0.29.0 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.65.10 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.38.0 // indirect
|
||||
modernc.org/sqlite v1.38.2 // indirect
|
||||
)
|
||||
|
||||
replace github.com/Edgio/edgio-api v0.0.0-workspace => ./pkg/forks/github.com/Edgio/edgio-api@v0.0.0-workspace
|
||||
|
||||
@@ -36,12 +36,18 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
@@ -102,6 +108,8 @@ github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2 h1:+KJOPukTM+xMyiLOW5qBwYKG2df
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2/go.mod h1:GnPiPL3HlzCi8SGiLiVgKrAFkP1vTtcF4yGtjsl4wfo=
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.6 h1:0NB4hiiqqF7etZ7WgsaO7fkaj79pEMGEUeb74ZGPRKE=
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.6/go.mod h1:5bdtTnhDmV4jXyIinHY15bJrUl8TwMIhrICNQBy+MD8=
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.7 h1:+NCbioja/R33VQdqA35X7AVuYrpkCZ6uDVQ+j1GkSjY=
|
||||
github.com/alibabacloud-go/cloudapi-20160714/v5 v5.7.7/go.mod h1:6fBf26RoBBpSq3dMPro5UnOw34+dk5PnikcynJX9opU=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
|
||||
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
|
||||
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
|
||||
@@ -117,6 +125,8 @@ github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcw
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8/go.mod h1:d+z3ScRqc7PFzg4h9oqE3h8yunRZvAvU7u+iuPYEhpU=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 h1:7P0KWfed/YMtpeuW3E2iwokzoz9L7H9rB+VZzg5DeBs=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9/go.mod h1:kgnXaV74AVjM3ZWJu1GhyXGuCtxljJ677oUfz6MyJOE=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 h1:5JIs4BPVpzbgcBrgmUxBdDsMjmsHZvPbTPD1m4aB+ZQ=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11/go.mod h1:ue0+WkdPxpCB2JP3iaG4Iawayxp72kyT5uDbozQKaW8=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||
@@ -125,6 +135,8 @@ github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0 h1:EQmKhYju6y38kJ1ZvZROeJG2Q1
|
||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0/go.mod h1:b9qzvr/2V1f0r1Z6xUmkLqEouKcPGy4LCC22yV+6HQo=
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.1 h1:KD17N7f83BO+bSKfPU7Mg2pSfIiBCbBlHL14Zd3S/a0=
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.1/go.mod h1:iXO5nADvrdxU7Lek8DjNrWG7yD7EV6X18kewPUKBC2c=
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.2 h1:KvHAZ1yCJ6NnF6aeqhR0e8kuc7zCrUAkS7+2RyQaJJU=
|
||||
github.com/alibabacloud-go/ddoscoo-20200101/v4 v4.0.2/go.mod h1:fBEEvkfQEp5k7yfMzU4n0zWcibHDoGfakv3YaopBdiI=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
|
||||
@@ -166,6 +178,8 @@ github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZL
|
||||
github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
||||
github.com/alibabacloud-go/tea v1.3.10 h1:J0Ke8iMyoxX2daj90hdPr1QgfxJnhR8SOflB910o/Dk=
|
||||
github.com/alibabacloud-go/tea v1.3.10/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
||||
github.com/alibabacloud-go/tea v1.3.11 h1:F7s2HRszY0J+tFckhy5FCpnBEENTijgFcYR68Brg9/Y=
|
||||
github.com/alibabacloud-go/tea v1.3.11/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
||||
github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
|
||||
github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
|
||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
|
||||
@@ -190,6 +204,8 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4 h1:MYP2xfrcud8vlWljQ4lhemNgAgi9/AUAa450n8TUXZo=
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.8.4/go.mod h1:5ocQ6hIc9tpGixD2iy099aOGwIgpzjT2le4Krd4aLn8=
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.9.2 h1:4TojRyj2k/k0Vs9j0SVRR3BJbdTXMEZhJy7poFs3ZkM=
|
||||
github.com/alibabacloud-go/vod-20170321/v4 v4.9.2/go.mod h1:3iJRCLBKjbMhYXt2sUbvz5vUdMuynms3cMTKNQ9nIdM=
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.6.0 h1:jqiPGtZJhdnGnni6EKIrUY9yhwOo37qz1IeX0bkfBbk=
|
||||
github.com/alibabacloud-go/waf-openapi-20211001/v5 v5.6.0/go.mod h1:9itYSTzipL3NlvhvNYfTjFaapoZzG68nlu/KUdh9SpA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
@@ -216,6 +232,8 @@ github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm
|
||||
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA=
|
||||
@@ -224,17 +242,27 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.33.1 h1:VAAadBIWgoYoS0tRWkghu1E2LfSsKQw8m/sOkdF1D3E=
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.33.1/go.mod h1:eq3JsAPGHsNfhRbPoVRUVDxtQFynlnFcDXzxFMEeOdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.36.0 h1:U16SZFwZpyQGXUyrrmOqWqU9jMYhokCSpc+fYajYLy0=
|
||||
github.com/aws/aws-sdk-go-v2/service/acm v1.36.0/go.mod h1:fdYDfiFuQij96Ryxl5uJK5xGAjyLhHGiBwquH7mpuAc=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.47.0 h1:RyGeARqe2WTJU1K7L7rFQuCqLscbzQoi1oy7LxnROIE=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.47.0/go.mod h1:0zgTNyuzL2+HfnkP+w8Z+eKtKu7KbOTWuywJYdjkWfY=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.50.0 h1:PN9qG49RrQ5b9in9ZfHqY3LxVEKoURo0Ia0LMjzFkw8=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.50.0/go.mod h1:HLzQI9ENSq0pNCO+ASh5KbwL7AoYBqPkTLv1Y40+pl4=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.43.1 h1:xpPZZpbmqIJse9OH+Kf/bW/n+bRe0BtE/LtHvBJYcbc=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.43.1/go.mod h1:/IEkOg5Gkv2HFxOb3Prs84xpRyxO9P/9Zow/clWl84Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.1 h1:8qIz2VOP22KhWlMhh2nZOlvQjXHcZ1jIYy/LmP1r0go=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.1/go.mod h1:t7ahGe9ZaK9mmtYhCMjVA6euun4iNzaeDnJyONTBlms=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg=
|
||||
@@ -250,8 +278,12 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
|
||||
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/baidubce/bce-sdk-go v0.9.235 h1:iAi+seH9w1Go2szFNzyGumahLGDsuYZ3i8hduX3qiM8=
|
||||
github.com/baidubce/bce-sdk-go v0.9.235/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
github.com/baidubce/bce-sdk-go v0.9.240 h1:vw0BDkg+uV47tqYXqwgsQnCEIAmKEvq/4lprs6bH0bg=
|
||||
github.com/baidubce/bce-sdk-go v0.9.240/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
@@ -329,6 +361,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
@@ -353,6 +386,8 @@ github.com/go-acme/alidns-20150109/v4 v4.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zp
|
||||
github.com/go-acme/alidns-20150109/v4 v4.5.10/go.mod h1:qGRq8kD0xVgn82qRSQmhHwh/oWxKRjF4Db5OI4ScV5g=
|
||||
github.com/go-acme/lego/v4 v4.25.1 h1:AYPUM7quPN/g2PcjjWw8sAMz3eV+Z8UWkr1kitDOyVA=
|
||||
github.com/go-acme/lego/v4 v4.25.1/go.mod h1:OORYyVNZPaNdIdVYCGSBNRNZDIjhQbPuFxwGDgWj/yM=
|
||||
github.com/go-acme/lego/v4 v4.25.2 h1:+D1Q+VnZrD+WJdlkgUEGHFFTcDrwGlE7q24IFtMmHDI=
|
||||
github.com/go-acme/lego/v4 v4.25.2/go.mod h1:OORYyVNZPaNdIdVYCGSBNRNZDIjhQbPuFxwGDgWj/yM=
|
||||
github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM=
|
||||
github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@@ -392,6 +427,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
@@ -410,6 +446,8 @@ github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncV
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
|
||||
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
@@ -420,6 +458,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -546,6 +586,8 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159 h1:6LZysc4iyO4cHB1aJsRklWfSEJr8CEhW7BmcM0SkYcU=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.163 h1:2OAYy5J1sJXjVTqucSDqf4VSd8Pzf10ZuyzK2HTTqUU=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.163/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI=
|
||||
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -580,6 +622,8 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
@@ -743,6 +787,10 @@ github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU
|
||||
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||
github.com/pocketbase/pocketbase v0.29.0 h1:oL6qvkU2QSybClVtQdaq9Z1F3Wk59iKYCfIaf1R8KUs=
|
||||
github.com/pocketbase/pocketbase v0.29.0/go.mod h1:SqyH7o/3e+/uLySATlJqxH4S8gyU6R0adG56ZSV1vuU=
|
||||
github.com/pocketbase/pocketbase v0.29.2 h1:MghVgLYy/xh9lBwHtteNSYjYOvHKYD+dS9pzUzOP79Q=
|
||||
github.com/pocketbase/pocketbase v0.29.2/go.mod h1:QZPKtMCWfiDJb0aLhwgj7ZOr6O8tusbui2EhTFAHThU=
|
||||
github.com/pocketbase/pocketbase v0.29.3 h1:Mj8o5awsbVJIdIoTuQNhfC2oL/c4aImQ3RyfFZlzFVg=
|
||||
github.com/pocketbase/pocketbase v0.29.3/go.mod h1:oGpT67LObxCFK4V2fSL7J9YnPbBnnshOpJ5v3zcneww=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/povsister/scp v0.0.0-20250701154629-777cf82de5df h1:zEgSHrxo8f6hGG1xCaqunfBq8hlfDmFd1JM0QXiQi7o=
|
||||
@@ -791,6 +839,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -836,8 +885,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1207 h1:FO4Q4UNwLP6achx7Y8EblzeVDG4BW66aGG4Hk7y5r9U=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1207/go.mod h1:YQW7rko8ub/X7RJWS/Yqb8fG30Wk4EEmnZI7GTnKBp4=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.1.5 h1:d1Qxqq2eHHGkNhUS1lUmgNnyJMVz0Eg+n8gIqTMxA+8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.1.5/go.mod h1:ITOgZuYkf3FdQJceKzaBwUhG535JaqSA5Nog4mM/e1I=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1212 h1:PrOVwyGIVITOP51HzfBqiD3PDrWJ3ZrijsiYyEj8Wn4=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1212/go.mod h1:EDYceO3jwZ8FU7LWjXpZGeCO4Ti3iqeg2j2NZTMBHQs=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.1.14 h1:AyCLbxE55tuoei9fhlhGVrd9yiU/PdyDOyMQYFvRxJU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.1.14/go.mod h1:zHhSaVjo4w4+toT8XsV6TCT5N5gtPOg538L7Aay7U+E=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1200/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1207/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
@@ -847,20 +900,41 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1211/go.mod
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1213 h1:ChLfbQsFFiBoolNyZ4Umvrg90QrdsnYH0KiKZwOhLkE=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1213/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.0/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.3/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.5/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.7/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.11/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.13/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.14 h1:+I+n8wDpnG95yLnV3rtT4MUs8gdwgbpuZEVKQhSxqxs=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.14/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1200 h1:SqfiAwdDQx1SG2XKMBBxwUTZSBlVNFCVt/hynkl6JHM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/gaap v1.0.1200/go.mod h1:zwjcd7dfjsmAPD7yEHHY36tnDM5ts5rUgThXGvsZ+OE=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1208 h1:GLCCKdL1yWCSAF7hinVnOmOGgMfKXDrWkOuvhAhIg8I=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1208/go.mod h1:0TssSKHt8SCgjMTaxr1Fjfrp+OUTqp+tkglNoXIkCyM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.1.3 h1:z8cdM+64rsQ6DbG6aXG9NaP/CldF+lgpZwli0qFnuVc=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.1.3/go.mod h1:O9AbLV8J4wdm1pEx66qy7UbACGE8NO2UxucPPDNMcDQ=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1209 h1:2cYGQZktchIE3jjn1qglPwSCGg2kaQBW8k5xbUIEWIw=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1209/go.mod h1:u14H5ciHEeHMPQL1e6HwHf5gAHc4h65c5gDMaF4dF38=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.1.0 h1:tBrYIRge/u1vOXLDuFBbiP3uv59I7JUekhkCzM8KQ5A=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.1.0/go.mod h1:q904dfEmrK/ETugTjgL7j1spCT08ioeLUV5JA4Bj8Xk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1211 h1:SXEmqWgiSln9UDElTFz2XwZAwxLS2F92fOeWygMFA9I=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1211/go.mod h1:k0C4B9RPQcL++fMFS32d8nAl6ApXs8+Nfusk1HugCis=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.1.10 h1:dj7PUBNAK1D4aSWxSxAbPxmJ12M8Etos8lYVJWyIdGs=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.1.10/go.mod h1:Xt/9WKwki9CCfSAG8e848Sk54HfNa9ZApFhLHxBr1W8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1212 h1:LUeCZrOPScN5HVRvT+Vz+MJ0aOHE0kqhkCy/UXdzRJc=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1212/go.mod h1:mWIN9z1o7n3T59FAu0bpPq2iKG4VcmHHWTe74Cz6wN0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.1.7 h1:2nuXJAtcySCGhKyv/LOFA04v9DtukYk6U0Xc3ZZU0pA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.1.7/go.mod h1:uqAhbeIHHrk9eb3D4pFIREq1kY0y+AjXY26pNj5N2Zo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1210 h1:DbRK20aFfzpnFEUbzuBaeMmIMrpQs5LCZIZlPTmvJ68=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.1210/go.mod h1:8Gy21lo1a5wUAzrzhmgXF/4zhulI5sbW3HwGt5i5qzc=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.1.13 h1:eU1aa13vM0YWPP4DC/2HSeAVGP+U/Qdqb/syrepIroI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.1.13/go.mod h1:q8AXovSiUhSct/xncaNrz1Br0Rfg0IiZrLaRIuYz3rI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1213 h1:/2TWW5pHhxHcft+aj9tLS3o+s8ZfrYaXC+eZo1eMyko=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1213/go.mod h1:xzGeque72VBwuOin47PdjPh5axADALnYc9WR5g/plBU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.1.11 h1:2jrTSxfC4AX0qv8rrK2Cpay+hz1FJlii2jNMlPFlnPI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.1.11/go.mod h1:JY5OQafRSIAh/s+YIT8mgnZbqiq+EjdFgDriJLMThf0=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -878,11 +952,17 @@ github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.18 h1:uint/YlMWuQ5et0ab+AOHq+re+yi8pOvjS77gker8ms=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.18/go.mod h1:IrjK84IJJTuOZOTMv/P18Ydjy/x+ow7fF7q11jAxXLM=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.21 h1:t6OkemY2V5wi6YNWB5ezqTrQM+/+bu0Bf35H9XrP5kQ=
|
||||
github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.21/go.mod h1:IrjK84IJJTuOZOTMv/P18Ydjy/x+ow7fF7q11jAxXLM=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.216 h1:+wAq8RvxpGECveRJaAXZFpzrZoQ33WjMuRyd9iY2Oc0=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.216/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.217 h1:ylONAeFRGTRc5D0s2ASvekaoQsKDD1fd+w41IgAoVvk=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.217/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.23 h1:wa4ytrLa1UBc/+/R4hEJDtBYD+TpHxLLh5+G+J1x1Rg=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.23/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.28 h1:hDErsr+6LDlX++Wfjsmra4bHAx8T0IbkNChG9VsOjjM=
|
||||
github.com/volcengine/volcengine-go-sdk v1.1.28/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
@@ -955,6 +1035,8 @@ golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5D
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -970,6 +1052,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -1003,6 +1087,8 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1060,6 +1146,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1147,6 +1235,7 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1167,6 +1256,8 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1184,6 +1275,8 @@ golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1204,6 +1297,8 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1267,9 +1362,12 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
@@ -1407,10 +1505,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
|
||||
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
|
||||
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
|
||||
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
|
||||
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
|
||||
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
|
||||
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
|
||||
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
|
||||
k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=
|
||||
k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
@@ -1428,6 +1532,8 @@ modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
|
||||
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -1438,6 +1544,8 @@ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type certificateRepository interface {
|
||||
ListExpireSoon(ctx context.Context) ([]*domain.Certificate, error)
|
||||
ListExpiringSoon(ctx context.Context) ([]*domain.Certificate, error)
|
||||
GetById(ctx context.Context, id string) (*domain.Certificate, error)
|
||||
DeleteWhere(ctx context.Context, exprs ...dbx.Expression) (int, error)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package domain
|
||||
|
||||
type Statistics struct {
|
||||
CertificateTotal int `json:"certificateTotal"`
|
||||
CertificateExpireSoon int `json:"certificateExpireSoon"`
|
||||
CertificateExpired int `json:"certificateExpired"`
|
||||
CertificateTotal int `json:"certificateTotal"`
|
||||
CertificateExpiringSoon int `json:"certificateExpiringSoon"`
|
||||
CertificateExpired int `json:"certificateExpired"`
|
||||
|
||||
WorkflowTotal int `json:"workflowTotal"`
|
||||
WorkflowEnabled int `json:"workflowEnabled"`
|
||||
|
||||
+32
-20
@@ -93,6 +93,7 @@ const (
|
||||
WorkflowNodeTypeTryCatch = WorkflowNodeType("tryCatch")
|
||||
WorkflowNodeTypeTryBlock = WorkflowNodeType("tryBlock")
|
||||
WorkflowNodeTypeCatchBlock = WorkflowNodeType("catchBlock")
|
||||
WorkflowNodeTypeDelay = WorkflowNodeType("delay")
|
||||
WorkflowNodeTypeBizApply = WorkflowNodeType("bizApply")
|
||||
WorkflowNodeTypeBizUpload = WorkflowNodeType("bizUpload")
|
||||
WorkflowNodeTypeBizMonitor = WorkflowNodeType("bizMonitor")
|
||||
@@ -101,12 +102,36 @@ const (
|
||||
)
|
||||
|
||||
type WorkflowNodeData struct {
|
||||
Name string `json:"name"`
|
||||
Config WorkflowNodeConfig `json:"config"`
|
||||
Name string `json:"name"`
|
||||
Disabled bool `json:"disabled,omitempty,omitzero"`
|
||||
Config WorkflowNodeConfig `json:"config,omitempty,omitzero"`
|
||||
}
|
||||
|
||||
type WorkflowNodeConfig map[string]any
|
||||
|
||||
func (c WorkflowNodeConfig) AsDelay() WorkflowNodeConfigForDelay {
|
||||
return WorkflowNodeConfigForDelay{
|
||||
Wait: xmaps.GetInt32(c, "wait"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c WorkflowNodeConfig) AsBranchBlock() WorkflowNodeConfigForBranchBlock {
|
||||
expression := c["expression"]
|
||||
if expression == nil {
|
||||
return WorkflowNodeConfigForBranchBlock{}
|
||||
}
|
||||
|
||||
exprRaw, _ := json.Marshal(expression)
|
||||
expr, err := expr.UnmarshalExpr([]byte(exprRaw))
|
||||
if err != nil {
|
||||
return WorkflowNodeConfigForBranchBlock{}
|
||||
}
|
||||
|
||||
return WorkflowNodeConfigForBranchBlock{
|
||||
Expression: expr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c WorkflowNodeConfig) AsBizApply() WorkflowNodeConfigForBizApply {
|
||||
return WorkflowNodeConfigForBizApply{
|
||||
Domains: xmaps.GetString(c, "domains"),
|
||||
@@ -169,21 +194,12 @@ func (c WorkflowNodeConfig) AsBizNotify() WorkflowNodeConfigForBizNotify {
|
||||
}
|
||||
}
|
||||
|
||||
func (c WorkflowNodeConfig) AsBranchBlock() WorkflowNodeConfigForBranchBlock {
|
||||
expression := c["expression"]
|
||||
if expression == nil {
|
||||
return WorkflowNodeConfigForBranchBlock{}
|
||||
}
|
||||
type WorkflowNodeConfigForDelay struct {
|
||||
Wait int32 `json:"wait"` // 等待时间
|
||||
}
|
||||
|
||||
exprRaw, _ := json.Marshal(expression)
|
||||
expr, err := expr.UnmarshalExpr([]byte(exprRaw))
|
||||
if err != nil {
|
||||
return WorkflowNodeConfigForBranchBlock{}
|
||||
}
|
||||
|
||||
return WorkflowNodeConfigForBranchBlock{
|
||||
Expression: expr,
|
||||
}
|
||||
type WorkflowNodeConfigForBranchBlock struct {
|
||||
Expression expr.Expr `json:"expression"` // 条件表达式
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForBizApply struct {
|
||||
@@ -236,7 +252,3 @@ type WorkflowNodeConfigForBizNotify struct {
|
||||
Message string `json:"message"` // 通知内容
|
||||
SkipOnAllPrevSkipped bool `json:"skipOnAllPrevSkipped"` // 前序节点均已跳过时是否跳过
|
||||
}
|
||||
|
||||
type WorkflowNodeConfigForBranchBlock struct {
|
||||
Expression expr.Expr `json:"expression"` // 条件表达式
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewCertificateRepository() *CertificateRepository {
|
||||
return &CertificateRepository{}
|
||||
}
|
||||
|
||||
func (r *CertificateRepository) ListExpireSoon(ctx context.Context) ([]*domain.Certificate, error) {
|
||||
func (r *CertificateRepository) ListExpiringSoon(ctx context.Context) ([]*domain.Certificate, error) {
|
||||
records, err := app.GetApp().FindAllRecords(
|
||||
domain.CollectionNameCertificate,
|
||||
dbx.NewExp("validityNotAfter>DATETIME('now')"),
|
||||
|
||||
@@ -28,15 +28,15 @@ func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, err
|
||||
rs.CertificateTotal = certTotal.Total
|
||||
|
||||
// 即将过期证书
|
||||
certExpireSoonTotal := struct {
|
||||
certExpiringSoonTotal := struct {
|
||||
Total int `db:"total"`
|
||||
}{}
|
||||
if err := app.GetDB().
|
||||
NewQuery("SELECT COUNT(*) AS total FROM certificate WHERE validityNotAfter > DATETIME('now') and validityNotAfter < DATETIME('now', '+20 days') AND deleted = ''").
|
||||
One(&certExpireSoonTotal); err != nil {
|
||||
One(&certExpiringSoonTotal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs.CertificateExpireSoon = certExpireSoonTotal.Total
|
||||
rs.CertificateExpiringSoon = certExpiringSoonTotal.Total
|
||||
|
||||
// 已过期证书
|
||||
certExpiredTotal := struct {
|
||||
|
||||
@@ -2,10 +2,12 @@ package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/internal/domain/dtos"
|
||||
"github.com/certimate-go/certimate/internal/rest/resp"
|
||||
)
|
||||
@@ -36,6 +38,9 @@ func (handler *WorkflowHandler) run(e *core.RequestEvent) error {
|
||||
if err := e.BindBody(req); err != nil {
|
||||
return resp.Err(e, err)
|
||||
}
|
||||
if req.RunTrigger != domain.WorkflowTriggerTypeManual {
|
||||
return resp.Err(e, errors.New("invalid parameters: the value of 'trigger' must be 'manual'"))
|
||||
}
|
||||
|
||||
res, err := handler.service.StartRun(e.Request.Context(), req)
|
||||
if err != nil {
|
||||
|
||||
@@ -232,6 +232,10 @@ func (wd *workflowDispatcher) tryExecuteAsync(task *taskInfo) {
|
||||
return nil
|
||||
})
|
||||
we.OnNodeError(func(ctx context.Context, node *engine.Node, err error) error {
|
||||
if errors.Is(err, engine.ErrTerminated) || errors.Is(err, engine.ErrBlocksException) {
|
||||
return nil
|
||||
}
|
||||
|
||||
log := domain.WorkflowLog{}
|
||||
log.WorkflowId = task.WorkflowId
|
||||
log.RunId = task.RunId
|
||||
|
||||
@@ -62,7 +62,7 @@ func (we *workflowEngine) Invoke(ctx context.Context, workflowId string, runId s
|
||||
SetInputsManager(newInOutManager()).
|
||||
SetContext(ctx)
|
||||
if err := we.executeBlocks(wfCtx, runGraph.Nodes); err != nil {
|
||||
if !errors.Is(err, errInterrupted) {
|
||||
if !errors.Is(err, ErrTerminated) {
|
||||
we.fireOnErrorHooks(ctx, err)
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (we *workflowEngine) executeNode(wfCtx *WorkflowContext, node *Node) error
|
||||
|
||||
execCtx := newNodeExecutionContext(wfCtx, node)
|
||||
execRes, err := executor.Execute(execCtx)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, ErrTerminated) {
|
||||
we.fireOnNodeErrorHooks(wfCtx.ctx, node, err)
|
||||
return err
|
||||
}
|
||||
@@ -179,11 +179,15 @@ func (we *workflowEngine) executeNode(wfCtx *WorkflowContext, node *Node) error
|
||||
}
|
||||
}
|
||||
|
||||
if execRes.Interrupted {
|
||||
return errInterrupted
|
||||
if execRes.Terminated {
|
||||
return ErrTerminated
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && errors.Is(err, ErrTerminated) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -195,6 +199,11 @@ func (we *workflowEngine) executeBlocks(wfCtx *WorkflowContext, blocks []*Node)
|
||||
default:
|
||||
}
|
||||
|
||||
// 节点已禁用,直接跳过执行
|
||||
if node.Data.Disabled {
|
||||
continue
|
||||
}
|
||||
|
||||
err := we.executeNode(wfCtx, node)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -282,6 +291,7 @@ func NewWorkflowEngine() WorkflowEngine {
|
||||
}
|
||||
engine.executors[NodeTypeStart] = newStartNodeExecutor()
|
||||
engine.executors[NodeTypeEnd] = newEndNodeExecutor()
|
||||
engine.executors[NodeTypeDelay] = newDelayNodeExecutor()
|
||||
engine.executors[NodeTypeCondition] = newConditionNodeExecutor()
|
||||
engine.executors[NodeTypeBranchBlock] = newBranchBlockNodeExecutor()
|
||||
engine.executors[NodeTypeTryCatch] = newTryCatchNodeExecutor()
|
||||
|
||||
@@ -4,4 +4,9 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errInterrupted = errors.New("workflow engine: interrupted, may be ended")
|
||||
var (
|
||||
// 表示工作流引擎执行被中断,可能已结束
|
||||
ErrTerminated = errors.New("workflow engine: execution was terminated")
|
||||
// 表示工作流引擎在执行子节点时发生异常
|
||||
ErrBlocksException = errors.New("workflow engine: error occurred when executing blocks")
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func newNodeExecutionContext(wfCtx *WorkflowContext, node *Node) *NodeExecutionC
|
||||
type NodeExecutionResult struct {
|
||||
node *Node
|
||||
|
||||
Interrupted bool // 是否中断执行(通常由 End 节点主动触发)
|
||||
Terminated bool // 是否终止执行(通常由 End 节点主动触发)
|
||||
|
||||
variablesMtx sync.Mutex
|
||||
Variables []VariableState
|
||||
|
||||
@@ -33,7 +33,7 @@ func (ne *conditionNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeEx
|
||||
|
||||
err := engine.executeNode(execCtx.Clone(), node)
|
||||
if err != nil {
|
||||
if errors.Is(err, errInterrupted) {
|
||||
if errors.Is(err, ErrTerminated) {
|
||||
return execRes, err
|
||||
}
|
||||
errs = append(errs, err)
|
||||
@@ -41,7 +41,7 @@ func (ne *conditionNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeEx
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return execRes, fmt.Errorf("error occurred when executing child nodes: %w", errors.Join(errs...))
|
||||
return execRes, fmt.Errorf("%w: %w", ErrBlocksException, errors.Join(errs...))
|
||||
}
|
||||
|
||||
return execRes, nil
|
||||
@@ -92,7 +92,7 @@ func (ne *branchBlockNodeExecutor) Execute(execCtx *NodeExecutionContext) (*Node
|
||||
panic("impossible!")
|
||||
} else {
|
||||
if err := engine.executeBlocks(execCtx.Clone(), execCtx.Node.Blocks); err != nil {
|
||||
return execRes, err
|
||||
return execRes, fmt.Errorf("%w: %w", ErrBlocksException, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type delayNodeExecutor struct {
|
||||
nodeExecutor
|
||||
}
|
||||
|
||||
func (ne *delayNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExecutionResult, error) {
|
||||
execRes := newNodeExecutionResult(execCtx.Node)
|
||||
|
||||
nodeCfg := execCtx.Node.Data.Config.AsDelay()
|
||||
ne.logger.Info(fmt.Sprintf("delay for %d second(s) before continuing ...", nodeCfg.Wait))
|
||||
|
||||
time.Sleep(time.Duration(nodeCfg.Wait) * time.Second)
|
||||
|
||||
return execRes, nil
|
||||
}
|
||||
|
||||
func newDelayNodeExecutor() NodeExecutor {
|
||||
return &delayNodeExecutor{
|
||||
nodeExecutor: nodeExecutor{logger: slog.Default()},
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@ type endNodeExecutor struct {
|
||||
nodeExecutor
|
||||
}
|
||||
|
||||
func (e *endNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExecutionResult, error) {
|
||||
func (ne *endNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExecutionResult, error) {
|
||||
execRes := newNodeExecutionResult(execCtx.Node)
|
||||
execRes.Interrupted = true
|
||||
execRes.Terminated = true
|
||||
|
||||
ne.logger.Info("the is ending")
|
||||
|
||||
return execRes, nil
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ type startNodeExecutor struct {
|
||||
nodeExecutor
|
||||
}
|
||||
|
||||
func (e *startNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExecutionResult, error) {
|
||||
func (ne *startNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExecutionResult, error) {
|
||||
execRes := newNodeExecutionResult(execCtx.Node)
|
||||
|
||||
ne.logger.Info("")
|
||||
|
||||
return execRes, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func (ne *tryCatchNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExe
|
||||
|
||||
err := engine.executeNode(execCtx.Clone(), node)
|
||||
if err != nil {
|
||||
if errors.Is(err, errInterrupted) {
|
||||
if errors.Is(err, ErrTerminated) {
|
||||
return execRes, err
|
||||
}
|
||||
tryErrs = append(tryErrs, err)
|
||||
@@ -52,18 +52,17 @@ func (ne *tryCatchNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExe
|
||||
|
||||
err := engine.executeNode(execCtx.Clone(), node)
|
||||
if err != nil {
|
||||
if errors.Is(err, errInterrupted) {
|
||||
if errors.Is(err, ErrTerminated) {
|
||||
return execRes, err
|
||||
}
|
||||
catchErrs = append(catchErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(catchErrs) > 0 {
|
||||
return execRes, fmt.Errorf("error occurred when executing child nodes: %w", errors.Join(append(tryErrs, catchErrs...)...))
|
||||
}
|
||||
|
||||
return execRes, fmt.Errorf("error occurred when executing child nodes: %w", errors.Join(tryErrs...))
|
||||
errs := make([]error, 0)
|
||||
errs = append(errs, tryErrs...)
|
||||
errs = append(errs, catchErrs...)
|
||||
return execRes, fmt.Errorf("%w: %w", ErrBlocksException, errors.Join(errs...))
|
||||
}
|
||||
|
||||
return execRes, nil
|
||||
@@ -90,7 +89,7 @@ func (ne *tryBlockNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeExe
|
||||
execRes := newNodeExecutionResult(execCtx.Node)
|
||||
|
||||
if err := engine.executeBlocks(execCtx.Clone(), execCtx.Node.Blocks); err != nil {
|
||||
return execRes, err
|
||||
return execRes, fmt.Errorf("%w: %w", ErrBlocksException, err)
|
||||
}
|
||||
|
||||
return execRes, nil
|
||||
@@ -117,7 +116,7 @@ func (ne *catchBlockNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeE
|
||||
}
|
||||
|
||||
if err := engine.executeBlocks(execCtx.Clone(), execCtx.Node.Blocks); err != nil {
|
||||
return execRes, err
|
||||
return execRes, fmt.Errorf("%w: %w", ErrBlocksException, err)
|
||||
}
|
||||
|
||||
return execRes, nil
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
NodeTypeTryCatch = domain.WorkflowNodeTypeTryCatch
|
||||
NodeTypeTryBlock = domain.WorkflowNodeTypeTryBlock
|
||||
NodeTypeCatchBlock = domain.WorkflowNodeTypeCatchBlock
|
||||
NodeTypeDelay = domain.WorkflowNodeTypeDelay
|
||||
NodeTypeBizApply = domain.WorkflowNodeTypeBizApply
|
||||
NodeTypeBizUpload = domain.WorkflowNodeTypeBizUpload
|
||||
NodeTypeBizMonitor = domain.WorkflowNodeTypeBizMonitor
|
||||
|
||||
@@ -273,7 +273,6 @@ func newInOutManager() InOutManager {
|
||||
|
||||
const (
|
||||
stateIOTypeRef = "ref"
|
||||
stateIOType = "recordRef"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -81,7 +81,7 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if workflow.LastRunStatus == domain.WorkflowRunStatusTypePending || workflow.LastRunStatus == domain.WorkflowRunStatusTypeProcessing {
|
||||
if req.RunTrigger == domain.WorkflowTriggerTypeManual && (workflow.LastRunStatus == domain.WorkflowRunStatusTypePending || workflow.LastRunStatus == domain.WorkflowRunStatusTypeProcessing) {
|
||||
return nil, errors.New("workflow is already pending or processing")
|
||||
} else if workflow.GraphContent == nil {
|
||||
return nil, errors.New("workflow graph content is empty")
|
||||
|
||||
@@ -37,12 +37,17 @@ func init() {
|
||||
|
||||
// update collection `access`
|
||||
// - modify field `config` schema: rename property `defaultReceiver` to `receiver`
|
||||
// - modify field `reserve` candidates
|
||||
// - delete records: 'local'
|
||||
{
|
||||
collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if collection != nil {
|
||||
if _, err := app.DB().NewQuery("UPDATE access SET reserve = 'notif' WHERE reserve = 'notification'").Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := app.DB().NewQuery("DELETE FROM access WHERE provider = 'local'").Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Generated
+346
-348
File diff suppressed because it is too large
Load Diff
+15
-14
@@ -13,20 +13,20 @@
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.11.3",
|
||||
"@codemirror/legacy-modes": "^6.5.1",
|
||||
"@flowgram.ai/document": "^0.4.0",
|
||||
"@flowgram.ai/fixed-layout-editor": "^0.4.0",
|
||||
"@flowgram.ai/minimap-plugin": "^0.4.0",
|
||||
"@flowgram.ai/document": "0.4.3",
|
||||
"@flowgram.ai/fixed-layout-editor": "0.4.3",
|
||||
"@flowgram.ai/minimap-plugin": "0.4.3",
|
||||
"@tabler/icons-react": "^3.34.1",
|
||||
"@uiw/codemirror-extensions-basic-setup": "^4.25.1",
|
||||
"@uiw/codemirror-theme-vscode": "^4.25.1",
|
||||
"@uiw/react-codemirror": "^4.25.1",
|
||||
"ahooks": "^3.9.0",
|
||||
"antd": "^5.27.0",
|
||||
"ahooks": "^3.9.4",
|
||||
"antd": "^5.27.1",
|
||||
"antd-zod": "^7.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cron-parser": "^5.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^25.3.6",
|
||||
"i18next": "^25.4.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immer": "^10.1.1",
|
||||
"nanoid": "^5.1.5",
|
||||
@@ -35,14 +35,15 @@
|
||||
"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.8.1",
|
||||
"react-i18next": "^15.7.2",
|
||||
"react-router-dom": "^7.8.2",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zod": "^4.0.17",
|
||||
"zustand": "^5.0.7"
|
||||
"yaml": "^2.8.1",
|
||||
"zod": "^4.1.1",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@tailwindcss/postcss": "^4.1.12",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
@@ -53,10 +54,10 @@
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-legacy": "^6.1.1",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-better-tailwindcss": "^3.7.4",
|
||||
"eslint-plugin-better-tailwindcss": "^3.7.5",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
@@ -65,7 +66,7 @@
|
||||
"prettier": "^3.6.2",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ const ModalForm = <T extends NonNullable<unknown> = any>({
|
||||
onOk={handleOkClick}
|
||||
onCancel={handleCancelClick}
|
||||
>
|
||||
<div className="pt-4 pb-2">
|
||||
<div className="py-3">
|
||||
<Form className={className} style={style} {...mergedFormProps} form={formInst}>
|
||||
{children}
|
||||
</Form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { startTransition, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { useControllableValue, useGetState } from "ahooks";
|
||||
import { App, Button, Drawer, Flex } from "antd";
|
||||
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
@@ -145,8 +145,9 @@ const AccessEditDrawer = ({ afterClose, afterSubmit, mode, data, loading, trigge
|
||||
|
||||
const useDrawer = () => {
|
||||
type DataType = AccessEditDrawerProps["data"];
|
||||
const [data, setData] = useState<DataType>();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [data, setData, getData] = useGetState<DataType>();
|
||||
const [loading, setLoading] = useState<boolean>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
@@ -158,17 +159,34 @@ const useDrawer = () => {
|
||||
startTransition(() => {
|
||||
if (!open) {
|
||||
setData(void 0);
|
||||
setLoading(void 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
data,
|
||||
loading,
|
||||
open,
|
||||
onOpenChange,
|
||||
},
|
||||
|
||||
open: (data: NonNullable<DataType>) => {
|
||||
open: ({ data, loading }: { data: NonNullable<DataType>; loading?: boolean }) => {
|
||||
setData(data);
|
||||
setLoading(loading);
|
||||
setOpen(true);
|
||||
|
||||
return {
|
||||
safeUpdate: ({ data, loading }: { data?: NonNullable<DataType>; loading?: boolean }) => {
|
||||
if (data != null) {
|
||||
if (data.id !== getData()?.id) return; // 确保数据不脏读
|
||||
|
||||
setData(data);
|
||||
}
|
||||
|
||||
if (loading != null) {
|
||||
setLoading(loading);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
close: () => {
|
||||
setOpen(false);
|
||||
|
||||
@@ -334,7 +334,7 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
||||
getFieldsValue: () => {
|
||||
const values = formInst.getFieldsValue(true);
|
||||
values.config = nestedFormInst.getFieldsValue();
|
||||
values.reserve = usage === "ca" ? "ca" : usage === "notification" ? "notification" : void 0;
|
||||
values.reserve = usage === "ca" ? "ca" : usage === "notification" ? "notif" : void 0;
|
||||
return values;
|
||||
},
|
||||
resetFields: (fields) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { startTransition, useCallback, useState } from "react";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { useControllableValue, useGetState } from "ahooks";
|
||||
import { Button, Drawer, Flex } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
@@ -64,8 +64,9 @@ const CertificateDetailDrawer = ({ afterClose, data, loading, trigger, ...props
|
||||
|
||||
const useDrawer = () => {
|
||||
type DataType = CertificateDetailDrawerProps["data"];
|
||||
const [data, setData] = useState<DataType>();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [data, setData, getData] = useGetState<DataType>();
|
||||
const [loading, setLoading] = useState<boolean>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
@@ -77,17 +78,34 @@ const useDrawer = () => {
|
||||
startTransition(() => {
|
||||
if (!open) {
|
||||
setData(void 0);
|
||||
setLoading(void 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
data,
|
||||
loading,
|
||||
open,
|
||||
onOpenChange,
|
||||
},
|
||||
|
||||
open: (data: NonNullable<DataType>) => {
|
||||
open: ({ data, loading }: { data: NonNullable<DataType>; loading?: boolean }) => {
|
||||
setData(data);
|
||||
setLoading(loading);
|
||||
setOpen(true);
|
||||
|
||||
return {
|
||||
safeUpdate: ({ data, loading }: { data?: NonNullable<DataType>; loading?: boolean }) => {
|
||||
if (data != null) {
|
||||
if (data.id !== getData()?.id) return; // 确保数据不脏读
|
||||
|
||||
setData(data);
|
||||
}
|
||||
|
||||
if (loading != null) {
|
||||
setLoading(loading);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
close: () => {
|
||||
setOpen(false);
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FlowNodeBaseType, FlowNodeSplitType } from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconClipboard } from "@tabler/icons-react";
|
||||
import { App, Button, Form, Radio, Tooltip } from "antd";
|
||||
import { stringify as stringifyYaml } from "yaml";
|
||||
|
||||
import CodeInput from "@/components/CodeInput";
|
||||
import { type WorkflowGraph, type WorkflowNode } from "@/domain/workflow";
|
||||
|
||||
import { getAllNodeRegistries } from "./designer/nodes";
|
||||
|
||||
export type WorkflowGraphExportBoxFormats = "json" | "yaml";
|
||||
|
||||
export interface WorkflowGraphExportBoxProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
data: WorkflowGraph;
|
||||
}
|
||||
|
||||
const serialize = (graph: WorkflowGraph | undefined, format: WorkflowGraphExportBoxFormats): string | undefined => {
|
||||
if (!graph) return;
|
||||
|
||||
const nodeRegistries = getAllNodeRegistries();
|
||||
|
||||
const deepConvert = (node: WorkflowNode): Map<string, unknown> => {
|
||||
// 利用 Map 来保证字段序列化的有序性
|
||||
const map = new Map<string, unknown>([
|
||||
["id", node.id],
|
||||
["type", node.type],
|
||||
["name", node.data.name],
|
||||
]);
|
||||
|
||||
if (node.data.disabled != null) {
|
||||
if (node.data.disabled) {
|
||||
map.set("disabled", node.data.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.data.config != null) {
|
||||
map.set("config", node.data.config);
|
||||
}
|
||||
|
||||
if (node.blocks != null) {
|
||||
const branchLikeNodeTypes = [
|
||||
FlowNodeBaseType.BLOCK,
|
||||
FlowNodeSplitType.SIMPLE_SPLIT,
|
||||
FlowNodeSplitType.DYNAMIC_SPLIT,
|
||||
FlowNodeSplitType.STATIC_SPLIT,
|
||||
] as string[];
|
||||
const hasChildren = node.blocks.length > 0 || branchLikeNodeTypes.includes(nodeRegistries.find((r) => r.type === node.type)?.extend ?? "");
|
||||
if (hasChildren) {
|
||||
const childern = node.blocks.map((block) => deepConvert(block));
|
||||
map.set("blocks", childern);
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(node.data).forEach(([k, v]) => {
|
||||
if (k === "name" || k === "disabled" || k === "config") return;
|
||||
map.set(k, v);
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
const nodes = graph.nodes.map((node) => deepConvert(node));
|
||||
|
||||
let content: string = "";
|
||||
switch (format) {
|
||||
case "json":
|
||||
content = JSON.stringify(
|
||||
{ ...graph, nodes },
|
||||
(_, value) => {
|
||||
if (value instanceof Map) {
|
||||
return Object.fromEntries(value.entries());
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
2
|
||||
);
|
||||
break;
|
||||
|
||||
case "yaml":
|
||||
content = stringifyYaml(
|
||||
{ ...graph, nodes },
|
||||
{
|
||||
indent: 2,
|
||||
defaultKeyType: "PLAIN",
|
||||
defaultStringType: "QUOTE_DOUBLE",
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
const WorkflowGraphExportBox = ({ className, style, data }: WorkflowGraphExportBoxProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { message } = App.useApp();
|
||||
|
||||
const [format, setFormat] = useState<WorkflowGraphExportBoxFormats>("yaml");
|
||||
const [content, setContent] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
setContent(serialize(data, format));
|
||||
}, [data, format]);
|
||||
|
||||
return (
|
||||
<Form className={className} style={style} layout="vertical">
|
||||
<Form.Item className="mb-4" label={t("workflow.detail.design.action.export.form.format.label")}>
|
||||
<Radio.Group className="w-full" value={format} onChange={(e) => setFormat(e.target.value)}>
|
||||
<Radio.Button className="w-1/2 text-center" value="yaml">
|
||||
YAML
|
||||
</Radio.Button>
|
||||
<Radio.Button className="w-1/2 text-center" value="json">
|
||||
JSON
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("workflow.detail.design.action.export.form.content.label")}>
|
||||
<div className="absolute -top-[6px] right-0 -translate-y-full">
|
||||
<Tooltip title={t("common.button.copy")}>
|
||||
<CopyToClipboard
|
||||
text={content!}
|
||||
onCopy={() => {
|
||||
message.success(t("common.text.copied"));
|
||||
}}
|
||||
>
|
||||
<Button icon={<IconClipboard size="1.25em" />} disabled={!content} size="small" type="text" />
|
||||
</CopyToClipboard>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<CodeInput height="calc(min(60vh, 512px))" language={format} value={content} readOnly />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowGraphExportBox;
|
||||
@@ -0,0 +1,95 @@
|
||||
import { startTransition, useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Modal } from "antd";
|
||||
|
||||
import { type WorkflowGraph } from "@/domain/workflow";
|
||||
import { useTriggerElement } from "@/hooks";
|
||||
|
||||
import WorkflowGraphExportBox from "./WorkflowGraphExportBox";
|
||||
|
||||
export interface WorkflowGraphExportModalProps {
|
||||
afterClose?: () => void;
|
||||
data: WorkflowGraph;
|
||||
loading?: boolean;
|
||||
open?: boolean;
|
||||
trigger?: React.ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const WorkflowGraphExportModal = ({ afterClose, data, loading, trigger, ...props }: WorkflowGraphExportModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||
valuePropName: "open",
|
||||
defaultValuePropName: "defaultOpen",
|
||||
trigger: "onOpenChange",
|
||||
});
|
||||
|
||||
const triggerEl = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||
|
||||
const handleCancelClick = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{triggerEl}
|
||||
|
||||
<Modal
|
||||
afterClose={afterClose}
|
||||
closable
|
||||
destroyOnHidden
|
||||
footer={null}
|
||||
loading={loading}
|
||||
open={open}
|
||||
title={t("workflow.detail.design.action.export.modal.title")}
|
||||
width="768px"
|
||||
onCancel={handleCancelClick}
|
||||
>
|
||||
<div className="py-3 pb-0">
|
||||
<WorkflowGraphExportBox data={data} />
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useModal = () => {
|
||||
type DataType = WorkflowGraphExportModalProps["data"];
|
||||
const [data, setData] = useState<DataType>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
modalProps: {
|
||||
afterClose: () => {
|
||||
startTransition(() => {
|
||||
if (!open) {
|
||||
setData(void 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
data: data!,
|
||||
open,
|
||||
onOpenChange,
|
||||
},
|
||||
|
||||
open: ({ data }: { data: DataType }) => {
|
||||
setData(data);
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => {
|
||||
setOpen(false);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const _default = Object.assign(WorkflowGraphExportModal, {
|
||||
useModal,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -0,0 +1,249 @@
|
||||
import { forwardRef, useImperativeHandle } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form, Radio } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { z } from "zod";
|
||||
|
||||
import CodeInput from "@/components/CodeInput";
|
||||
import { WORKFLOW_NODE_TYPES, type WorkflowGraph, type WorkflowNode, type WorkflowNodeType } from "@/domain/workflow";
|
||||
import { useAntdForm } from "@/hooks";
|
||||
|
||||
export type WorkflowGraphImportInputBoxFormats = "json" | "yaml";
|
||||
|
||||
export interface WorkflowGraphImportInputBoxProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface WorkflowGraphImportInputBoxInstance {
|
||||
validate: () => Promise<WorkflowGraph | undefined>;
|
||||
}
|
||||
|
||||
const deserialize = (content: string | undefined, format: WorkflowGraphImportInputBoxFormats): WorkflowGraph | undefined => {
|
||||
if (!content?.trim()) return;
|
||||
|
||||
let temp: any;
|
||||
switch (format) {
|
||||
case "json":
|
||||
temp = JSON.parse(content);
|
||||
break;
|
||||
|
||||
case "yaml":
|
||||
temp = parseYaml(content);
|
||||
break;
|
||||
}
|
||||
|
||||
const deepParse = (item: any): WorkflowNode => {
|
||||
item = item ?? {};
|
||||
|
||||
const node: WorkflowNode = {
|
||||
id: item.id?.toString() ?? "",
|
||||
type: (item.type?.toString() ?? "") as WorkflowNodeType,
|
||||
data: {
|
||||
name: item.name?.toString() ?? "",
|
||||
disabled: item.disabled === true || item.disabled === "true",
|
||||
config: item.config,
|
||||
},
|
||||
blocks: Array.isArray(item.blocks) ? item.blocks.map((block: any) => deepParse(block)) : [],
|
||||
};
|
||||
|
||||
if (item.data != null) {
|
||||
Object.entries(item.data).forEach(([k, v]) => {
|
||||
if (k === "id" || k === "type" || k === "meta" || k === "blocks") return;
|
||||
if (k === "name" || k === "disabled" || k === "config") return;
|
||||
node.data[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
const nodes = Array.from(temp.nodes ?? []).map((item) => deepParse(item));
|
||||
|
||||
return { nodes: nodes };
|
||||
};
|
||||
|
||||
const WorkflowGraphImportInputBox = forwardRef<WorkflowGraphImportInputBoxInstance, WorkflowGraphImportInputBoxProps>(({ className, style }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
format: z.enum(["json", "yaml"]),
|
||||
content: z.string().refine((v) => !!v?.trim(), t("workflow.detail.design.action.import.form.content.errmsg.invalid")),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
let graph: WorkflowGraph | undefined;
|
||||
try {
|
||||
graph = deserialize(values.content, values.format);
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: t("workflow.detail.design.action.import.form.content.errmsg.invalid"),
|
||||
path: ["content"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (graph) {
|
||||
const errmsgs: string[] = [];
|
||||
|
||||
if (graph.nodes.at(0)?.type !== WORKFLOW_NODE_TYPES.START) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.first_node_start"));
|
||||
}
|
||||
|
||||
if (graph.nodes.at(-1)?.type !== WORKFLOW_NODE_TYPES.END) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.last_node_end"));
|
||||
}
|
||||
|
||||
let startNodeId: string | undefined;
|
||||
let startNodeDuplicated: boolean = false;
|
||||
const nodeIds = new Set<string>();
|
||||
const deepValidate = (node: WorkflowNode) => {
|
||||
// 验证字段:ID
|
||||
if (!/^(?![_-])[a-zA-Z0-9_-]{1,32}$/.test(node.id)) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.invalid_id", { nodeId: node.id }));
|
||||
}
|
||||
|
||||
// 验证字段:配置项
|
||||
if (node.data.config != null) {
|
||||
if (typeof node.data.config !== "object" || Array.isArray(node.data.config)) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.invalid_config", { nodeId: node.id }));
|
||||
}
|
||||
}
|
||||
|
||||
// 验证节点 ID 是否冲突
|
||||
if (nodeIds.has(node.id)) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.conflict_id", { nodeId: node.id }));
|
||||
} else {
|
||||
nodeIds.add(node.id);
|
||||
}
|
||||
|
||||
// 验证开始节点是否重复
|
||||
if (node.type === WORKFLOW_NODE_TYPES.START) {
|
||||
if (startNodeId) {
|
||||
if (!startNodeDuplicated) {
|
||||
startNodeDuplicated = true;
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.duplicate_start"));
|
||||
}
|
||||
} else {
|
||||
startNodeId = node.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 Condition 分支结构
|
||||
if (node.type === WORKFLOW_NODE_TYPES.CONDITION) {
|
||||
const blocks = node.blocks ?? [];
|
||||
const f1 = blocks.length > 0;
|
||||
const f2 = blocks.every((block) => block.type === WORKFLOW_NODE_TYPES.BRANCHBLOCK);
|
||||
if (!f1 || !f2) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.abnormal_condition_branch", { nodeId: node.id }));
|
||||
}
|
||||
} else if (node.type === WORKFLOW_NODE_TYPES.BRANCHBLOCK) {
|
||||
const blocks = node.blocks ?? [];
|
||||
const f1 = blocks.length > 0;
|
||||
const f2 = blocks.every((block) => block.type !== WORKFLOW_NODE_TYPES.BRANCHBLOCK);
|
||||
if (!f1 || !f2) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.abnormal_condition_branch", { nodeId: node.id }));
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 TryCatch 分支结构
|
||||
if (node.type === WORKFLOW_NODE_TYPES.TRYCATCH) {
|
||||
const blocks = node.blocks ?? [];
|
||||
const f1 = blocks.length >= 2;
|
||||
const f2 = blocks[0]?.type === WORKFLOW_NODE_TYPES.TRYBLOCK;
|
||||
const f3 = blocks.some((block) => block.type === WORKFLOW_NODE_TYPES.CATCHBLOCK);
|
||||
if (!f1 || !f2 || !f3) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.abnormal_try_catch_branch", { nodeId: node.id }));
|
||||
}
|
||||
} else if (node.type === WORKFLOW_NODE_TYPES.TRYBLOCK || node.type === WORKFLOW_NODE_TYPES.CATCHBLOCK) {
|
||||
const blocks = node.blocks ?? [];
|
||||
const f1 = blocks.length > 0;
|
||||
const f2 = blocks.every((block) => block.type !== WORKFLOW_NODE_TYPES.TRYBLOCK);
|
||||
const f3 = blocks.every((block) => block.type !== WORKFLOW_NODE_TYPES.CATCHBLOCK);
|
||||
if (!f1 || !f2 || !f3) {
|
||||
errmsgs.push(t("workflow.detail.design.action.import.form.content.errmsg.abnormal_try_catch_branch", { nodeId: node.id }));
|
||||
}
|
||||
}
|
||||
|
||||
// 验证子节点
|
||||
if (Array.isArray(node.blocks)) {
|
||||
node.blocks.forEach((block) => deepValidate(block));
|
||||
}
|
||||
};
|
||||
graph.nodes.forEach((node) => deepValidate(node));
|
||||
|
||||
const MAX_ISSUE_COUNT = 5;
|
||||
for (let i = 0; i < Math.min(MAX_ISSUE_COUNT, errmsgs.length); i++) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: errmsgs[i],
|
||||
path: ["content"],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm<z.infer<typeof formSchema>>({
|
||||
name: "workflowGraphExportInputBoxForm",
|
||||
initialValues: {
|
||||
format: "yaml",
|
||||
content: "",
|
||||
},
|
||||
});
|
||||
|
||||
const fieldFormat = Form.useWatch<WorkflowGraphImportInputBoxFormats>("format", formInst);
|
||||
const fieldContent = Form.useWatch<string>("content", formInst);
|
||||
|
||||
const handleFormatChange = (format: WorkflowGraphImportInputBoxFormats) => {
|
||||
formInst.setFieldValue("format", format);
|
||||
|
||||
switch (format) {
|
||||
case "json":
|
||||
try {
|
||||
JSON.parse(fieldContent);
|
||||
} catch {
|
||||
formInst.setFieldValue("content", "");
|
||||
}
|
||||
break;
|
||||
|
||||
case "yaml":
|
||||
try {
|
||||
parseYaml(fieldContent);
|
||||
} catch {
|
||||
formInst.setFieldValue("content", "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
validate: async () => {
|
||||
const formValues = await formInst.validateFields();
|
||||
return deserialize(formValues.content, formValues.format);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Form className={className} style={style} {...formProps} clearOnDestroy={true} form={formInst} layout="vertical" preserve={false} scrollToFirstError>
|
||||
<Form.Item className="mb-4" name="format" label={t("workflow.detail.design.action.import.form.format.label")} rules={[formRule]}>
|
||||
<Radio.Group className="w-full" onChange={(e) => handleFormatChange(e.target.value)}>
|
||||
<Radio.Button className="w-1/2 text-center" value="yaml">
|
||||
YAML
|
||||
</Radio.Button>
|
||||
<Radio.Button className="w-1/2 text-center" value="json">
|
||||
JSON
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="content" label={t("workflow.detail.design.action.import.form.content.label")} rules={[formRule]}>
|
||||
<CodeInput height="calc(min(60vh, 512px))" language={fieldFormat} value={fieldContent} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
export default WorkflowGraphImportInputBox;
|
||||
@@ -0,0 +1,119 @@
|
||||
import { startTransition, useCallback, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Button, Flex, Modal } from "antd";
|
||||
|
||||
import { type WorkflowGraph } from "@/domain/workflow";
|
||||
import { useTriggerElement } from "@/hooks";
|
||||
|
||||
import WorkflowImportExportForm, { type WorkflowGraphImportInputBoxInstance } from "./WorkflowGraphImportInputBox";
|
||||
|
||||
export interface WorkflowGraphImportModalProps {
|
||||
afterClose?: () => void;
|
||||
open?: boolean;
|
||||
trigger?: React.ReactNode;
|
||||
onCancel?: () => void;
|
||||
onOk?: (graph: WorkflowGraph) => void;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const WorkflowGraphImportModal = ({ afterClose, trigger, onCancel, onOk, ...props }: WorkflowGraphImportModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||
valuePropName: "open",
|
||||
defaultValuePropName: "defaultOpen",
|
||||
trigger: "onOpenChange",
|
||||
});
|
||||
|
||||
const triggerEl = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||
|
||||
const graphInputBoxRef = useRef<WorkflowGraphImportInputBoxInstance>(null);
|
||||
|
||||
const handleCancelClick = () => {
|
||||
setOpen(false);
|
||||
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
const handleOkClick = async () => {
|
||||
const graph = await graphInputBoxRef.current!.validate();
|
||||
|
||||
setOpen(false);
|
||||
|
||||
if (graph != null) {
|
||||
onOk?.(graph);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{triggerEl}
|
||||
|
||||
<Modal
|
||||
afterClose={afterClose}
|
||||
closable
|
||||
destroyOnHidden
|
||||
footer={
|
||||
<Flex className="px-2" justify="end" gap="small">
|
||||
<Button onClick={handleCancelClick}>{t("common.button.cancel")}</Button>
|
||||
<Button type="primary" onClick={handleOkClick}>
|
||||
{t("workflow.detail.design.action.import.modal.ok_button")}
|
||||
</Button>
|
||||
</Flex>
|
||||
}
|
||||
open={open}
|
||||
title={t("workflow.detail.design.action.import.modal.title")}
|
||||
width="768px"
|
||||
onCancel={handleCancelClick}
|
||||
>
|
||||
<div className="py-3">
|
||||
<WorkflowImportExportForm ref={graphInputBoxRef} />
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useModal = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [onOkHandler, setOnOkHandler] = useState<{ handler: WorkflowGraphImportModalProps["onOk"] }>();
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
modalProps: {
|
||||
afterClose: () => {
|
||||
startTransition(() => {
|
||||
if (!open) {
|
||||
setOnOkHandler(void 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
open,
|
||||
onOk: (graph: WorkflowGraph) => {
|
||||
onOkHandler?.handler?.(graph);
|
||||
},
|
||||
onOpenChange,
|
||||
},
|
||||
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers<WorkflowGraph>();
|
||||
setOnOkHandler({ handler: (graph) => resolve(graph) });
|
||||
return promise;
|
||||
},
|
||||
close: () => {
|
||||
setOpen(false);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const _default = Object.assign(WorkflowGraphImportModal, {
|
||||
useModal,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconBrowserShare, IconCheck, IconChevronRight, IconDownload, IconSettings2 } from "@tabler/icons-react";
|
||||
import { EditorState, FlowLayoutDefault } from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconBrowserShare, IconCheck, IconDots, IconDownload, IconSettings2, IconTransferOut } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Collapse, Divider, Dropdown, Empty, Skeleton, Spin, Table, type TableProps, Tooltip, Typography } from "antd";
|
||||
import { Alert, App, Button, Card, Divider, Dropdown, Empty, Skeleton, Spin, Table, type TableProps, Tooltip, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
@@ -17,6 +18,9 @@ import { listByWorkflowRunId as listLogsByWorkflowRunId } from "@/repository/wor
|
||||
import { mergeCls } from "@/utils/css";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
import WorkflowDesigner from "./designer/Designer";
|
||||
import WorkflowToolbar from "./designer/Toolbar";
|
||||
import WorkflowGraphExportModal from "./WorkflowGraphExportModal";
|
||||
import WorkflowStatus from "./WorkflowStatus";
|
||||
|
||||
export interface WorkflowRunDetailProps {
|
||||
@@ -25,21 +29,127 @@ export interface WorkflowRunDetailProps {
|
||||
data: WorkflowRunModel;
|
||||
}
|
||||
|
||||
const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<Show when={!!data}>
|
||||
<WorkflowRunLogs runId={data.id} runStatus={data.status} />
|
||||
</Show>
|
||||
const WorkflowRunDetail = ({ className, style, data }: WorkflowRunDetailProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
<Show when={!!data && data.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
||||
<Divider />
|
||||
<WorkflowRunArtifacts runId={data.id} />
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<Alert
|
||||
message={
|
||||
<div className="text-xs">
|
||||
{data.endedAt
|
||||
? t("workflow_run.base.description_with_time_cost", {
|
||||
trigger: t(`workflow_run.base.trigger.${data.trigger}`),
|
||||
startedAt: dayjs(data.startedAt).format("YYYY-MM-DD HH:mm:ss"),
|
||||
timeCost: dayjs(data.endedAt).diff(dayjs(data.startedAt), "second") + "s",
|
||||
})
|
||||
: t("workflow_run.base.description", {
|
||||
trigger: t(`workflow_run.base.trigger.${data.trigger}`),
|
||||
startedAt: dayjs(data.startedAt).format("YYYY-MM-DD HH:mm:ss"),
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
showIcon
|
||||
type={
|
||||
{
|
||||
[WORKFLOW_RUN_STATUSES.SUCCEEDED]: "success" as const,
|
||||
[WORKFLOW_RUN_STATUSES.FAILED]: "error" as const,
|
||||
[WORKFLOW_RUN_STATUSES.CANCELED]: "warning" as const,
|
||||
}[data.status] ?? ("info" as const)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mt-8">
|
||||
<Typography.Title level={5}>{t("workflow_run.process")}</Typography.Title>
|
||||
<WorkflowRunProcess runData={data} />
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<Typography.Title level={5}>{t("workflow_run.logs")}</Typography.Title>
|
||||
<WorkflowRunLogs runId={data.id} runStatus={data.status} />
|
||||
</div>
|
||||
|
||||
<Show when={data.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
||||
<div className="mt-8">
|
||||
<Typography.Title level={5}>{t("workflow_run.artifacts")}</Typography.Title>
|
||||
<WorkflowRunArtifacts runId={data.id} />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WorkflowRunProcess = ({ runData }: { runData: WorkflowRunModel }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const { modalProps: graphExportModalProps, ...graphExportModal } = WorkflowGraphExportModal.useModal();
|
||||
|
||||
const handleExportClick = () => {
|
||||
graphExportModal.open({ data: runData.graph! });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
className="size-full overflow-hidden"
|
||||
styles={{
|
||||
body: {
|
||||
position: "relative",
|
||||
height: "240px",
|
||||
padding: 0,
|
||||
cursor: "grab",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<WorkflowDesigner
|
||||
defaultEditorState={EditorState.STATE_MOUSE_FRIENDLY_SELECT.id}
|
||||
defaultLayout={FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT}
|
||||
initialData={runData.graph}
|
||||
readonly
|
||||
>
|
||||
<div className="absolute bottom-4 z-10 w-full px-4">
|
||||
<div className="container">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<WorkflowToolbar
|
||||
style={{
|
||||
backgroundColor: themeToken.colorBgContainer,
|
||||
borderRadius: themeToken.borderRadius,
|
||||
}}
|
||||
size="small"
|
||||
showMouseState={false}
|
||||
showLayout={false}
|
||||
showMinimap={false}
|
||||
showZoomLevel={false}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "export",
|
||||
label: t("workflow_run.process.menu.export"),
|
||||
icon: <IconTransferOut size="1.25em" />,
|
||||
onClick: handleExportClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button icon={<IconDots size="1.25em" />} size="small" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</WorkflowDesigner>
|
||||
</Card>
|
||||
|
||||
<WorkflowGraphExportModal {...graphExportModalProps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -131,7 +241,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
.map((group) => {
|
||||
const escape = (str: string) => str.replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
||||
return (
|
||||
group.name +
|
||||
`#${group.id} ${group.name}` +
|
||||
NEWLINE +
|
||||
group.records
|
||||
.map((record) => {
|
||||
@@ -156,49 +266,48 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{t("workflow_run.logs")}</Typography.Title>
|
||||
<div className="rounded-md bg-black text-stone-200">
|
||||
<div className="flex items-center gap-2 p-4">
|
||||
<div className="grow overflow-hidden">
|
||||
<WorkflowStatus value={runStatus} />
|
||||
</div>
|
||||
<div>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "show-timestamp",
|
||||
label: t("workflow_run.logs.menu.show_timestamps"),
|
||||
icon: <IconCheck className={showTimestamp ? "visible" : "invisible"} size="1.25em" />,
|
||||
onClick: () => setShowTimestamp(!showTimestamp),
|
||||
},
|
||||
{
|
||||
key: "show-whitespace",
|
||||
label: t("workflow_run.logs.menu.show_whitespaces"),
|
||||
icon: <IconCheck className={showWhitespace ? "visible" : "invisible"} size="1.25em" />,
|
||||
onClick: () => setShowWhitespace(!showWhitespace),
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "download-logs",
|
||||
label: t("workflow_run.logs.menu.download_logs"),
|
||||
icon: <IconDownload className="invisible" size="1.25em" />,
|
||||
onClick: handleDownloadClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button color="primary" icon={<IconSettings2 size="1.25em" />} ghost={browserTheme === "light"} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className="rounded-md bg-black text-stone-200">
|
||||
<div className="flex items-center gap-2 p-4">
|
||||
<div className="grow overflow-hidden">
|
||||
<WorkflowStatus value={runStatus} />
|
||||
</div>
|
||||
<div>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "show-timestamp",
|
||||
label: t("workflow_run.logs.menu.show_timestamps"),
|
||||
icon: <IconCheck className={showTimestamp ? "visible" : "invisible"} size="1.25em" />,
|
||||
onClick: () => setShowTimestamp(!showTimestamp),
|
||||
},
|
||||
{
|
||||
key: "show-whitespace",
|
||||
label: t("workflow_run.logs.menu.show_whitespaces"),
|
||||
icon: <IconCheck className={showWhitespace ? "visible" : "invisible"} size="1.25em" />,
|
||||
onClick: () => setShowWhitespace(!showWhitespace),
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "download-logs",
|
||||
label: t("workflow_run.logs.menu.download_logs"),
|
||||
icon: <IconDownload className="invisible" size="1.25em" />,
|
||||
onClick: handleDownloadClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button color="primary" icon={<IconSettings2 size="1.25em" />} ghost={browserTheme === "light"} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider className="my-0 bg-stone-800" />
|
||||
<Divider className="my-0 bg-stone-800" />
|
||||
|
||||
<div className="min-h-8 px-4 py-2">
|
||||
<Show
|
||||
when={!loading || listData.length > 0}
|
||||
fallback={
|
||||
@@ -207,32 +316,20 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
</Spin>
|
||||
}
|
||||
>
|
||||
<div className="py-2">
|
||||
<Collapse
|
||||
style={{ color: "inherit" }}
|
||||
bordered={false}
|
||||
defaultActiveKey={listData.map((group) => group.id)}
|
||||
expandIcon={({ isActive }) => <IconChevronRight className={mergeCls(isActive ? "rotate-90" : "", "transition-transform")} size="1.25em" />}
|
||||
items={listData.map((group) => {
|
||||
return {
|
||||
key: group.id,
|
||||
classNames: {
|
||||
header: "text-sm text-stone-200",
|
||||
body: "text-stone-200",
|
||||
},
|
||||
style: { color: "inherit", border: "none" },
|
||||
styles: {
|
||||
header: { color: "inherit" },
|
||||
},
|
||||
label: group.name,
|
||||
children: <div className="flex flex-col space-y-1">{group.records.map((record) => renderRecord(record))}</div>,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{listData.map((group) => {
|
||||
return (
|
||||
<div className="mb-3 text-stone-200">
|
||||
<div className="truncate text-xs leading-loose">
|
||||
<span className="font-mono text-stone-400">{`#${group.id}\u00A0`}</span>
|
||||
<span>{group.name}</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">{group.records.map((record) => renderRecord(record))}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -286,7 +383,7 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<CertificateModel[]>([]);
|
||||
const { loading: tableLoading } = useRequest(
|
||||
const { loading } = useRequest(
|
||||
() => {
|
||||
return listCertificatesByWorkflowRunId(runId);
|
||||
},
|
||||
@@ -309,20 +406,17 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{t("workflow_run.artifacts")}</Typography.Title>
|
||||
<Table<CertificateModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={tableLoading}
|
||||
locale={{
|
||||
emptyText: <Empty description={t("common.text.nodata")} image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey={(record) => record.id}
|
||||
size="small"
|
||||
/>
|
||||
</>
|
||||
<Table<CertificateModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
locale={{
|
||||
emptyText: <Empty description={t("common.text.nodata")} image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey={(record) => record.id}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { startTransition, useCallback, useState } from "react";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { useControllableValue, useGetState } from "ahooks";
|
||||
import { Button, Drawer, Flex } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
@@ -64,8 +64,9 @@ const WorkflowRunDetailDrawer = ({ afterClose, data, loading, trigger, ...props
|
||||
|
||||
const useDrawer = () => {
|
||||
type DataType = WorkflowRunDetailDrawerProps["data"];
|
||||
const [data, setData] = useState<DataType>();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [data, setData, getData] = useGetState<DataType>();
|
||||
const [loading, setLoading] = useState<boolean>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
@@ -77,17 +78,34 @@ const useDrawer = () => {
|
||||
startTransition(() => {
|
||||
if (!open) {
|
||||
setData(void 0);
|
||||
setLoading(void 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
data,
|
||||
loading,
|
||||
open,
|
||||
onOpenChange,
|
||||
},
|
||||
|
||||
open: (data: NonNullable<DataType>) => {
|
||||
open: ({ data, loading }: { data: NonNullable<DataType>; loading?: boolean }) => {
|
||||
setData(data);
|
||||
setLoading(loading);
|
||||
setOpen(true);
|
||||
|
||||
return {
|
||||
safeUpdate: ({ data, loading }: { data?: NonNullable<DataType>; loading?: boolean }) => {
|
||||
if (data != null) {
|
||||
if (data.id !== getData()?.id) return; // 确保数据不脏读
|
||||
|
||||
setData(data);
|
||||
}
|
||||
|
||||
if (loading != null) {
|
||||
setLoading(loading);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
close: () => {
|
||||
setOpen(false);
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface DesignerProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
defaultEditorState?: string;
|
||||
defaultLayout?: string;
|
||||
initialData?: FlowDocumentJSON;
|
||||
readonly?: boolean;
|
||||
onDocumentChange?: (ctx: FixedLayoutPluginContext) => void;
|
||||
@@ -39,7 +41,7 @@ export interface DesignerInstance extends FixedLayoutPluginContext {
|
||||
}
|
||||
|
||||
const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
({ className, style, children, initialData, readonly, onDocumentChange, onNodeChange, onNodeClick }, ref) => {
|
||||
({ className, style, children, defaultEditorState, defaultLayout, initialData, readonly, onDocumentChange, onNodeChange, onNodeClick }, ref) => {
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const rendered = useRef(false);
|
||||
@@ -47,6 +49,8 @@ const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
const flowgramEditorRef = useRef<FixedLayoutPluginContext>(null);
|
||||
const flowgramEditorProps = useMemo<FixedLayoutProps>(
|
||||
() => ({
|
||||
defaultLayout: defaultLayout,
|
||||
|
||||
initialData: initialData,
|
||||
|
||||
constants: {
|
||||
@@ -121,9 +125,13 @@ const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
],
|
||||
|
||||
onInit: (ctx) => {
|
||||
const maybeMobile = ["android", "ios", "iphone", "ipad", "micromessenger"].some((s) => navigator.userAgent.includes(s));
|
||||
if (maybeMobile) {
|
||||
ctx.playground.editorState.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id);
|
||||
if (defaultEditorState != null) {
|
||||
ctx.playground.editorState.changeState(defaultEditorState);
|
||||
} else {
|
||||
const maybeMobile = ["android", "ios", "iphone", "ipad", "micromessenger"].some((s) => navigator.userAgent.includes(s));
|
||||
if (maybeMobile) {
|
||||
ctx.playground.editorState.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -136,7 +144,7 @@ const Designer = forwardRef<DesignerInstance, DesignerProps>(
|
||||
}, 1);
|
||||
},
|
||||
}),
|
||||
[themeToken, initialData, readonly, onDocumentChange]
|
||||
[defaultEditorState, defaultLayout, initialData, readonly, onDocumentChange, themeToken]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import BizMonitorNodeConfigDrawer from "./forms/BizMonitorNodeConfigDrawer";
|
||||
import BizNotifyNodeConfigDrawer from "./forms/BizNotifyNodeConfigDrawer";
|
||||
import BizUploadNodeConfigDrawer from "./forms/BizUploadNodeConfigDrawer";
|
||||
import BranchBlockNodeConfigDrawer from "./forms/BranchBlockNodeConfigDrawer";
|
||||
import DelayNodeConfigDrawer from "./forms/DelayNodeConfigDrawer";
|
||||
import StartNodeConfigDrawer from "./forms/StartNodeConfigDrawer";
|
||||
import { NodeType } from "./nodes/typings";
|
||||
|
||||
@@ -50,6 +51,10 @@ const NodeDrawer = ({ node, trigger, ...props }: NodeDrawerProps) => {
|
||||
|
||||
{node?.flowNodeType === NodeType.Start ? (
|
||||
<StartNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.Delay ? (
|
||||
<DelayNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.BranchBlock ? (
|
||||
<BranchBlockNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.BizApply ? (
|
||||
<BizApplyNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.BizUpload ? (
|
||||
@@ -60,8 +65,6 @@ const NodeDrawer = ({ node, trigger, ...props }: NodeDrawerProps) => {
|
||||
<BizDeployNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.BizNotify ? (
|
||||
<BizNotifyNodeConfigDrawer {...drawerProps} />
|
||||
) : node?.flowNodeType === NodeType.BranchBlock ? (
|
||||
<BranchBlockNodeConfigDrawer {...drawerProps} />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
@@ -72,7 +75,7 @@ const NodeDrawer = ({ node, trigger, ...props }: NodeDrawerProps) => {
|
||||
const useDrawer = () => {
|
||||
type NodeDataType = NodeDrawerProps["node"];
|
||||
const [node, setNode] = useState<NodeDataType>();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onOpenChange = useCallback((open: boolean) => {
|
||||
setOpen(open);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { EditorState, FlowLayoutDefault, useClientContext, usePlaygroundTools, useRefresh } from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconHandStop, IconLayoutCards, IconMatrix, IconMaximize, IconMinus, IconPlus } from "@tabler/icons-react";
|
||||
import { Button, Dropdown, Tooltip } from "antd";
|
||||
import { Button, type ButtonProps, Dropdown, Tooltip } from "antd";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { mergeCls } from "@/utils/css";
|
||||
|
||||
import Minimap from "./Minimap";
|
||||
@@ -11,9 +12,26 @@ import Minimap from "./Minimap";
|
||||
export interface ToolbarProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
size?: ButtonProps["size"];
|
||||
showLayout?: boolean;
|
||||
showMinimap?: boolean;
|
||||
showMouseState?: boolean;
|
||||
showZoom?: boolean;
|
||||
showZoomFit?: boolean;
|
||||
showZoomLevel?: boolean;
|
||||
}
|
||||
|
||||
const Toolbar = ({ className, style }: ToolbarProps) => {
|
||||
const Toolbar = ({
|
||||
className,
|
||||
style,
|
||||
size,
|
||||
showLayout = true,
|
||||
showMinimap = true,
|
||||
showMouseState = true,
|
||||
showZoom = true,
|
||||
showZoomFit = true,
|
||||
showZoomLevel = true,
|
||||
}: ToolbarProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ctx = useClientContext();
|
||||
@@ -29,6 +47,12 @@ const Toolbar = ({ className, style }: ToolbarProps) => {
|
||||
return () => d.dispose();
|
||||
}, [playground]);
|
||||
|
||||
const buttonIconSize = useMemo(() => {
|
||||
if (size === "large") return "1.5em";
|
||||
if (size === "small") return "1em";
|
||||
return "1.25em";
|
||||
}, [size]);
|
||||
|
||||
const [isMinimapVisible, setIsMinimapVisible] = useState(() => window.screen.availWidth >= 1024);
|
||||
|
||||
const [isMouseFriendly, setIsMouseFriendly] = useState(() => playground.editorState.is(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id));
|
||||
@@ -58,55 +82,80 @@ const Toolbar = ({ className, style }: ToolbarProps) => {
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Tooltip title={isMouseFriendly ? t("workflow.detail.design.toolbar.hand_mode") : t("workflow.detail.design.toolbar.pointer_mode")}>
|
||||
<Button
|
||||
ghost={isMouseFriendly}
|
||||
icon={<IconHandStop size="1.25em" />}
|
||||
type={isMouseFriendly ? "primary" : "default"}
|
||||
onClick={handleToggleMouseFriendly}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Show when={showMouseState}>
|
||||
<Tooltip title={isMouseFriendly ? t("workflow.detail.design.toolbar.hand_mode") : t("workflow.detail.design.toolbar.pointer_mode")}>
|
||||
<Button
|
||||
ghost={isMouseFriendly}
|
||||
icon={<IconHandStop size={buttonIconSize} />}
|
||||
size={size}
|
||||
type={isMouseFriendly ? "primary" : "default"}
|
||||
onClick={handleToggleMouseFriendly}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.zoomout")}>
|
||||
<Button icon={<IconMinus size="1.25em" />} onClick={() => tools.zoomout()} />
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
...[200, 100, 75, 50, 25].map((zoom) => ({
|
||||
key: `${zoom}%`,
|
||||
label: `${zoom}%`,
|
||||
onClick: () => tools.updateZoom(zoom / 100),
|
||||
})),
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "auto",
|
||||
label: t("workflow.detail.design.toolbar.auto_fit"),
|
||||
onClick: () => tools.fitView(),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button className="w-16 text-center">{Math.round(tools.zoom * 100)}%</Button>
|
||||
</Dropdown>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.zoomin")}>
|
||||
<Button icon={<IconPlus size="1.25em" />} onClick={() => tools.zoomin()} />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.auto_fit")}>
|
||||
<Button icon={<IconMaximize size="1.25em" />} onClick={() => tools.fitView()} />
|
||||
</Tooltip>
|
||||
<Show when={showZoom}>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.zoomout")}>
|
||||
<Button icon={<IconMinus size={buttonIconSize} />} size={size} onClick={() => tools.zoomout()} />
|
||||
</Tooltip>
|
||||
<Show when={showZoomLevel}>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
...[200, 100, 75, 50, 25].map((zoom) => ({
|
||||
key: `${zoom}%`,
|
||||
label: `${zoom}%`,
|
||||
onClick: () => tools.updateZoom(zoom / 100),
|
||||
})),
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "auto",
|
||||
label: t("workflow.detail.design.toolbar.auto_fit"),
|
||||
onClick: () => tools.fitView(),
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button className="w-16 text-center" size={size}>
|
||||
{Math.round(tools.zoom * 100)}%
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Show>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.zoomin")}>
|
||||
<Button icon={<IconPlus size={buttonIconSize} />} size={size} onClick={() => tools.zoomin()} />
|
||||
</Tooltip>
|
||||
<Show when={showZoomFit}>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.auto_fit")}>
|
||||
<Button icon={<IconMaximize size={buttonIconSize} />} size={size} onClick={() => tools.fitView()} />
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</Show>
|
||||
|
||||
<Tooltip title={tools.isVertical ? t("workflow.detail.design.toolbar.vertical_layout") : t("workflow.detail.design.toolbar.horizontal_layout")}>
|
||||
<Button icon={<IconLayoutCards className={mergeCls({ ["rotate-90"]: tools.isVertical })} size="1.25em" />} onClick={handleToggleLayout} />
|
||||
</Tooltip>
|
||||
<Show when={showLayout}>
|
||||
<Tooltip title={tools.isVertical ? t("workflow.detail.design.toolbar.vertical_layout") : t("workflow.detail.design.toolbar.horizontal_layout")}>
|
||||
<Button
|
||||
icon={<IconLayoutCards className={mergeCls({ ["rotate-90"]: tools.isVertical })} size={buttonIconSize} />}
|
||||
size={size}
|
||||
onClick={handleToggleLayout}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.minimap")}>
|
||||
<Button icon={<IconMatrix size="1.25em" />} ghost={isMinimapVisible} type={isMinimapVisible ? "primary" : "default"} onClick={handleToggleMinimap} />
|
||||
</Tooltip>
|
||||
{isMinimapVisible && <Minimap className="absolute right-0 bottom-[42px]" />}
|
||||
<Show when={showMinimap}>
|
||||
<Tooltip title={t("workflow.detail.design.toolbar.minimap")}>
|
||||
<Button
|
||||
icon={<IconMatrix size={buttonIconSize} />}
|
||||
ghost={isMinimapVisible}
|
||||
type={isMinimapVisible ? "primary" : "default"}
|
||||
size={size}
|
||||
onClick={handleToggleMinimap}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isMinimapVisible && <Minimap className="absolute right-0 bottom-[42px]" />}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
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()),
|
||||
listenerPort: z.preprocess((v) => (v == null || v === "" ? void 0 : Number(v)), z.number().nullish()),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
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()),
|
||||
listenerPort: z.preprocess((v) => (v == null || v === "" ? void 0 : Number(v)), z.number().nullish()),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
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()),
|
||||
listenerPort: z.preprocess((v) => (v == null || v === "" ? void 0 : Number(v)), z.number().nullish()),
|
||||
domain: z
|
||||
.string()
|
||||
.nullish()
|
||||
|
||||
@@ -168,7 +168,7 @@ const BizNotifyNodeConfigForm = ({ node, ...props }: BizNotifyNodeConfigFormProp
|
||||
showSearch
|
||||
onChange={handleProviderAccessSelect}
|
||||
onFilter={(_, option) => {
|
||||
if (option.reserve !== "notification") return false;
|
||||
if (option.reserve !== "notif") return false;
|
||||
|
||||
const provider = accessProvidersMap.get(option.provider);
|
||||
return !!provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
return z.object({
|
||||
chatId: z
|
||||
.preprocess(
|
||||
(v) => (v == null || v === "" ? undefined : Number(v)),
|
||||
(v) => (v == null || v === "" ? void 0 : Number(v)),
|
||||
z
|
||||
.number()
|
||||
.nullish()
|
||||
|
||||
+15
-14
@@ -21,7 +21,7 @@ import { useNodeFormContext } from "./_context";
|
||||
import { getAllPreviousNodes } from "../_util";
|
||||
import { NodeType } from "../nodes/typings";
|
||||
|
||||
export interface BranchBlockNodeConfigFormExpressionEditorProps {
|
||||
export interface BranchBlockNodeConfigExprInputBoxProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
defaultValue?: Expr;
|
||||
@@ -29,8 +29,8 @@ export interface BranchBlockNodeConfigFormExpressionEditorProps {
|
||||
onChange?: (value: Expr) => void;
|
||||
}
|
||||
|
||||
export interface BranchBlockNodeConfigFormExpressionEditorInstance {
|
||||
validate: () => Promise<void>;
|
||||
export interface BranchBlockNodeConfigExprInputBoxInstance {
|
||||
validate: () => Promise<Expr | undefined>;
|
||||
}
|
||||
|
||||
// 表单内部使用的扁平结构
|
||||
@@ -50,7 +50,7 @@ type ConditionFormValues = {
|
||||
logicalOperator: ExprLogicalOperator;
|
||||
};
|
||||
|
||||
const exprToFormValues = (expr?: Expr): ConditionFormValues => {
|
||||
const exprToFormValues = (expr: Expr | undefined): ConditionFormValues => {
|
||||
if (!expr) return getInitialValues();
|
||||
|
||||
const conditions: ConditionItem[] = [];
|
||||
@@ -60,9 +60,9 @@ const exprToFormValues = (expr?: Expr): ConditionFormValues => {
|
||||
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,
|
||||
leftSelector: expr.left.selector?.id != null ? `${expr.left.selector.id}#${expr.left.selector.name}#${expr.left.selector.type}` : void 0,
|
||||
operator: expr.operator != null ? expr.operator : void 0,
|
||||
rightValue: expr.right?.value != null ? String(expr.right.value) : void 0,
|
||||
});
|
||||
} else {
|
||||
console.warn("[certimate] invalid comparison expression: left must be a variant and right must be a constant", expr);
|
||||
@@ -113,14 +113,14 @@ const formValuesToExpr = (values: ConditionFormValues): Expr | undefined => {
|
||||
};
|
||||
|
||||
if (values.conditions.length === 0) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// 只有一个条件时,直接返回比较表达式
|
||||
if (values.conditions.length === 1) {
|
||||
const { leftSelector, operator, rightValue } = values.conditions[0];
|
||||
if (!leftSelector || !operator || !rightValue) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
return wrapExpr(values.conditions[0]);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ const formValuesToExpr = (values: ConditionFormValues): Expr | undefined => {
|
||||
return expr;
|
||||
};
|
||||
|
||||
const BranchBlockNodeConfigFormExpressionEditor = forwardRef<BranchBlockNodeConfigFormExpressionEditorInstance, BranchBlockNodeConfigFormExpressionEditorProps>(
|
||||
const BranchBlockNodeConfigExprInputBox = forwardRef<BranchBlockNodeConfigExprInputBoxInstance, BranchBlockNodeConfigExprInputBoxProps>(
|
||||
({ className, style, ...props }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -153,7 +153,7 @@ const BranchBlockNodeConfigFormExpressionEditor = forwardRef<BranchBlockNodeConf
|
||||
const { node } = useNodeFormContext();
|
||||
|
||||
const [formInst] = Form.useForm<ConditionFormValues>();
|
||||
const formName = useAntdFormName({ form: formInst, name: "workflowNodeBranchBlockConfigFormExpressionEditorForm" });
|
||||
const formName = useAntdFormName({ form: formInst, name: "workflowNodeBranchBlockConfigExprInputBoxForm" });
|
||||
const [formModel, setFormModel] = useState<ConditionFormValues>(getInitialValues());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -253,8 +253,9 @@ const BranchBlockNodeConfigFormExpressionEditor = forwardRef<BranchBlockNodeConf
|
||||
return {
|
||||
validate: async () => {
|
||||
await formInst.validateFields();
|
||||
return formValuesToExpr(formInst.getFieldsValue());
|
||||
},
|
||||
} as BranchBlockNodeConfigFormExpressionEditorInstance;
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -315,7 +316,7 @@ const BranchBlockNodeConfigFormExpressionEditor = forwardRef<BranchBlockNodeConf
|
||||
{...rest}
|
||||
>
|
||||
<Select
|
||||
open={operators.length === 0 ? false : undefined}
|
||||
open={operators.length === 0 ? false : void 0}
|
||||
options={operators}
|
||||
placeholder={t("workflow_node.branch_block.form.expression.operator.placeholder")}
|
||||
/>
|
||||
@@ -383,4 +384,4 @@ const getInitialValues = (): ConditionFormValues => {
|
||||
};
|
||||
};
|
||||
|
||||
export default BranchBlockNodeConfigFormExpressionEditor;
|
||||
export default BranchBlockNodeConfigExprInputBox;
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { useAntdForm } from "@/hooks";
|
||||
|
||||
import { NodeFormContextProvider } from "./_context";
|
||||
import BranchBlockNodeConfigFormExpressionEditor, { type BranchBlockNodeConfigFormExpressionEditorInstance } from "./BranchBlockNodeConfigFormExpressionEditor";
|
||||
import BranchBlockNodeConfigExprInputBox, { type BranchBlockNodeConfigExprInputBoxInstance } from "./BranchBlockNodeConfigExprInputBox";
|
||||
|
||||
import { NodeType } from "../nodes/typings";
|
||||
|
||||
@@ -41,7 +41,7 @@ const BranchBlockNodeConfigForm = ({ node, ...props }: BranchBlockNodeConfigForm
|
||||
const formSchema = getSchema({ i18n }).superRefine(async (values, ctx) => {
|
||||
if (values.expression != null) {
|
||||
try {
|
||||
await exprEditorRef.current!.validate();
|
||||
await exprInputBoxRef.current!.validate();
|
||||
} catch {
|
||||
if (!ctx.issues.some((issue) => issue.path?.[0] === "expression")) {
|
||||
ctx.addIssue({
|
||||
@@ -60,14 +60,14 @@ const BranchBlockNodeConfigForm = ({ node, ...props }: BranchBlockNodeConfigForm
|
||||
initialValues: initialValues ?? getInitialValues(),
|
||||
});
|
||||
|
||||
const exprEditorRef = useRef<BranchBlockNodeConfigFormExpressionEditorInstance>(null);
|
||||
const exprInputBoxRef = useRef<BranchBlockNodeConfigExprInputBoxInstance>(null);
|
||||
|
||||
return (
|
||||
<NodeFormContextProvider value={{ node }}>
|
||||
<Form {...formProps} clearOnDestroy={true} form={formInst} layout="vertical" preserve={false} scrollToFirstError>
|
||||
<div id="parameters" data-anchor="parameters">
|
||||
<Form.Item name="expression" label={t("workflow_node.branch_block.form.expression.label")} rules={[formRule]}>
|
||||
<BranchBlockNodeConfigFormExpressionEditor ref={exprEditorRef} />
|
||||
<BranchBlockNodeConfigExprInputBox ref={exprInputBoxRef} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
@@ -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 DelayNodeConfigForm from "./DelayNodeConfigForm";
|
||||
import { NodeType } from "../nodes/typings";
|
||||
|
||||
export interface DelayNodeConfigDrawerProps {
|
||||
afterClose?: () => void;
|
||||
loading?: boolean;
|
||||
node: FlowNodeEntity;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const DelayNodeConfigDrawer = ({ node, ...props }: DelayNodeConfigDrawerProps) => {
|
||||
if (node.flowNodeType !== NodeType.Delay) {
|
||||
console.warn(`[certimate] current workflow node type is not: ${NodeType.Delay}`);
|
||||
}
|
||||
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const [formInst] = Form.useForm();
|
||||
|
||||
return (
|
||||
<NodeConfigDrawer
|
||||
anchor={{
|
||||
items: DelayNodeConfigForm.getAnchorItems({ i18n }),
|
||||
}}
|
||||
form={formInst}
|
||||
node={node}
|
||||
{...props}
|
||||
>
|
||||
<DelayNodeConfigForm form={formInst} node={node} />
|
||||
</NodeConfigDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DelayNodeConfigDrawer;
|
||||
@@ -0,0 +1,92 @@
|
||||
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, InputNumber } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { type WorkflowNodeConfigForDelay, defaultNodeConfigForDelay } from "@/domain/workflow";
|
||||
import { useAntdForm } from "@/hooks";
|
||||
|
||||
import { NodeFormContextProvider } from "./_context";
|
||||
import { NodeType } from "../nodes/typings";
|
||||
|
||||
export interface DelayNodeConfigFormProps {
|
||||
form: FormInstance;
|
||||
node: FlowNodeEntity;
|
||||
}
|
||||
|
||||
const DelayNodeConfigForm = ({ node, ...props }: DelayNodeConfigFormProps) => {
|
||||
if (node.flowNodeType !== NodeType.Delay) {
|
||||
console.warn(`[certimate] current workflow node type is not: ${NodeType.Delay}`);
|
||||
}
|
||||
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
return getNodeForm(node)?.getValueIn("config") as WorkflowNodeConfigForDelay | undefined;
|
||||
}, [node]);
|
||||
|
||||
const formSchema = getSchema({ i18n });
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const { form: formInst, formProps } = useAntdForm({
|
||||
form: props.form,
|
||||
name: "workflowNodeDelayConfigForm",
|
||||
initialValues: initialValues ?? getInitialValues(),
|
||||
});
|
||||
|
||||
return (
|
||||
<NodeFormContextProvider value={{ node }}>
|
||||
<Form {...formProps} clearOnDestroy={true} form={formInst} layout="vertical" preserve={false} scrollToFirstError>
|
||||
<div id="parameters" data-anchor="parameters">
|
||||
<Form.Item name="wait" label={t("workflow_node.delay.form.wait.label")} rules={[formRule]}>
|
||||
<InputNumber
|
||||
style={{ width: "100%" }}
|
||||
min={0}
|
||||
max={3600}
|
||||
placeholder={t("workflow_node.delay.form.wait.placeholder")}
|
||||
addonAfter={t("workflow_node.delay.form.wait.unit")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
</NodeFormContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const getAnchorItems = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }): Required<AnchorProps>["items"] => {
|
||||
const { t } = i18n;
|
||||
|
||||
return ["parameters"].map((key) => ({
|
||||
key: key,
|
||||
title: t(`workflow_node.delay.form_anchor.${key}.tab`),
|
||||
href: "#" + key,
|
||||
}));
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
...defaultNodeConfigForDelay(),
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
|
||||
const { t } = i18n;
|
||||
|
||||
return z.object({
|
||||
wait: z.preprocess(
|
||||
(v) => Number(v),
|
||||
z
|
||||
.number(t("workflow_node.delay.form.wait.placeholder"))
|
||||
.int(t("workflow_node.delay.form.wait.placeholder"))
|
||||
.gte(1, t("workflow_node.delay.form.wait.placeholder"))
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(DelayNodeConfigForm, {
|
||||
getAnchorItems,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useEffect, 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 { type FlowNodeEntity, getNodeForm, useClientContext, useRefresh } from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconEye, IconEyeOff, IconX } from "@tabler/icons-react";
|
||||
import { useControllableValue } from "ahooks";
|
||||
import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Typography } from "antd";
|
||||
import { Anchor, type AnchorProps, App, Button, Drawer, Flex, type FormInstance, Tooltip, Typography } from "antd";
|
||||
import { isEqual } from "radash";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
import { type NodeRegistry } from "../nodes/typings";
|
||||
@@ -25,6 +26,11 @@ export interface NodeConfigDrawerProps {
|
||||
export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true, form: formInst, loading, node, ...props }: NodeConfigDrawerProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ctx = useClientContext();
|
||||
const { playground } = ctx;
|
||||
|
||||
const refresh = useRefresh();
|
||||
|
||||
const { modal, notification } = App.useApp();
|
||||
|
||||
const [open, setOpen] = useControllableValue<boolean>(props, {
|
||||
@@ -35,7 +41,7 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [formPending, setFormPending] = useState<boolean>(false);
|
||||
const [formPending, setFormPending] = useState(false);
|
||||
|
||||
const nodeRegistry = node?.getNodeRegistry<NodeRegistry>();
|
||||
const NodeIcon = nodeRegistry?.meta?.icon;
|
||||
@@ -52,6 +58,22 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
</div>
|
||||
);
|
||||
|
||||
const [isNodeDisabled, setIsNodeDisabled] = useState(() => {
|
||||
if (node) {
|
||||
return getNodeForm(node)?.getValueIn<boolean>("disabled");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
useEffect(() => {
|
||||
const d1 = playground.config.onDataChange(() => refresh());
|
||||
const d2 = node?.onDataChange(() => setIsNodeDisabled(getNodeForm(node)?.getValueIn<boolean>("disabled")));
|
||||
|
||||
return () => {
|
||||
d1.dispose();
|
||||
d2.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
const handleOkClick = async () => {
|
||||
if (node == null) {
|
||||
setOpen(false);
|
||||
@@ -93,8 +115,7 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
return Object.entries(obj).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const isEmpty =
|
||||
value === null ||
|
||||
value === undefined ||
|
||||
value == null ||
|
||||
(typeof value === "string" && value === "") ||
|
||||
(Array.isArray(value) && value.length === 0) ||
|
||||
(typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
|
||||
@@ -112,13 +133,12 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
const newValues = picker(formInst.getFieldsValue(true));
|
||||
const changed = !isEqual(oldValues, {}) && !isEqual(oldValues, newValues);
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
if (changed) {
|
||||
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);
|
||||
@@ -127,6 +147,10 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
promise.then(() => setOpen(false));
|
||||
};
|
||||
|
||||
const handleDisableNodeClick = () => {
|
||||
getNodeForm(node)!.setValueIn("disabled", !isNodeDisabled);
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
styles={{
|
||||
@@ -160,21 +184,38 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
<Flex align="center" justify="space-between" gap="small">
|
||||
<div>{renderNodeIcon()}</div>
|
||||
<div className="flex-1 truncate">{node?.toJSON()?.data?.name}</div>
|
||||
<Show when={!!node && !nodeRegistry?.meta?.isStart && !nodeRegistry?.meta?.isNodeEnd}>
|
||||
<Tooltip
|
||||
title={isNodeDisabled ? t("workflow.detail.design.drawer.disabled.on.tooltip") : t("workflow.detail.design.drawer.disabled.off.tooltip")}
|
||||
>
|
||||
<Button
|
||||
className="ant-drawer-close"
|
||||
style={{ marginInline: 0 }}
|
||||
disabled={playground.config.readonlyOrDisabled}
|
||||
icon={isNodeDisabled ? <IconEyeOff size="1.25em" /> : <IconEye size="1.25em" />}
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={handleDisableNodeClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Button className="ant-drawer-close" style={{ marginInline: 0 }} icon={<IconX size="1.25em" />} size="small" type="text" onClick={handleClose} />
|
||||
</Flex>
|
||||
<div className="mt-3 text-sm font-normal">
|
||||
|
||||
<div className="mt-3 truncate text-sm font-normal">
|
||||
<Typography.Text className="text-xs" type="secondary">
|
||||
<span>{t("workflow.detail.design.drawer.node_id.label")}</span>
|
||||
<span>{node?.id}</span>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{anchor && (
|
||||
|
||||
<Show when={!!anchor}>
|
||||
<div className="-mx-[2px] mt-3 text-sm font-normal">
|
||||
<Anchor
|
||||
affix={false}
|
||||
getContainer={() => containerRef.current!}
|
||||
direction="horizontal"
|
||||
items={anchor.items}
|
||||
items={(anchor as AnchorProps).items}
|
||||
onClick={(e, link) => {
|
||||
// https://github.com/ant-design/ant-design/issues/10577
|
||||
// https://github.com/ant-design/ant-design/issues/15326
|
||||
@@ -186,7 +227,7 @@ export const NodeConfigDrawer = ({ children, afterClose, anchor, footer = true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
onClose={handleClose}
|
||||
|
||||
@@ -24,6 +24,7 @@ export const BizApplyNodeRegistry: NodeRegistry = {
|
||||
iconBgColor: "#5b65f5",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
|
||||
@@ -25,6 +25,7 @@ export const BizDeployNodeRegistry: NodeRegistry = {
|
||||
iconBgColor: "#5b65f5",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
|
||||
@@ -22,6 +22,7 @@ export const BizMonitorNodeRegistry: NodeRegistry = {
|
||||
iconBgColor: "#5b65f5",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
|
||||
@@ -24,6 +24,7 @@ export const BizNotifyNodeRegistry: NodeRegistry = {
|
||||
iconBgColor: "#0693d4",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
|
||||
@@ -22,6 +22,7 @@ export const BizUploadNodeRegistry: NodeRegistry = {
|
||||
iconBgColor: "#5b65f5",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { getI18n } from "react-i18next";
|
||||
import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconHourglassHigh } from "@tabler/icons-react";
|
||||
|
||||
import { newNode } from "@/domain/workflow";
|
||||
|
||||
import { BaseNode } from "./_shared";
|
||||
import { NodeKindType, type NodeRegistry, NodeType } from "./typings";
|
||||
import DelayNodeConfigForm from "../forms/DelayNodeConfigForm";
|
||||
|
||||
export const DelayNodeRegistry: NodeRegistry = {
|
||||
type: NodeType.Delay,
|
||||
|
||||
kind: NodeKindType.Basis,
|
||||
|
||||
meta: {
|
||||
helpText: getI18n().t("workflow_node.delay.help"),
|
||||
labelText: getI18n().t("workflow_node.delay.label"),
|
||||
|
||||
icon: IconHourglassHigh,
|
||||
iconColor: "#2a354c",
|
||||
iconBgColor: "#fed421",
|
||||
|
||||
clickable: true,
|
||||
expandable: false,
|
||||
},
|
||||
|
||||
formMeta: {
|
||||
validate: {
|
||||
["config"]: ({ value }) => {
|
||||
const res = DelayNodeConfigForm.getSchema({}).safeParse(value);
|
||||
if (!res.success) {
|
||||
return {
|
||||
message: res.error.message,
|
||||
level: FeedbackLevel.Error,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render: () => {
|
||||
const { t } = getI18n();
|
||||
|
||||
return (
|
||||
<BaseNode
|
||||
description={
|
||||
<Field name="config.wait">
|
||||
{({ field: { value } }) => (
|
||||
<>
|
||||
<div>{value != null ? `${value} ${t("workflow_node.delay.form.wait.unit")}` : t("workflow.detail.design.editor.placeholder")}</div>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
onAdd() {
|
||||
return newNode(NodeType.Delay, { i18n: getI18n() });
|
||||
},
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type NodeRenderReturnType,
|
||||
useClientContext,
|
||||
useWatchFormState,
|
||||
useWatchFormValueIn,
|
||||
} from "@flowgram.ai/fixed-layout-editor";
|
||||
import { IconCopy, IconDotsVertical, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react";
|
||||
import { Button, type ButtonProps, Card, Dropdown, Input, type InputRef, Popover, Tooltip, theme } from "antd";
|
||||
@@ -72,16 +73,18 @@ const InternalNodeCard = ({
|
||||
|
||||
const isActivated = useMemo(() => nodeRenderData.activated || nodeRenderData.lineActivated, [nodeRenderData.activated, nodeRenderData.lineActivated]);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [isInvalid, setIsInvalid] = useState(false);
|
||||
const [isNodeInvalid, setIsNodeInvalid] = useState(false);
|
||||
const isNodeDisabled = useWatchFormValueIn(nodeRender.node, "disabled");
|
||||
|
||||
const formState = useWatchFormState(nodeRender.node);
|
||||
useEffect(() => setIsInvalid(!!formState?.invalid), [formState?.invalid]);
|
||||
useEffect(() => setIsNodeInvalid(!!formState?.invalid), [formState?.invalid]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={mergeCls(
|
||||
"relative rounded-xl shadow-sm",
|
||||
{ "border-primary": isActivated },
|
||||
{ "border-dashed": isNodeDisabled },
|
||||
nodeRegistry.meta?.clickable ? "cursor-pointer" : "cursor-default",
|
||||
className
|
||||
)}
|
||||
@@ -91,7 +94,14 @@ const InternalNodeCard = ({
|
||||
onMouseEnter={() => startTransition(() => setIsHovering(true))}
|
||||
onMouseLeave={() => startTransition(() => setIsHovering(false))}
|
||||
>
|
||||
<div className="relative z-1">{children}</div>
|
||||
<div
|
||||
className="relative z-1 transition-opacity"
|
||||
style={{
|
||||
opacity: isHovering ? 1 : isNodeDisabled ? 0.3 : void 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className="absolute z-0 rounded-xl border-solid border-transparent transition-all duration-500"
|
||||
style={{
|
||||
@@ -100,7 +110,8 @@ const InternalNodeCard = ({
|
||||
right: "-1px",
|
||||
bottom: "-1px",
|
||||
borderWidth: "2px",
|
||||
borderColor: isHovering ? "var(--color-primary)" : isInvalid ? "var(--color-error)" : void 0,
|
||||
borderColor: isHovering ? "var(--color-primary)" : isNodeInvalid ? "var(--color-error)" : void 0,
|
||||
borderStyle: isNodeDisabled ? "dashed" : "solid",
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
@@ -408,7 +419,7 @@ export const BranchNode = ({ className, style, children, description }: BranchNo
|
||||
/>
|
||||
)
|
||||
}
|
||||
placement="right"
|
||||
placement="rightTop"
|
||||
>
|
||||
<div
|
||||
className="group/node relative"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BizMonitorNodeRegistry } from "./BizMonitorNodeRegistry";
|
||||
import { BizNotifyNodeRegistry } from "./BizNotifyNodeRegistry";
|
||||
import { BizUploadNodeRegistry } from "./BizUploadNodeRegistry";
|
||||
import { BranchBlockNodeRegistry, ConditionNodeRegistry } from "./ConditionNode";
|
||||
import { DelayNodeRegistry } from "./DelayNode";
|
||||
import { EndNodeRegistry } from "./EndNode";
|
||||
import { StartNodeRegistry } from "./StartNode";
|
||||
import { CatchBlockNodeRegistry, TryCatchNodeRegistry } from "./TryCatchNode";
|
||||
@@ -12,6 +13,7 @@ export const getAllNodeRegistries = () => {
|
||||
return [
|
||||
StartNodeRegistry,
|
||||
EndNodeRegistry,
|
||||
DelayNodeRegistry,
|
||||
BizApplyNodeRegistry,
|
||||
BizUploadNodeRegistry,
|
||||
BizMonitorNodeRegistry,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { WORKFLOW_NODE_TYPES, type WorkflowNode } from "@/domain/workflow";
|
||||
export enum NodeType {
|
||||
Start = "start",
|
||||
End = "end",
|
||||
Delay = "delay",
|
||||
Condition = "condition",
|
||||
BranchBlock = "branchBlock",
|
||||
TryCatch = "tryCatch",
|
||||
@@ -28,6 +29,7 @@ export enum NodeType {
|
||||
/* TYPE GUARD, PLEASE DO NOT REMOVE THESE! */
|
||||
console.assert(NodeType.Start === WORKFLOW_NODE_TYPES.START);
|
||||
console.assert(NodeType.End === WORKFLOW_NODE_TYPES.END);
|
||||
console.assert(NodeType.Delay === WORKFLOW_NODE_TYPES.DELAY);
|
||||
console.assert(NodeType.Condition === WORKFLOW_NODE_TYPES.CONDITION);
|
||||
console.assert(NodeType.BranchBlock === WORKFLOW_NODE_TYPES.BRANCHBLOCK);
|
||||
console.assert(NodeType.TryCatch === WORKFLOW_NODE_TYPES.TRYCATCH);
|
||||
@@ -56,21 +58,67 @@ export interface DocumentJSON {
|
||||
}
|
||||
|
||||
export interface NodeMeta extends FlowNodeMeta {
|
||||
/**
|
||||
* 自定义样式。
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* 帮助文本。
|
||||
*/
|
||||
helpText?: React.ReactNode;
|
||||
/**
|
||||
* 标题文本。
|
||||
*/
|
||||
labelText?: React.ReactNode;
|
||||
/**
|
||||
* 图标组件。
|
||||
*/
|
||||
icon?: React.ExoticComponent<any> | React.ComponentType<any>;
|
||||
/**
|
||||
* 图标前景色。
|
||||
*/
|
||||
iconColor?: string;
|
||||
/**
|
||||
* 图标背景色。
|
||||
*/
|
||||
iconBgColor?: string;
|
||||
/**
|
||||
* 是否可点击。通常配合抽屉表单使用。
|
||||
*/
|
||||
clickable?: boolean;
|
||||
}
|
||||
|
||||
export interface NodeRegistry<V extends NodeJSON["data"] = NodeJSON["data"]> extends FlowNodeRegistry<NodeMeta> {
|
||||
/**
|
||||
* 节点类型分类。
|
||||
*/
|
||||
kind?: NodeKindType;
|
||||
|
||||
formMeta?: Omit<FormMeta<V>, "render"> & {
|
||||
render: (props: FormRenderProps<V>) => React.ReactElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否可以添加一个该类型的节点。
|
||||
* 如果不存在该方法,默认等同于返回值为 true。
|
||||
* @param {FixedLayoutPluginContext} ctx
|
||||
* @param {FlowNodeEntity} from
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
canAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
|
||||
/**
|
||||
* 判断是否可以删除一个该类型的节点。
|
||||
* 如果不存在该方法,默认等同于返回值为 true。
|
||||
* @param {FixedLayoutPluginContext} ctx
|
||||
* @param {FlowNodeEntity} from
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
canDelete?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => boolean;
|
||||
/**
|
||||
* 返回一个新的表示该类型的节点结构。
|
||||
* @param {FixedLayoutPluginContext} ctx
|
||||
* @param {FlowNodeEntity} from
|
||||
* @returns {FlowNodeJSON}
|
||||
*/
|
||||
onAdd?: (ctx: FixedLayoutPluginContext, from: FlowNodeEntity) => FlowNodeJSON;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface AccessModel extends BaseModel {
|
||||
name: string;
|
||||
provider: string;
|
||||
config: /*
|
||||
config?: /*
|
||||
注意:如果追加新的类型,请保持以 ASCII 排序。
|
||||
NOTICE: If you add new type, please keep ASCII order.
|
||||
*/ Record<string, unknown> &
|
||||
@@ -79,7 +79,7 @@ export interface AccessModel extends BaseModel {
|
||||
| AccessConfigForWestcn
|
||||
| AccessConfigForZeroSSL
|
||||
);
|
||||
reserve?: "ca" | "notification";
|
||||
reserve?: "ca" | "notif";
|
||||
}
|
||||
|
||||
// #region AccessConfig
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface CertificateModel extends BaseModel {
|
||||
validityNotAfter: ISO8601String;
|
||||
workflowRef: string;
|
||||
expand?: {
|
||||
workflowRef?: WorkflowModel;
|
||||
workflowRef?: Pick<WorkflowModel, "id" | "name" | "description">;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type Statistics = {
|
||||
certificateTotal: number;
|
||||
certificateExpired: number;
|
||||
certificateExpireSoon: number;
|
||||
certificateExpiringSoon: number;
|
||||
workflowTotal: number;
|
||||
workflowEnabled: number;
|
||||
workflowDisabled: number;
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface WorkflowModel extends BaseModel {
|
||||
lastRunStatus?: string;
|
||||
lastRunTime?: string;
|
||||
expand?: {
|
||||
lastRunRef?: WorkflowRunModel;
|
||||
lastRunRef?: Pick<WorkflowRunModel, "id" | "status" | "trigger" | "startedAt" | "endedAt" | "error">;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export type WorkflowTriggerType = (typeof WORKFLOW_TRIGGERS)[keyof typeof WORKFL
|
||||
export const WORKFLOW_NODE_TYPES = Object.freeze({
|
||||
START: "start",
|
||||
END: "end",
|
||||
DELAY: "delay",
|
||||
CONDITION: "condition",
|
||||
BRANCHBLOCK: "branchBlock",
|
||||
TRYCATCH: "tryCatch",
|
||||
@@ -58,6 +59,7 @@ export type WorkflowNode = {
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
config?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
blocks?: WorkflowNode[];
|
||||
};
|
||||
@@ -73,6 +75,14 @@ export const defaultNodeConfigForStart = (): Partial<WorkflowNodeConfigForStart>
|
||||
};
|
||||
};
|
||||
|
||||
export type WorkflowNodeConfigForDelay = {
|
||||
wait?: number;
|
||||
};
|
||||
|
||||
export const defaultNodeConfigForDelay = (): Partial<WorkflowNodeConfigForDelay> => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export type WorkflowNodeConfigForBranchBlock = {
|
||||
expression?: Expr;
|
||||
};
|
||||
@@ -189,6 +199,16 @@ export const newNode = (type: WorkflowNodeType, { i18n = getI18n() }: { i18n?: R
|
||||
},
|
||||
};
|
||||
|
||||
case WORKFLOW_NODE_TYPES.DELAY:
|
||||
return {
|
||||
id: newNodeId(),
|
||||
type: type,
|
||||
data: {
|
||||
name: t("workflow_node.delay.default_name"),
|
||||
config: defaultNodeConfigForDelay(),
|
||||
},
|
||||
};
|
||||
|
||||
case WORKFLOW_NODE_TYPES.CONDITION: {
|
||||
const branch1 = newNode(WORKFLOW_NODE_TYPES.BRANCHBLOCK, { i18n });
|
||||
branch1.data.name = `${branch1.data.name} 1`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type WorkflowModel } from "./workflow";
|
||||
import { type WorkflowGraph, type WorkflowModel } from "./workflow";
|
||||
|
||||
export interface WorkflowRunModel extends BaseModel {
|
||||
workflowRef: string;
|
||||
@@ -6,9 +6,10 @@ export interface WorkflowRunModel extends BaseModel {
|
||||
trigger: string;
|
||||
startedAt: ISO8601String;
|
||||
endedAt: ISO8601String;
|
||||
graph?: WorkflowGraph;
|
||||
error?: string;
|
||||
expand?: {
|
||||
workflowRef?: WorkflowModel;
|
||||
workflowRef?: Pick<WorkflowModel, "id" | "name" | "description">;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"certificate.props.validity.expired": "Expired",
|
||||
"certificate.props.validity.expiration": "Expire on {{date}}",
|
||||
"certificate.props.validity.filter.all": "All",
|
||||
"certificate.props.validity.filter.expire_soon": "Expire soon",
|
||||
"certificate.props.validity.filter.expiring_soon": "Expiring soon",
|
||||
"certificate.props.validity.filter.expired": "Expired",
|
||||
"certificate.props.brand": "Brand",
|
||||
"certificate.props.source": "Source",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"common.button.add": "Add",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.close": "Close",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.create": "Create",
|
||||
"common.button.copy": "Copy",
|
||||
"common.button.create": "Create",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.download": "Download",
|
||||
"common.button.edit": "Edit",
|
||||
@@ -23,6 +24,7 @@
|
||||
"common.text.operation_succeeded": "Operation succeeded",
|
||||
"common.text.operation_failed": "Operation failed",
|
||||
"common.text.request_error": "Request error",
|
||||
"common.text.saving": "Saving ...",
|
||||
"common.text.search": "Search ...",
|
||||
|
||||
"common.menu.document": "Document",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"dashboard.page.title": "Dashboard",
|
||||
|
||||
"dashboard.statistics.all_certificates": "All certificates",
|
||||
"dashboard.statistics.expire_soon_certificates": "Expire soon certificates",
|
||||
"dashboard.statistics.expire_soon_certificates": "Expiring soon certificates",
|
||||
"dashboard.statistics.expired_certificates": "Expired certificates",
|
||||
"dashboard.statistics.all_workflows": "All workflows",
|
||||
"dashboard.statistics.enabled_workflows": "Active workflows",
|
||||
|
||||
@@ -74,12 +74,32 @@
|
||||
"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.drawer.disabled.on.tooltip": "Disable",
|
||||
"workflow.detail.design.drawer.disabled.off.tooltip": "Enable",
|
||||
"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.action.import.button": "Import",
|
||||
"workflow.detail.design.action.import.modal.title": "Import workflow",
|
||||
"workflow.detail.design.action.import.modal.ok_button": "Import",
|
||||
"workflow.detail.design.action.import.form.format.label": "Format",
|
||||
"workflow.detail.design.action.import.form.content.label": "Content",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid": "Please enter a valid content",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.first_node_start": "The first node must be a start node",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.last_node_end": "The last node must be an end node",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.duplicate_start": "The start node must be unique",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid_id": "Node ID invalid: #{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid_config": "Node config invalid: #{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.conflict_id": "Node ID conflict: #{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.abnormal_condition_branch": "Abnormal parallel/conditional branch: #{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.abnormal_try_catch_branch": "Abnormal execution result branch: #{{nodeId}}",
|
||||
"workflow.detail.design.action.export.button": "Export",
|
||||
"workflow.detail.design.action.export.modal.title": "Export workflow",
|
||||
"workflow.detail.design.action.export.form.format.label": "Format",
|
||||
"workflow.detail.design.action.export.form.content.label": "Content",
|
||||
"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?",
|
||||
|
||||
@@ -1025,10 +1025,18 @@
|
||||
"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.delay.label": "Delay",
|
||||
"workflow_node.delay.help": "Pause the execution, and wait for a certain amount of time.",
|
||||
"workflow_node.delay.default_name": "Delay",
|
||||
"workflow_node.delay.form_anchor.parameters.tab": "Parameters",
|
||||
"workflow_node.delay.form.wait.label": "Waiting time",
|
||||
"workflow_node.delay.form.wait.placeholder": "Please enter waiting time",
|
||||
"workflow_node.delay.form.wait.unit": "seconds",
|
||||
|
||||
"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_expire_soon": "If the certificate will be expiring soon ...",
|
||||
"workflow_node.condition.default_name.template_certtest_on_expired": "If the certificate has expired ...",
|
||||
|
||||
"workflow_node.branch_block.label": "Branch",
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
"workflow_run.props.started_at": "Started at",
|
||||
"workflow_run.props.ended_at": "Ended at",
|
||||
|
||||
"workflow_run.base.description": "Triggered {{trigger}} at {{startedAt}}",
|
||||
"workflow_run.base.description_with_time_cost": "Triggered {{trigger}} at {{startedAt}}. Time cost: {{timeCost}}.",
|
||||
"workflow_run.base.trigger.scheduled": "scheduledly",
|
||||
"workflow_run.base.trigger.manual": "manually",
|
||||
|
||||
"workflow_run.process": "Process",
|
||||
"workflow_run.process.menu.export": "Export",
|
||||
|
||||
"workflow_run.logs": "Logs",
|
||||
"workflow_run.logs.menu.show_timestamps": "Show timestamps",
|
||||
"workflow_run.logs.menu.show_whitespaces": "Show whitespaces",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"common.button.add": "新增",
|
||||
"common.button.cancel": "取消",
|
||||
"common.button.close": "关闭",
|
||||
"common.button.confirm": "确定",
|
||||
"common.button.create": "新建",
|
||||
"common.button.copy": "复制",
|
||||
"common.button.create": "新建",
|
||||
"common.button.delete": "刪除",
|
||||
"common.button.download": "下载",
|
||||
"common.button.edit": "编辑",
|
||||
@@ -23,6 +24,7 @@
|
||||
"common.text.operation_succeeded": "操作成功",
|
||||
"common.text.operation_failed": "操作失败",
|
||||
"common.text.request_error": "请求错误",
|
||||
"common.text.saving": "保存中 ……",
|
||||
"common.text.search": "搜索 ……",
|
||||
|
||||
"common.menu.document": "文档",
|
||||
|
||||
@@ -74,12 +74,32 @@
|
||||
"workflow.detail.design.toolbar.hand_mode": "触摸模式",
|
||||
"workflow.detail.design.toolbar.pointer_mode": "指针模式",
|
||||
"workflow.detail.design.drawer.node_id.label": "节点 ID:",
|
||||
"workflow.detail.design.drawer.disabled.on.tooltip": "禁用",
|
||||
"workflow.detail.design.drawer.disabled.off.tooltip": "启用",
|
||||
"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.action.import.button": "导入",
|
||||
"workflow.detail.design.action.import.modal.title": "导入工作流",
|
||||
"workflow.detail.design.action.import.modal.ok_button": "导入",
|
||||
"workflow.detail.design.action.import.form.format.label": "格式",
|
||||
"workflow.detail.design.action.import.form.content.label": "内容",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid": "请输入有效的内容",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.first_node_start": "第一个节点必须是开始节点",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.last_node_end": "最后一个节点必须是结束节点",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.duplicate_start": "有且只能有一个开始节点",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid_id": "节点 ID 无效:#{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.invalid_config": "节点配置无效:#{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.conflict_id": "节点 ID 冲突:#{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.abnormal_condition_branch": "并行/条件分支结构异常:#{{nodeId}}",
|
||||
"workflow.detail.design.action.import.form.content.errmsg.abnormal_try_catch_branch": "执行结果分支结构异常:#{{nodeId}}",
|
||||
"workflow.detail.design.action.export.button": "导出",
|
||||
"workflow.detail.design.action.export.modal.title": "导出工作流",
|
||||
"workflow.detail.design.action.export.form.format.label": "格式",
|
||||
"workflow.detail.design.action.export.form.content.label": "内容",
|
||||
"workflow.detail.design.uncompleted_design.alert": "流程编排未就绪,请检查是否有节点未完成配置。",
|
||||
"workflow.detail.design.unpublished_draft.alert": "当前编排有尚未发布的更改。",
|
||||
"workflow.detail.design.unsaved_changes.confirm": "你有尚未保存的更改。确定要关闭面板吗?",
|
||||
|
||||
@@ -1023,6 +1023,14 @@
|
||||
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.on": "跳过",
|
||||
"workflow_node.notify.form.skip_on_all_prev_skipped.switch.off": "不跳过",
|
||||
|
||||
"workflow_node.delay.label": "延迟等待",
|
||||
"workflow_node.delay.help": "暂停执行工作流并等待一段时间。这在某些需要降低调用执行频率的场景很有用。",
|
||||
"workflow_node.delay.default_name": "延迟",
|
||||
"workflow_node.delay.form_anchor.parameters.tab": "参数设置",
|
||||
"workflow_node.delay.form.wait.label": "等待时间",
|
||||
"workflow_node.delay.form.wait.placeholder": "请输入等待时间",
|
||||
"workflow_node.delay.form.wait.unit": "秒",
|
||||
|
||||
"workflow_node.condition.label": "并行/条件分支",
|
||||
"workflow_node.condition.help": "当满足指定的条件时,进入相应分支。某一分支中的节点执行失败不影响平行分支继续执行。",
|
||||
"workflow_node.condition.default_name": "并行",
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
"workflow_run.props.started_at": "开始时间",
|
||||
"workflow_run.props.ended_at": "完成时间",
|
||||
|
||||
"workflow_run.base.description": "{{trigger}}触发于 {{startedAt}}",
|
||||
"workflow_run.base.description_with_time_cost": "{{trigger}}触发于 {{startedAt}},总计用时 {{timeCost}}。",
|
||||
"workflow_run.base.trigger.scheduled": "定时",
|
||||
"workflow_run.base.trigger.manual": "手动",
|
||||
|
||||
"workflow_run.process": "流程",
|
||||
"workflow_run.process.menu.export": "导出",
|
||||
|
||||
"workflow_run.logs": "日志",
|
||||
"workflow_run.logs.menu.show_timestamps": "显示日期时间",
|
||||
"workflow_run.logs.menu.show_whitespaces": "显示转义换行符",
|
||||
|
||||
@@ -40,10 +40,10 @@ const ConsoleLayout = () => {
|
||||
};
|
||||
|
||||
const handleDocumentClick = () => {
|
||||
if (i18n.language.startsWith("en")) {
|
||||
window.open(APP_DOCUMENT_URL + "/en/", "_blank");
|
||||
} else {
|
||||
if (i18n.language.startsWith("zh")) {
|
||||
window.open(APP_DOCUMENT_URL, "_blank");
|
||||
} else {
|
||||
window.open(APP_DOCUMENT_URL + "/en/", "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import Show from "@/components/Show";
|
||||
import { type AccessModel } from "@/domain/access";
|
||||
import { ACCESS_USAGES, accessProvidersMap } from "@/domain/provider";
|
||||
import { useAppSettings, useZustandShallowSelector } from "@/hooks";
|
||||
import { get as getAccess } from "@/repository/access";
|
||||
import { useAccessesStore } from "@/stores/access";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
@@ -222,7 +223,7 @@ const AccessList = () => {
|
||||
case "ca":
|
||||
return e.reserve === "ca" && provider?.usages?.includes(ACCESS_USAGES.CA);
|
||||
case "notification":
|
||||
return e.reserve === "notification" && provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
return e.reserve === "notif" && provider?.usages?.includes(ACCESS_USAGES.NOTIFICATION);
|
||||
}
|
||||
});
|
||||
return Promise.resolve({
|
||||
@@ -281,17 +282,18 @@ const AccessList = () => {
|
||||
navigate(`/accesses/new?usage=${filters["usage"]}`);
|
||||
};
|
||||
|
||||
const { drawerProps: createDrawerProps, ...createDrawer } = AccessEditDrawer.useDrawer();
|
||||
const { drawerProps: detailDrawerProps, ...detailDrawer } = AccessEditDrawer.useDrawer();
|
||||
const [detailMode, setDetailMode] = useState<AccessEditDrawerProps["mode"]>("create");
|
||||
|
||||
const handleRecordDetailClick = (access: AccessModel) => {
|
||||
setDetailMode("edit");
|
||||
detailDrawer.open(access);
|
||||
const drawer = detailDrawer.open({ data: access, loading: true });
|
||||
getAccess(access.id).then((data) => {
|
||||
drawer.safeUpdate({ data, loading: false });
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecordDuplicateClick = (access: AccessModel) => {
|
||||
setDetailMode("create");
|
||||
detailDrawer.open({ ...access, id: void 0, name: `${access.name}-copy` });
|
||||
createDrawer.open({ data: { ...access, id: void 0, name: `${access.name}-copy` } });
|
||||
};
|
||||
|
||||
const handleRecordDeleteClick = async (access: AccessModel) => {
|
||||
@@ -461,7 +463,8 @@ const AccessList = () => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<AccessEditDrawer mode={detailMode} usage={filters["usage"] as AccessUsages} {...detailDrawerProps} />
|
||||
<AccessEditDrawer mode="create" usage={filters["usage"] as AccessUsages} {...createDrawerProps} />
|
||||
<AccessEditDrawer mode="edit" usage={filters["usage"] as AccessUsages} {...detailDrawerProps} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,12 @@ import Empty from "@/components/Empty";
|
||||
import Show from "@/components/Show";
|
||||
import { type CertificateModel } from "@/domain/certificate";
|
||||
import { useAppSettings } from "@/hooks";
|
||||
import { list as listCertificates, type ListRequest as listCertificatesRequest, remove as removeCertificate } from "@/repository/certificate";
|
||||
import {
|
||||
get as getCertificate,
|
||||
list as listCertificates,
|
||||
type ListRequest as listCertificatesRequest,
|
||||
remove as removeCertificate,
|
||||
} from "@/repository/certificate";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
const CertificateList = () => {
|
||||
@@ -288,7 +293,10 @@ const CertificateList = () => {
|
||||
const { drawerProps: detailDrawerProps, ...detailDrawer } = CertificateDetailDrawer.useDrawer();
|
||||
|
||||
const handleRecordDetailClick = (certificate: CertificateModel) => {
|
||||
detailDrawer.open(certificate);
|
||||
const drawer = detailDrawer.open({ data: certificate, loading: true });
|
||||
getCertificate(certificate.id).then((data) => {
|
||||
drawer.safeUpdate({ data, loading: false });
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecordDeleteClick = (certificate: CertificateModel) => {
|
||||
@@ -365,7 +373,7 @@ const CertificateList = () => {
|
||||
<Segmented
|
||||
options={[
|
||||
{ label: <span className="text-sm">{t("certificate.props.validity.filter.all")}</span>, value: "" },
|
||||
{ label: <span className="text-sm">{t("certificate.props.validity.filter.expire_soon")}</span>, value: "expireSoon" },
|
||||
{ label: <span className="text-sm">{t("certificate.props.validity.filter.expire_soon")}</span>, value: "expiringSoon" },
|
||||
{ label: <span className="text-sm">{t("certificate.props.validity.filter.expired")}</span>, value: "expired" },
|
||||
]}
|
||||
size="large"
|
||||
|
||||
@@ -26,13 +26,11 @@ import WorkflowStatus from "@/components/workflow/WorkflowStatus";
|
||||
import { type Statistics } from "@/domain/statistics";
|
||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { useBrowserTheme } from "@/hooks";
|
||||
import { list as listWorkflowRuns } from "@/repository/workflowRun";
|
||||
import { get as getWorkflowRun, list as listWorkflowRuns } from "@/repository/workflowRun";
|
||||
import { mergeCls } from "@/utils/css";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
const Dashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -48,17 +46,7 @@ const Dashboard = () => {
|
||||
|
||||
<div className="mt-8">
|
||||
<h3>{t("dashboard.shortcut")}</h3>
|
||||
<div className="flex items-center gap-4 not-md:flex-wrap">
|
||||
<Button icon={<IconCirclePlus size="1em" />} shape="round" size="large" type="primary" onClick={() => navigate("/workflows/new")}>
|
||||
<span className="text-sm">{t("dashboard.shortcut.create_workflow")}</span>
|
||||
</Button>
|
||||
<Button icon={<IconLock size="1em" />} shape="round" size="large" onClick={() => navigate("/settings/account")}>
|
||||
<span className="text-sm">{t("dashboard.shortcut.change_account")}</span>
|
||||
</Button>
|
||||
<Button icon={<IconPlugConnected size="1em" />} shape="round" size="large" onClick={() => navigate("/settings/ssl-provider")}>
|
||||
<span className="text-sm">{t("dashboard.shortcut.configure_ca")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Shortcuts />
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
@@ -185,8 +173,8 @@ const StatisticCards = ({ className, style }: { className?: string; style?: Reac
|
||||
icon={<IconAlertHexagon size={48} />}
|
||||
label={t("dashboard.statistics.expire_soon_certificates")}
|
||||
loading={loading}
|
||||
value={statistics?.certificateExpireSoon ?? "-"}
|
||||
onClick={() => navigate("/certificates?state=expireSoon")}
|
||||
value={statistics?.certificateExpiringSoon ?? "-"}
|
||||
onClick={() => navigate("/certificates?state=expiringSoon")}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="overflow-hidden" {...cardGridSpans}>
|
||||
@@ -224,6 +212,46 @@ const StatisticCards = ({ className, style }: { className?: string; style?: Reac
|
||||
);
|
||||
};
|
||||
|
||||
const Shortcuts = ({ className, style }: { className?: string; style?: React.CSSProperties }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<div className="flex items-center gap-4 not-md:flex-wrap">
|
||||
<Button
|
||||
className="shadow"
|
||||
icon={<IconCirclePlus color="var(--color-primary)" size="1.25em" />}
|
||||
shape="round"
|
||||
size="large"
|
||||
onClick={() => navigate("/workflows/new")}
|
||||
>
|
||||
<span className="text-sm">{t("dashboard.shortcut.create_workflow")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className="shadow"
|
||||
icon={<IconLock color="var(--color-warning)" size="1.25em" />}
|
||||
shape="round"
|
||||
size="large"
|
||||
onClick={() => navigate("/settings/account")}
|
||||
>
|
||||
<span className="text-sm">{t("dashboard.shortcut.change_account")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className="shadow"
|
||||
icon={<IconPlugConnected color="var(--color-info)" size="1.25em" />}
|
||||
shape="round"
|
||||
size="large"
|
||||
onClick={() => navigate("/settings/ssl-provider")}
|
||||
>
|
||||
<span className="text-sm">{t("dashboard.shortcut.configure_ca")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WorkflowRunHistoryTable = ({ className, style }: { className?: string; style?: React.CSSProperties }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -332,7 +360,10 @@ const WorkflowRunHistoryTable = ({ className, style }: { className?: string; sty
|
||||
const { drawerProps: detailDrawerProps, ...detailDrawer } = WorkflowRunDetailDrawer.useDrawer();
|
||||
|
||||
const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => {
|
||||
detailDrawer.open(workflowRun);
|
||||
const drawer = detailDrawer.open({ data: workflowRun, loading: true });
|
||||
getWorkflowRun(workflowRun.id).then((data) => {
|
||||
drawer.safeUpdate({ data, loading: false });
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -65,13 +65,12 @@ const WorkflowDetail = () => {
|
||||
}, [workflow.lastRunStatus]);
|
||||
|
||||
const handleRunClick = () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
if (workflow.hasDraft) {
|
||||
modal.confirm({
|
||||
title: t("workflow.action.run.modal.title"),
|
||||
content: t("workflow.action.run.modal.content"),
|
||||
onOk: () => resolve(void 0),
|
||||
onCancel: () => reject(),
|
||||
});
|
||||
} else {
|
||||
resolve(void 0);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconArrowBackUp, IconDots } from "@tabler/icons-react";
|
||||
import { IconArrowBackUp, IconDots, IconTransferIn, IconTransferOut } from "@tabler/icons-react";
|
||||
import { useDeepCompareEffect } from "ahooks";
|
||||
import { Alert, App, Button, Card, Dropdown, Result, Space, theme } from "antd";
|
||||
import { debounce } from "radash";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { WorkflowDesigner, type WorkflowDesignerInstance, WorkflowNodeDrawer, WorkflowToolbar } from "@/components/workflow/designer";
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
import WorkflowGraphExportModal from "@/components/workflow/WorkflowGraphExportModal";
|
||||
import WorkflowGraphImportModal from "@/components/workflow/WorkflowGraphImportModal";
|
||||
import { useZustandShallowSelector } from "@/hooks";
|
||||
import { useWorkflowStore } from "@/stores/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
@@ -20,16 +21,8 @@ const WorkflowDetailDesign = () => {
|
||||
|
||||
const { workflow, ...workflowStore } = useWorkflowStore(useZustandShallowSelector(["workflow", "orchestrate", "publish", "rollback"]));
|
||||
|
||||
const [workflowRunDisabled, setWorkflowRunDisabled] = useState(false);
|
||||
const workflowRollbackDisabled = useMemo(
|
||||
() => workflowRunDisabled || !workflow.hasDraft || !workflow.hasContent,
|
||||
[workflowRunDisabled, workflow.hasDraft, workflow.hasContent]
|
||||
);
|
||||
const workflowPublishDisabled = useMemo(() => workflowRunDisabled || !workflow.hasDraft, [workflowRunDisabled, workflow.hasDraft]);
|
||||
useEffect(() => {
|
||||
const running = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PROCESSING;
|
||||
setWorkflowRunDisabled(running);
|
||||
}, [workflow.lastRunStatus]);
|
||||
const workflowRollbackDisabled = useMemo(() => !workflow.hasDraft || !workflow.hasContent, [workflow.hasDraft, workflow.hasContent]);
|
||||
const workflowPublishDisabled = useMemo(() => !workflow.hasDraft, [workflow.hasDraft]);
|
||||
|
||||
const designerRef = useRef<WorkflowDesignerInstance>(null);
|
||||
const designerPending = useRef(false); // 保存中时阻止刷新画布
|
||||
@@ -48,6 +41,8 @@ const WorkflowDetailDesign = () => {
|
||||
}
|
||||
}, [workflow.graphDraft]);
|
||||
|
||||
const { drawerProps: designerNodeDrawerProps, ...designerNodeDrawer } = WorkflowNodeDrawer.useDrawer();
|
||||
|
||||
const handleDesignerDocumentChange = debounce({ delay: 300 }, async () => {
|
||||
if (designerRef.current == null || designerRef.current.document.disposed) return;
|
||||
|
||||
@@ -102,7 +97,30 @@ const WorkflowDetailDesign = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const { drawerProps: designerNodeDrawerProps, ...designerNodeDrawer } = WorkflowNodeDrawer.useDrawer();
|
||||
const { modalProps: graphImportModalProps, ...graphImportModal } = WorkflowGraphImportModal.useModal();
|
||||
const { modalProps: graphExportModalProps, ...graphExportModal } = WorkflowGraphExportModal.useModal();
|
||||
|
||||
const handleImportClick = async () => {
|
||||
graphImportModal.open().then(async (graph) => {
|
||||
const loadingKey = Math.random().toString(36).substring(0, 8);
|
||||
message.loading({ key: loadingKey, content: t("common.text.saving"), duration: 0 });
|
||||
|
||||
try {
|
||||
await workflowStore.orchestrate(graph);
|
||||
|
||||
message.destroy(loadingKey);
|
||||
message.success(t("common.text.operation_succeeded"));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
message.destroy(loadingKey);
|
||||
notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleExportClick = () => {
|
||||
graphExportModal.open({ data: workflow.graphDraft! });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="size-full">
|
||||
@@ -140,6 +158,21 @@ const WorkflowDetailDesign = () => {
|
||||
icon: <IconArrowBackUp size="1.25em" />,
|
||||
onClick: handleRollbackClick,
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "import",
|
||||
label: t("workflow.detail.design.action.import.button"),
|
||||
icon: <IconTransferIn size="1.25em" />,
|
||||
onClick: handleImportClick,
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
label: t("workflow.detail.design.action.export.button"),
|
||||
icon: <IconTransferOut size="1.25em" />,
|
||||
onClick: handleExportClick,
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
@@ -173,6 +206,9 @@ const WorkflowDetailDesign = () => {
|
||||
|
||||
<WorkflowNodeDrawer {...designerNodeDrawerProps} />
|
||||
</WorkflowDesigner>
|
||||
|
||||
<WorkflowGraphImportModal {...graphImportModalProps} />
|
||||
<WorkflowGraphExportModal {...graphExportModalProps} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { useAppSettings, useZustandShallowSelector } from "@/hooks";
|
||||
import {
|
||||
get as getWorkflowRun,
|
||||
list as listWorkflowRuns,
|
||||
remove as removeWorkflowRun,
|
||||
subscribe as subscribeWorkflowRun,
|
||||
@@ -260,7 +261,10 @@ const WorkflowDetailRuns = () => {
|
||||
const { drawerProps: detailDrawerProps, ...detailDrawer } = WorkflowRunDetailDrawer.useDrawer();
|
||||
|
||||
const handleRecordDetailClick = (workflowRun: WorkflowRunModel) => {
|
||||
detailDrawer.open(workflowRun);
|
||||
const drawer = detailDrawer.open({ data: workflowRun, loading: true });
|
||||
getWorkflowRun(workflowRun.id).then((data) => {
|
||||
drawer.safeUpdate({ data, loading: false });
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecordCancelClick = (workflowRun: WorkflowRunModel) => {
|
||||
|
||||
@@ -66,7 +66,6 @@ const WorkflowNew = () => {
|
||||
const tryCatchNode = newNode(WORKFLOW_NODE_TYPES.TRYCATCH, { i18n: i18n });
|
||||
const applyNode = newNode(WORKFLOW_NODE_TYPES.BIZ_APPLY, { i18n: i18n });
|
||||
const deployNode = newNode(WORKFLOW_NODE_TYPES.BIZ_DEPLOY, { i18n: i18n });
|
||||
const notifyOnSuccessNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const notifyOnFailureNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const endNode = newNode(WORKFLOW_NODE_TYPES.END, { i18n: i18n });
|
||||
|
||||
@@ -75,13 +74,6 @@ const WorkflowNew = () => {
|
||||
certificateOutputNodeId: applyNode.id,
|
||||
} as WorkflowNodeConfigForBizDeploy;
|
||||
|
||||
notifyOnSuccessNode.data.config = {
|
||||
...notifyOnSuccessNode.data.config,
|
||||
subject: "[Certimate] Workflow Complete",
|
||||
message: "Your workflow run has completed successfully.",
|
||||
skipOnAllPrevSkipped: true,
|
||||
} as WorkflowNodeConfigForBizNotify;
|
||||
|
||||
notifyOnFailureNode.data.config = {
|
||||
...notifyOnFailureNode.data.config,
|
||||
subject: "[Certimate] Workflow Failure Alert!",
|
||||
@@ -89,7 +81,7 @@ const WorkflowNew = () => {
|
||||
} as WorkflowNodeConfigForBizNotify;
|
||||
|
||||
tryCatchNode.blocks!.at(0)!.blocks ??= [];
|
||||
tryCatchNode.blocks!.at(0)!.blocks!.push(applyNode, deployNode, notifyOnSuccessNode);
|
||||
tryCatchNode.blocks!.at(0)!.blocks!.push(applyNode, deployNode);
|
||||
tryCatchNode.blocks!.at(1)!.blocks ??= [];
|
||||
tryCatchNode.blocks!.at(1)!.blocks!.unshift(notifyOnFailureNode);
|
||||
|
||||
@@ -103,15 +95,15 @@ const WorkflowNew = () => {
|
||||
const tryCatchNode = newNode(WORKFLOW_NODE_TYPES.TRYCATCH, { i18n: i18n });
|
||||
const monitorNode = newNode(WORKFLOW_NODE_TYPES.BIZ_MONITOR, { i18n: i18n });
|
||||
const conditionNode = newNode(WORKFLOW_NODE_TYPES.CONDITION, { i18n: i18n });
|
||||
const notifyOnExpireSoonNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const notifyOnExpiringSoonNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const notifyOnExpiredNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const notifyOnFailureNode = newNode(WORKFLOW_NODE_TYPES.BIZ_NOTIFY, { i18n: i18n });
|
||||
const endNode = newNode(WORKFLOW_NODE_TYPES.END, { i18n: i18n });
|
||||
|
||||
notifyOnExpireSoonNode.data.config = {
|
||||
...notifyOnExpireSoonNode.data.config,
|
||||
notifyOnExpiringSoonNode.data.config = {
|
||||
...notifyOnExpiringSoonNode.data.config,
|
||||
subject: "[Certimate] Certificate Expiry Alert!",
|
||||
message: "The certificate which you are monitoring will expire soon. Please pay attention to your website.",
|
||||
message: "The certificate which you are monitoring will be expiring soon. Please pay attention to your website.",
|
||||
} as WorkflowNodeConfigForBizNotify;
|
||||
|
||||
notifyOnExpiredNode.data.config = {
|
||||
@@ -174,7 +166,7 @@ const WorkflowNew = () => {
|
||||
},
|
||||
} as WorkflowNodeConfigForBranchBlock;
|
||||
conditionNode.blocks!.at(0)!.blocks ??= [];
|
||||
conditionNode.blocks!.at(0)!.blocks!.push(notifyOnExpireSoonNode);
|
||||
conditionNode.blocks!.at(0)!.blocks!.push(notifyOnExpiringSoonNode);
|
||||
conditionNode.blocks!.at(1)!.data.name = t("workflow_node.condition.default_name.template_certtest_on_expired");
|
||||
conditionNode.blocks!.at(1)!.data.config = {
|
||||
...conditionNode.blocks!.at(1)!.data.config,
|
||||
|
||||
@@ -4,18 +4,27 @@ import { type AccessModel } from "@/domain/access";
|
||||
import { COLLECTION_NAME_ACCESS, getPocketBase } from "./_pocketbase";
|
||||
|
||||
export const list = async () => {
|
||||
const list = await getPocketBase().collection(COLLECTION_NAME_ACCESS).getFullList<AccessModel>({
|
||||
batch: 65535,
|
||||
filter: "deleted=null",
|
||||
sort: "-created",
|
||||
requestKey: null,
|
||||
});
|
||||
const list = await getPocketBase()
|
||||
.collection(COLLECTION_NAME_ACCESS)
|
||||
.getFullList<AccessModel>({
|
||||
batch: 65535,
|
||||
fields: ["id", "name", "provider", "reserve", "created", "updated", "deleted"].join(","),
|
||||
filter: "deleted=null",
|
||||
sort: "-created",
|
||||
requestKey: null,
|
||||
});
|
||||
return {
|
||||
totalItems: list.length,
|
||||
items: list,
|
||||
};
|
||||
};
|
||||
|
||||
export const get = async (id: string) => {
|
||||
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).getOne<AccessModel>(id, {
|
||||
requestKey: null,
|
||||
});
|
||||
};
|
||||
|
||||
export const save = async (record: MaybeModelRecord<AccessModel>) => {
|
||||
if (record.id) {
|
||||
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).update<AccessModel>(record.id, record);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
|
||||
|
||||
export type ListRequest = {
|
||||
keyword?: string;
|
||||
state?: "expireSoon" | "expired";
|
||||
state?: "expiringSoon" | "expired";
|
||||
sort?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
@@ -18,7 +18,7 @@ export const list = async (request: ListRequest) => {
|
||||
if (request.keyword) {
|
||||
filters.push(pb.filter("(id={:keyword} || serialNumber={:keyword} || subjectAltNames~{:keyword})", { keyword: request.keyword }));
|
||||
}
|
||||
if (request.state === "expireSoon") {
|
||||
if (request.state === "expiringSoon") {
|
||||
filters.push(pb.filter("validityNotAfter<{:expiredAt} && validityNotAfter>@now", { expiredAt: dayjs().add(20, "d").toDate() }));
|
||||
} else if (request.state === "expired") {
|
||||
filters.push(pb.filter("validityNotAfter<={:expiredAt}", { expiredAt: new Date() }));
|
||||
@@ -30,7 +30,23 @@ export const list = async (request: ListRequest) => {
|
||||
const perPage = request.perPage || 10;
|
||||
|
||||
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList<CertificateModel>(page, perPage, {
|
||||
expand: "workflowRef",
|
||||
expand: ["workflowRef"].join(","),
|
||||
fields: [
|
||||
"id",
|
||||
"source",
|
||||
"subjectAltNames",
|
||||
"serialNumber",
|
||||
"issuerOrg",
|
||||
"keyAlgorithm",
|
||||
"validityNotBefore",
|
||||
"validityNotAfter",
|
||||
"created",
|
||||
"updated",
|
||||
"deleted",
|
||||
"expand.workflowRef.id",
|
||||
"expand.workflowRef.name",
|
||||
"expand.workflowRef.description",
|
||||
].join(","),
|
||||
filter: filters.join(" && "),
|
||||
sort: sort,
|
||||
requestKey: null,
|
||||
@@ -53,6 +69,15 @@ export const listByWorkflowRunId = async (workflowRunId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const get = async (id: string) => {
|
||||
return await getPocketBase()
|
||||
.collection(COLLECTION_NAME_CERTIFICATE)
|
||||
.getOne<CertificateModel>(id, {
|
||||
expand: ["workflowRef"].join(","),
|
||||
requestKey: null,
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = async (record: MaybeModelRecordWithId<CertificateModel> | MaybeModelRecordWithId<CertificateModel>[]) => {
|
||||
const pb = getPocketBase();
|
||||
|
||||
|
||||
@@ -29,7 +29,29 @@ export const list = async (request: ListRequest) => {
|
||||
const perPage = request.perPage || 10;
|
||||
|
||||
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList<WorkflowModel>(page, perPage, {
|
||||
expand: request.expand ? "lastRunRef" : void 0,
|
||||
expand: request.expand ? ["lastRunRef"].join(",") : void 0,
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"trigger",
|
||||
"triggerCron",
|
||||
"enabled",
|
||||
"hasDraft",
|
||||
"hasContent",
|
||||
"lastRunRef",
|
||||
"lastRunStatus",
|
||||
"lastRunTime",
|
||||
"created",
|
||||
"updated",
|
||||
"deleted",
|
||||
"expand.lastRunRef.id",
|
||||
"expand.lastRunRef.status",
|
||||
"expand.lastRunRef.trigger",
|
||||
"expand.lastRunRef.startedAt",
|
||||
"expand.lastRunRef.endedAt",
|
||||
"expand.lastRunRef.error",
|
||||
].join(","),
|
||||
filter: filters.join(" && "),
|
||||
sort: sort,
|
||||
requestKey: null,
|
||||
@@ -37,10 +59,12 @@ export const list = async (request: ListRequest) => {
|
||||
};
|
||||
|
||||
export const get = async (id: string) => {
|
||||
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne<WorkflowModel>(id, {
|
||||
expand: "lastRunRef",
|
||||
requestKey: null,
|
||||
});
|
||||
return await getPocketBase()
|
||||
.collection(COLLECTION_NAME_WORKFLOW)
|
||||
.getOne<WorkflowModel>(id, {
|
||||
expand: ["lastRunRef"].join(","),
|
||||
requestKey: null,
|
||||
});
|
||||
};
|
||||
|
||||
export const save = async (record: MaybeModelRecord<WorkflowModel>) => {
|
||||
|
||||
@@ -22,13 +22,36 @@ export const list = async (request: ListRequest) => {
|
||||
const page = request.page || 1;
|
||||
const perPage = request.perPage || 10;
|
||||
return await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).getList<WorkflowRunModel>(page, perPage, {
|
||||
expand: request.expand ? "workflowRef" : void 0,
|
||||
expand: request.expand ? ["workflowRef"].join(",") : void 0,
|
||||
fields: [
|
||||
"id",
|
||||
"status",
|
||||
"trigger",
|
||||
"startedAt",
|
||||
"endedAt",
|
||||
"error",
|
||||
"created",
|
||||
"updated",
|
||||
"deleted",
|
||||
"expand.workflowRef.id",
|
||||
"expand.workflowRef.name",
|
||||
"expand.workflowRef.description",
|
||||
].join(","),
|
||||
filter: filters.join(" && "),
|
||||
sort: "-created",
|
||||
requestKey: null,
|
||||
});
|
||||
};
|
||||
|
||||
export const get = async (id: string) => {
|
||||
return await getPocketBase()
|
||||
.collection(COLLECTION_NAME_WORKFLOW_RUN)
|
||||
.getOne<WorkflowRunModel>(id, {
|
||||
expand: ["workflowRef"].join(","),
|
||||
requestKey: null,
|
||||
});
|
||||
};
|
||||
|
||||
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel> | MaybeModelRecordWithId<WorkflowRunModel>[]) => {
|
||||
const pb = getPocketBase();
|
||||
|
||||
|
||||
@@ -14,30 +14,15 @@ export const validDomainName = (value: string, { allowWildcard = false }: { allo
|
||||
};
|
||||
|
||||
export const validEmailAddress = (value: string) => {
|
||||
try {
|
||||
z.email().parse(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return z.email().safeParse(value).success;
|
||||
};
|
||||
|
||||
export const validIPv4Address = (value: string) => {
|
||||
try {
|
||||
z.ipv4().parse(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return z.ipv4().safeParse(value).success;
|
||||
};
|
||||
|
||||
export const validIPv6Address = (value: string) => {
|
||||
try {
|
||||
z.ipv6().parse(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
return z.ipv6().safeParse(value).success;
|
||||
};
|
||||
|
||||
export const validHttpOrHttpsUrl = (value: string) => {
|
||||
|
||||
Reference in New Issue
Block a user