mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
refactor code
This commit is contained in:
parent
75326b1ddd
commit
9cdc59b272
@ -3,6 +3,7 @@ package domain
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Value any
|
type Value any
|
||||||
@ -11,6 +12,7 @@ type (
|
|||||||
ComparisonOperator string
|
ComparisonOperator string
|
||||||
LogicalOperator string
|
LogicalOperator string
|
||||||
ValueType string
|
ValueType string
|
||||||
|
ExprType string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,6 +31,12 @@ const (
|
|||||||
Number ValueType = "number"
|
Number ValueType = "number"
|
||||||
String ValueType = "string"
|
String ValueType = "string"
|
||||||
Boolean ValueType = "boolean"
|
Boolean ValueType = "boolean"
|
||||||
|
|
||||||
|
ConstExprType ExprType = "const"
|
||||||
|
VarExprType ExprType = "var"
|
||||||
|
CompareExprType ExprType = "compare"
|
||||||
|
LogicalExprType ExprType = "logical"
|
||||||
|
NotExprType ExprType = "not"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EvalResult struct {
|
type EvalResult struct {
|
||||||
@ -40,14 +48,40 @@ func (e *EvalResult) GetFloat64() (float64, error) {
|
|||||||
if e.Type != Number {
|
if e.Type != Number {
|
||||||
return 0, fmt.Errorf("type mismatch: %s", e.Type)
|
return 0, fmt.Errorf("type mismatch: %s", e.Type)
|
||||||
}
|
}
|
||||||
switch v := e.Value.(type) {
|
|
||||||
case int:
|
stringValue, ok := e.Value.(string)
|
||||||
return float64(v), nil
|
if !ok {
|
||||||
case float64:
|
return 0, fmt.Errorf("value is not a string: %v", e.Value)
|
||||||
return v, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unsupported type: %T", v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
floatValue, err := strconv.ParseFloat(stringValue, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to parse float64: %v", err)
|
||||||
|
}
|
||||||
|
return floatValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EvalResult) GetBool() (bool, error) {
|
||||||
|
if e.Type != Boolean {
|
||||||
|
return false, fmt.Errorf("type mismatch: %s", e.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
strValue, ok := e.Value.(string)
|
||||||
|
if ok {
|
||||||
|
if strValue == "true" {
|
||||||
|
return true, nil
|
||||||
|
} else if strValue == "false" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("value is not a boolean: %v", e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
boolValue, ok := e.Value.(bool)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("value is not a boolean: %v", e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EvalResult) GreaterThan(other *EvalResult) (*EvalResult, error) {
|
func (e *EvalResult) GreaterThan(other *EvalResult) (*EvalResult, error) {
|
||||||
@ -232,9 +266,17 @@ func (e *EvalResult) And(other *EvalResult) (*EvalResult, error) {
|
|||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case Boolean:
|
case Boolean:
|
||||||
|
left, err := e.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := other.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &EvalResult{
|
return &EvalResult{
|
||||||
Type: Boolean,
|
Type: Boolean,
|
||||||
Value: e.Value.(bool) && other.Value.(bool),
|
Value: left && right,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
||||||
@ -247,9 +289,17 @@ func (e *EvalResult) Or(other *EvalResult) (*EvalResult, error) {
|
|||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case Boolean:
|
case Boolean:
|
||||||
|
left, err := e.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := other.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &EvalResult{
|
return &EvalResult{
|
||||||
Type: Boolean,
|
Type: Boolean,
|
||||||
Value: e.Value.(bool) || other.Value.(bool),
|
Value: left || right,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
||||||
@ -260,9 +310,13 @@ func (e *EvalResult) Not() (*EvalResult, error) {
|
|||||||
if e.Type != Boolean {
|
if e.Type != Boolean {
|
||||||
return nil, fmt.Errorf("type mismatch: %s", e.Type)
|
return nil, fmt.Errorf("type mismatch: %s", e.Type)
|
||||||
}
|
}
|
||||||
|
boolValue, err := e.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &EvalResult{
|
return &EvalResult{
|
||||||
Type: Boolean,
|
Type: Boolean,
|
||||||
Value: !e.Value.(bool),
|
Value: !boolValue,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,9 +326,17 @@ func (e *EvalResult) Is(other *EvalResult) (*EvalResult, error) {
|
|||||||
}
|
}
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case Boolean:
|
case Boolean:
|
||||||
|
left, err := e.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := other.GetBool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &EvalResult{
|
return &EvalResult{
|
||||||
Type: Boolean,
|
Type: Boolean,
|
||||||
Value: e.Value.(bool) == other.Value.(bool),
|
Value: left == right,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
return nil, fmt.Errorf("unsupported type: %s", e.Type)
|
||||||
@ -282,17 +344,17 @@ func (e *EvalResult) Is(other *EvalResult) (*EvalResult, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Expr interface {
|
type Expr interface {
|
||||||
GetType() string
|
GetType() ExprType
|
||||||
Eval(variables map[string]map[string]any) (*EvalResult, error)
|
Eval(variables map[string]map[string]any) (*EvalResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConstExpr struct {
|
type ConstExpr struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
Value Value `json:"value"`
|
Value Value `json:"value"`
|
||||||
ValueType ValueType `json:"valueType"`
|
ValueType ValueType `json:"valueType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ConstExpr) GetType() string { return c.Type }
|
func (c ConstExpr) GetType() ExprType { return c.Type }
|
||||||
|
|
||||||
func (c ConstExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
func (c ConstExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
||||||
return &EvalResult{
|
return &EvalResult{
|
||||||
@ -302,11 +364,11 @@ func (c ConstExpr) Eval(variables map[string]map[string]any) (*EvalResult, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VarExpr struct {
|
type VarExpr struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
Selector WorkflowNodeIOValueSelector `json:"selector"`
|
Selector WorkflowNodeIOValueSelector `json:"selector"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v VarExpr) GetType() string { return v.Type }
|
func (v VarExpr) GetType() ExprType { return v.Type }
|
||||||
|
|
||||||
func (v VarExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
func (v VarExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
||||||
if v.Selector.Id == "" {
|
if v.Selector.Id == "" {
|
||||||
@ -330,13 +392,13 @@ func (v VarExpr) Eval(variables map[string]map[string]any) (*EvalResult, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CompareExpr struct {
|
type CompareExpr struct {
|
||||||
Type string `json:"type"` // compare
|
Type ExprType `json:"type"` // compare
|
||||||
Op ComparisonOperator `json:"op"`
|
Op ComparisonOperator `json:"op"`
|
||||||
Left Expr `json:"left"`
|
Left Expr `json:"left"`
|
||||||
Right Expr `json:"right"`
|
Right Expr `json:"right"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c CompareExpr) GetType() string { return c.Type }
|
func (c CompareExpr) GetType() ExprType { return c.Type }
|
||||||
|
|
||||||
func (c CompareExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
func (c CompareExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
||||||
left, err := c.Left.Eval(variables)
|
left, err := c.Left.Eval(variables)
|
||||||
@ -369,13 +431,13 @@ func (c CompareExpr) Eval(variables map[string]map[string]any) (*EvalResult, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogicalExpr struct {
|
type LogicalExpr struct {
|
||||||
Type string `json:"type"` // logical
|
Type ExprType `json:"type"` // logical
|
||||||
Op LogicalOperator `json:"op"`
|
Op LogicalOperator `json:"op"`
|
||||||
Left Expr `json:"left"`
|
Left Expr `json:"left"`
|
||||||
Right Expr `json:"right"`
|
Right Expr `json:"right"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LogicalExpr) GetType() string { return l.Type }
|
func (l LogicalExpr) GetType() ExprType { return l.Type }
|
||||||
|
|
||||||
func (l LogicalExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
func (l LogicalExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
||||||
left, err := l.Left.Eval(variables)
|
left, err := l.Left.Eval(variables)
|
||||||
@ -398,11 +460,11 @@ func (l LogicalExpr) Eval(variables map[string]map[string]any) (*EvalResult, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NotExpr struct {
|
type NotExpr struct {
|
||||||
Type string `json:"type"` // not
|
Type ExprType `json:"type"` // not
|
||||||
Expr Expr `json:"expr"`
|
Expr Expr `json:"expr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n NotExpr) GetType() string { return n.Type }
|
func (n NotExpr) GetType() ExprType { return n.Type }
|
||||||
|
|
||||||
func (n NotExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
func (n NotExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
|
||||||
inner, err := n.Expr.Eval(variables)
|
inner, err := n.Expr.Eval(variables)
|
||||||
@ -413,7 +475,7 @@ func (n NotExpr) Eval(variables map[string]map[string]any) (*EvalResult, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type rawExpr struct {
|
type rawExpr struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarshalExpr(e Expr) ([]byte, error) {
|
func MarshalExpr(e Expr) ([]byte, error) {
|
||||||
@ -427,31 +489,31 @@ func UnmarshalExpr(data []byte) (Expr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch typ.Type {
|
switch typ.Type {
|
||||||
case "const":
|
case ConstExprType:
|
||||||
var e ConstExpr
|
var e ConstExpr
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e, nil
|
return e, nil
|
||||||
case "var":
|
case VarExprType:
|
||||||
var e VarExpr
|
var e VarExpr
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e, nil
|
return e, nil
|
||||||
case "compare":
|
case CompareExprType:
|
||||||
var e CompareExprRaw
|
var e CompareExprRaw
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e.ToCompareExpr()
|
return e.ToCompareExpr()
|
||||||
case "logical":
|
case LogicalExprType:
|
||||||
var e LogicalExprRaw
|
var e LogicalExprRaw
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return e.ToLogicalExpr()
|
return e.ToLogicalExpr()
|
||||||
case "not":
|
case NotExprType:
|
||||||
var e NotExprRaw
|
var e NotExprRaw
|
||||||
if err := json.Unmarshal(data, &e); err != nil {
|
if err := json.Unmarshal(data, &e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -463,7 +525,7 @@ func UnmarshalExpr(data []byte) (Expr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CompareExprRaw struct {
|
type CompareExprRaw struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
Op ComparisonOperator `json:"op"`
|
Op ComparisonOperator `json:"op"`
|
||||||
Left json.RawMessage `json:"left"`
|
Left json.RawMessage `json:"left"`
|
||||||
Right json.RawMessage `json:"right"`
|
Right json.RawMessage `json:"right"`
|
||||||
@ -487,7 +549,7 @@ func (r CompareExprRaw) ToCompareExpr() (CompareExpr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogicalExprRaw struct {
|
type LogicalExprRaw struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
Op LogicalOperator `json:"op"`
|
Op LogicalOperator `json:"op"`
|
||||||
Left json.RawMessage `json:"left"`
|
Left json.RawMessage `json:"left"`
|
||||||
Right json.RawMessage `json:"right"`
|
Right json.RawMessage `json:"right"`
|
||||||
@ -511,7 +573,7 @@ func (r LogicalExprRaw) ToLogicalExpr() (LogicalExpr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NotExprRaw struct {
|
type NotExprRaw struct {
|
||||||
Type string `json:"type"`
|
Type ExprType `json:"type"`
|
||||||
Expr json.RawMessage `json:"expr"`
|
Expr json.RawMessage `json:"expr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +88,10 @@ type WorkflowNodeConfigForCondition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowNodeConfigForInspect struct {
|
type WorkflowNodeConfigForInspect struct {
|
||||||
|
Host string `json:"host"` // 主机
|
||||||
Domain string `json:"domain"` // 域名
|
Domain string `json:"domain"` // 域名
|
||||||
Port string `json:"port"` // 端口
|
Port string `json:"port"` // 端口
|
||||||
|
Path string `json:"path"` // 路径
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowNodeConfigForUpload struct {
|
type WorkflowNodeConfigForUpload struct {
|
||||||
@ -134,9 +136,14 @@ func (n *WorkflowNode) GetConfigForCondition() WorkflowNodeConfigForCondition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *WorkflowNode) GetConfigForInspect() WorkflowNodeConfigForInspect {
|
func (n *WorkflowNode) GetConfigForInspect() WorkflowNodeConfigForInspect {
|
||||||
|
host := maputil.GetString(n.Config, "host")
|
||||||
|
if host == "" {
|
||||||
|
return WorkflowNodeConfigForInspect{}
|
||||||
|
}
|
||||||
|
|
||||||
domain := maputil.GetString(n.Config, "domain")
|
domain := maputil.GetString(n.Config, "domain")
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
return WorkflowNodeConfigForInspect{}
|
domain = host
|
||||||
}
|
}
|
||||||
|
|
||||||
port := maputil.GetString(n.Config, "port")
|
port := maputil.GetString(n.Config, "port")
|
||||||
@ -144,9 +151,13 @@ func (n *WorkflowNode) GetConfigForInspect() WorkflowNodeConfigForInspect {
|
|||||||
port = "443"
|
port = "443"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path := maputil.GetString(n.Config, "path")
|
||||||
|
|
||||||
return WorkflowNodeConfigForInspect{
|
return WorkflowNodeConfigForInspect{
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@ func (n *applyNode) Process(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加中间结果
|
// 添加中间结果
|
||||||
n.outputs["certificate.validated"] = true
|
n.outputs[outputCertificateValidatedKey] = "true"
|
||||||
n.outputs["certificate.daysLeft"] = int(time.Until(certificate.ExpireAt).Hours() / 24)
|
n.outputs[outputCertificateDaysLeftKey] = fmt.Sprintf("%d", int(time.Until(certificate.ExpireAt).Hours()/24))
|
||||||
|
|
||||||
n.logger.Info("apply completed")
|
n.logger.Info("apply completed")
|
||||||
|
|
||||||
@ -147,8 +147,8 @@ func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
|||||||
expirationTime := time.Until(lastCertificate.ExpireAt)
|
expirationTime := time.Until(lastCertificate.ExpireAt)
|
||||||
if expirationTime > renewalInterval {
|
if expirationTime > renewalInterval {
|
||||||
|
|
||||||
n.outputs["certificate.validated"] = true
|
n.outputs[outputCertificateValidatedKey] = "true"
|
||||||
n.outputs["certificate.daysLeft"] = int(expirationTime.Hours() / 24)
|
n.outputs[outputCertificateDaysLeftKey] = fmt.Sprintf("%d", int(expirationTime.Hours()/24))
|
||||||
|
|
||||||
return true, fmt.Sprintf("the certificate has already been issued (expires in %dd, next renewal in %dd)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
return true, fmt.Sprintf("the certificate has already been issued (expires in %dd, next renewal in %dd)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
||||||
}
|
}
|
||||||
|
6
internal/workflow/node-processor/const.go
Normal file
6
internal/workflow/node-processor/const.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package nodeprocessor
|
||||||
|
|
||||||
|
const (
|
||||||
|
outputCertificateValidatedKey = "certificate.validated"
|
||||||
|
outputCertificateDaysLeftKey = "certificate.daysLeft"
|
||||||
|
)
|
@ -3,9 +3,12 @@ package nodeprocessor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
@ -26,13 +29,13 @@ func NewInspectNode(node *domain.WorkflowNode) *inspectNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *inspectNode) Process(ctx context.Context) error {
|
func (n *inspectNode) Process(ctx context.Context) error {
|
||||||
n.logger.Info("enter inspect website certificate node ...")
|
n.logger.Info("entering inspect certificate node...")
|
||||||
|
|
||||||
nodeConfig := n.node.GetConfigForInspect()
|
nodeConfig := n.node.GetConfigForInspect()
|
||||||
|
|
||||||
err := n.inspect(ctx, nodeConfig)
|
err := n.inspect(ctx, nodeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.logger.Warn("inspect website certificate failed: " + err.Error())
|
n.logger.Warn("inspect certificate failed: " + err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,18 +43,35 @@ func (n *inspectNode) Process(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *inspectNode) inspect(ctx context.Context, nodeConfig domain.WorkflowNodeConfigForInspect) error {
|
func (n *inspectNode) inspect(ctx context.Context, nodeConfig domain.WorkflowNodeConfigForInspect) error {
|
||||||
// 定义重试参数
|
|
||||||
maxRetries := 3
|
maxRetries := 3
|
||||||
retryInterval := 2 * time.Second
|
retryInterval := 2 * time.Second
|
||||||
|
|
||||||
var cert *tls.Certificate
|
|
||||||
var lastError error
|
var lastError error
|
||||||
|
var certInfo *x509.Certificate
|
||||||
|
|
||||||
domainWithPort := nodeConfig.Domain + ":" + nodeConfig.Port
|
host := nodeConfig.Host
|
||||||
|
|
||||||
|
port := nodeConfig.Port
|
||||||
|
if port == "" {
|
||||||
|
port = "443"
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := nodeConfig.Domain
|
||||||
|
if domain == "" {
|
||||||
|
domain = host
|
||||||
|
}
|
||||||
|
|
||||||
|
path := nodeConfig.Path
|
||||||
|
if path != "" && !strings.HasPrefix(path, "/") {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAddr := fmt.Sprintf("%s:%s", host, port)
|
||||||
|
n.logger.Info(fmt.Sprintf("Inspecting certificate at %s (validating domain: %s)", targetAddr, domain))
|
||||||
|
|
||||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
n.logger.Info(fmt.Sprintf("Retry #%d connecting to %s", attempt, domainWithPort))
|
n.logger.Info(fmt.Sprintf("Retry #%d connecting to %s", attempt, targetAddr))
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
@ -60,30 +80,65 @@ func (n *inspectNode) inspect(ctx context.Context, nodeConfig domain.WorkflowNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := &net.Dialer{
|
transport := &http.Transport{
|
||||||
Timeout: 10 * time.Second,
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: domain, // Set SNI to domain for proper certificate selection
|
||||||
|
},
|
||||||
|
ForceAttemptHTTP2: false,
|
||||||
|
DisableKeepAlives: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := tls.DialWithDialer(dialer, "tcp", domainWithPort, &tls.Config{
|
client := &http.Client{
|
||||||
InsecureSkipVerify: true, // Allow self-signed certificates
|
Transport: transport,
|
||||||
})
|
Timeout: 15 * time.Second,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "https"
|
||||||
|
urlStr := fmt.Sprintf("%s://%s", scheme, targetAddr)
|
||||||
|
if path != "" {
|
||||||
|
urlStr = urlStr + path
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "HEAD", urlStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastError = fmt.Errorf("failed to connect to %s: %w", domainWithPort, err)
|
lastError = fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
n.logger.Warn(fmt.Sprintf("Request creation attempt #%d failed: %s", attempt+1, lastError.Error()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain != host {
|
||||||
|
req.Host = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "CertificateValidator/1.0")
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lastError = fmt.Errorf("HTTP request failed: %w", err)
|
||||||
n.logger.Warn(fmt.Sprintf("Connection attempt #%d failed: %s", attempt+1, lastError.Error()))
|
n.logger.Warn(fmt.Sprintf("Connection attempt #%d failed: %s", attempt+1, lastError.Error()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get certificate information
|
if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
|
||||||
certInfo := conn.ConnectionState().PeerCertificates[0]
|
resp.Body.Close()
|
||||||
conn.Close()
|
lastError = fmt.Errorf("no TLS certificates received in HTTP response")
|
||||||
|
n.logger.Warn(fmt.Sprintf("Certificate retrieval attempt #%d failed: %s", attempt+1, lastError.Error()))
|
||||||
// Certificate information retrieved successfully
|
continue
|
||||||
cert = &tls.Certificate{
|
|
||||||
Certificate: [][]byte{certInfo.Raw},
|
|
||||||
Leaf: certInfo,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certInfo = resp.TLS.PeerCertificates[0]
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
lastError = nil
|
lastError = nil
|
||||||
n.logger.Info(fmt.Sprintf("Successfully retrieved certificate information for %s", domainWithPort))
|
n.logger.Info(fmt.Sprintf("Successfully retrieved certificate from %s", targetAddr))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,69 +146,46 @@ func (n *inspectNode) inspect(ctx context.Context, nodeConfig domain.WorkflowNod
|
|||||||
return fmt.Errorf("failed to retrieve certificate after %d attempts: %w", maxRetries, lastError)
|
return fmt.Errorf("failed to retrieve certificate after %d attempts: %w", maxRetries, lastError)
|
||||||
}
|
}
|
||||||
|
|
||||||
certInfo := cert.Leaf
|
if certInfo == nil {
|
||||||
now := time.Now()
|
outputs := map[string]any{
|
||||||
|
outputCertificateValidatedKey: "false",
|
||||||
isValid := now.Before(certInfo.NotAfter) && now.After(certInfo.NotBefore)
|
outputCertificateDaysLeftKey: "0",
|
||||||
|
|
||||||
// Check domain matching
|
|
||||||
domainMatch := false
|
|
||||||
if len(certInfo.DNSNames) > 0 {
|
|
||||||
for _, dnsName := range certInfo.DNSNames {
|
|
||||||
if matchDomain(nodeConfig.Domain, dnsName) {
|
|
||||||
domainMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if matchDomain(nodeConfig.Domain, certInfo.Subject.CommonName) {
|
n.setOutputs(outputs)
|
||||||
domainMatch = true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid = isValid && domainMatch
|
now := time.Now()
|
||||||
|
|
||||||
|
isValidTime := now.Before(certInfo.NotAfter) && now.After(certInfo.NotBefore)
|
||||||
|
|
||||||
|
domainMatch := true
|
||||||
|
if err := certInfo.VerifyHostname(domain); err != nil {
|
||||||
|
domainMatch = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid := isValidTime && domainMatch
|
||||||
|
|
||||||
daysRemaining := math.Floor(certInfo.NotAfter.Sub(now).Hours() / 24)
|
daysRemaining := math.Floor(certInfo.NotAfter.Sub(now).Hours() / 24)
|
||||||
|
|
||||||
// Set node outputs
|
isValidStr := "false"
|
||||||
outputs := map[string]any{
|
if isValid {
|
||||||
"certificate.validated": isValid,
|
isValidStr = "true"
|
||||||
"certificate.daysLeft": daysRemaining,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputs := map[string]any{
|
||||||
|
outputCertificateValidatedKey: isValidStr,
|
||||||
|
outputCertificateDaysLeftKey: fmt.Sprintf("%d", int(daysRemaining)),
|
||||||
|
}
|
||||||
|
|
||||||
n.setOutputs(outputs)
|
n.setOutputs(outputs)
|
||||||
|
|
||||||
|
n.logger.Info(fmt.Sprintf("Certificate inspection completed - Target: %s, Domain: %s, Valid: %s, Days Remaining: %d",
|
||||||
|
targetAddr, domain, isValidStr, int(daysRemaining)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *inspectNode) setOutputs(outputs map[string]any) {
|
func (n *inspectNode) setOutputs(outputs map[string]any) {
|
||||||
n.outputs = outputs
|
n.outputs = outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchDomain(requestDomain, certDomain string) bool {
|
|
||||||
if requestDomain == certDomain {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(certDomain) > 2 && certDomain[0] == '*' && certDomain[1] == '.' {
|
|
||||||
|
|
||||||
wildcardSuffix := certDomain[1:]
|
|
||||||
requestDomainLen := len(requestDomain)
|
|
||||||
suffixLen := len(wildcardSuffix)
|
|
||||||
|
|
||||||
if requestDomainLen > suffixLen && requestDomain[requestDomainLen-suffixLen:] == wildcardSuffix {
|
|
||||||
remainingPart := requestDomain[:requestDomainLen-suffixLen]
|
|
||||||
if len(remainingPart) > 0 && !contains(remainingPart, '.') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(s string, c byte) bool {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if s[i] == c {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@ -69,8 +69,8 @@ func (n *uploadNode) Process(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n.outputs["certificate.validated"] = true
|
n.outputs[outputCertificateValidatedKey] = "true"
|
||||||
n.outputs["certificate.daysLeft"] = int(time.Until(certificate.ExpireAt).Hours() / 24)
|
n.outputs[outputCertificateDaysLeftKey] = fmt.Sprintf("%d", int(time.Until(certificate.ExpireAt).Hours()/24))
|
||||||
|
|
||||||
n.logger.Info("upload completed")
|
n.logger.Info("upload completed")
|
||||||
|
|
||||||
@ -91,8 +91,8 @@ func (n *uploadNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workfl
|
|||||||
|
|
||||||
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id)
|
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id)
|
||||||
if lastCertificate != nil {
|
if lastCertificate != nil {
|
||||||
n.outputs["certificate.validated"] = true
|
n.outputs[outputCertificateValidatedKey] = "true"
|
||||||
n.outputs["certificate.daysLeft"] = int(time.Until(lastCertificate.ExpireAt).Hours() / 24)
|
n.outputs[outputCertificateDaysLeftKey] = fmt.Sprintf("%d", int(time.Until(lastCertificate.ExpireAt).Hours()/24))
|
||||||
return true, "the certificate has already been uploaded"
|
return true, "the certificate has already been uploaded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { Button, Card, Popover } from "antd";
|
|||||||
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
|
import SharedNode, { type SharedNodeProps } from "./_SharedNode";
|
||||||
import AddNode from "./AddNode";
|
import AddNode from "./AddNode";
|
||||||
import ConditionNodeConfigForm, { ConditionItem, ConditionNodeConfigFormFieldValues, ConditionNodeConfigFormInstance } from "./ConditionNodeConfigForm";
|
import ConditionNodeConfigForm, { ConditionItem, ConditionNodeConfigFormFieldValues, ConditionNodeConfigFormInstance } from "./ConditionNodeConfigForm";
|
||||||
import { Expr, WorkflowNodeIoValueType, Value } from "@/domain/workflow";
|
import { Expr, WorkflowNodeIoValueType, ExprType } from "@/domain/workflow";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { useWorkflowStore } from "@/stores/workflow";
|
import { useWorkflowStore } from "@/stores/workflow";
|
||||||
import { useZustandShallowSelector } from "@/hooks";
|
import { useZustandShallowSelector } from "@/hooks";
|
||||||
@ -32,7 +32,7 @@ const ConditionNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeP
|
|||||||
const selectors = condition.leftSelector.split("#");
|
const selectors = condition.leftSelector.split("#");
|
||||||
const t = selectors[2] as WorkflowNodeIoValueType;
|
const t = selectors[2] as WorkflowNodeIoValueType;
|
||||||
const left: Expr = {
|
const left: Expr = {
|
||||||
type: "var",
|
type: ExprType.Var,
|
||||||
selector: {
|
selector: {
|
||||||
id: selectors[0],
|
id: selectors[0],
|
||||||
name: selectors[1],
|
name: selectors[1],
|
||||||
@ -40,27 +40,10 @@ const ConditionNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeP
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let value: Value = condition.rightValue;
|
const right: Expr = { type: ExprType.Const, value: condition.rightValue, valueType: t };
|
||||||
switch (t) {
|
|
||||||
case "boolean":
|
|
||||||
if (value === "true") {
|
|
||||||
value = true;
|
|
||||||
} else if (value === "false") {
|
|
||||||
value = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "number":
|
|
||||||
value = parseInt(value as string);
|
|
||||||
break;
|
|
||||||
case "string":
|
|
||||||
value = value as string;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const right: Expr = { type: "const", value: value, valueType: t };
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "compare",
|
type: ExprType.Compare,
|
||||||
op: condition.operator,
|
op: condition.operator,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
@ -77,7 +60,7 @@ const ConditionNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeP
|
|||||||
|
|
||||||
for (let i = 1; i < values.conditions.length; i++) {
|
for (let i = 1; i < values.conditions.length; i++) {
|
||||||
expr = {
|
expr = {
|
||||||
type: "logical",
|
type: ExprType.Logical,
|
||||||
op: values.logicalOperator,
|
op: values.logicalOperator,
|
||||||
left: expr,
|
left: expr,
|
||||||
right: createComparisonExpr(values.conditions[i]),
|
right: createComparisonExpr(values.conditions[i]),
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
WorkflowNode,
|
WorkflowNode,
|
||||||
workflowNodeIOOptions,
|
workflowNodeIOOptions,
|
||||||
WorkflowNodeIoValueType,
|
WorkflowNodeIoValueType,
|
||||||
|
ExprType,
|
||||||
} from "@/domain/workflow";
|
} from "@/domain/workflow";
|
||||||
import { FormInstance } from "antd";
|
import { FormInstance } from "antd";
|
||||||
import { useZustandShallowSelector } from "@/hooks";
|
import { useZustandShallowSelector } from "@/hooks";
|
||||||
@ -58,7 +59,7 @@ const initFormModel = (): ConditionNodeConfigFormFieldValues => {
|
|||||||
rightValue: "",
|
rightValue: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
logicalOperator: "and",
|
logicalOperator: LogicalOperator.And,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,10 +68,10 @@ const expressionToForm = (expr?: Expr): ConditionNodeConfigFormFieldValues => {
|
|||||||
if (!expr) return initFormModel();
|
if (!expr) return initFormModel();
|
||||||
|
|
||||||
const conditions: ConditionItem[] = [];
|
const conditions: ConditionItem[] = [];
|
||||||
let logicalOp: LogicalOperator = "and";
|
let logicalOp: LogicalOperator = LogicalOperator.And;
|
||||||
|
|
||||||
const extractComparisons = (expr: Expr): void => {
|
const extractComparisons = (expr: Expr): void => {
|
||||||
if (expr.type === "compare") {
|
if (expr.type === ExprType.Compare) {
|
||||||
// 确保左侧是变量,右侧是常量
|
// 确保左侧是变量,右侧是常量
|
||||||
if (isVarExpr(expr.left) && isConstExpr(expr.right)) {
|
if (isVarExpr(expr.left) && isConstExpr(expr.right)) {
|
||||||
conditions.push({
|
conditions.push({
|
||||||
@ -79,7 +80,7 @@ const expressionToForm = (expr?: Expr): ConditionNodeConfigFormFieldValues => {
|
|||||||
rightValue: String(expr.right.value),
|
rightValue: String(expr.right.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (expr.type === "logical") {
|
} else if (expr.type === ExprType.Logical) {
|
||||||
logicalOp = expr.op;
|
logicalOp = expr.op;
|
||||||
extractComparisons(expr.left);
|
extractComparisons(expr.left);
|
||||||
extractComparisons(expr.right);
|
extractComparisons(expr.right);
|
||||||
@ -304,25 +305,18 @@ const formToExpression = (values: ConditionNodeConfigFormFieldValues): Expr => {
|
|||||||
const type = typeStr as WorkflowNodeIoValueType;
|
const type = typeStr as WorkflowNodeIoValueType;
|
||||||
|
|
||||||
const left: Expr = {
|
const left: Expr = {
|
||||||
type: "var",
|
type: ExprType.Var,
|
||||||
selector: { id, name, type },
|
selector: { id, name, type },
|
||||||
};
|
};
|
||||||
|
|
||||||
let rightValue: any = condition.rightValue;
|
|
||||||
if (type === "number") {
|
|
||||||
rightValue = Number(condition.rightValue);
|
|
||||||
} else if (type === "boolean") {
|
|
||||||
rightValue = condition.rightValue === "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
const right: Expr = {
|
const right: Expr = {
|
||||||
type: "const",
|
type: ExprType.Const,
|
||||||
value: rightValue,
|
value: condition.rightValue,
|
||||||
valueType: type,
|
valueType: type,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "compare",
|
type: ExprType.Compare,
|
||||||
op: condition.operator,
|
op: condition.operator,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
@ -339,7 +333,7 @@ const formToExpression = (values: ConditionNodeConfigFormFieldValues): Expr => {
|
|||||||
|
|
||||||
for (let i = 1; i < values.conditions.length; i++) {
|
for (let i = 1; i < values.conditions.length; i++) {
|
||||||
expr = {
|
expr = {
|
||||||
type: "logical",
|
type: ExprType.Logical,
|
||||||
op: values.logicalOperator,
|
op: values.logicalOperator,
|
||||||
left: expr,
|
left: expr,
|
||||||
right: createComparisonExpr(values.conditions[i]),
|
right: createComparisonExpr(values.conditions[i]),
|
||||||
|
@ -39,7 +39,7 @@ const InspectNode = ({ node, disabled }: InspectNodeProps) => {
|
|||||||
const config = (node.config as WorkflowNodeConfigForInspect) ?? {};
|
const config = (node.config as WorkflowNodeConfigForInspect) ?? {};
|
||||||
return (
|
return (
|
||||||
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
||||||
<Typography.Text className="truncate">{config.domain ?? ""}</Typography.Text>
|
<Typography.Text className="truncate">{config.host ?? ""}</Typography.Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}, [node]);
|
}, [node]);
|
||||||
|
@ -7,7 +7,7 @@ import { z } from "zod";
|
|||||||
import { type WorkflowNodeConfigForInspect } from "@/domain/workflow";
|
import { type WorkflowNodeConfigForInspect } from "@/domain/workflow";
|
||||||
import { useAntdForm } from "@/hooks";
|
import { useAntdForm } from "@/hooks";
|
||||||
|
|
||||||
import { validDomainName, validPortNumber } from "@/utils/validators";
|
import { validDomainName, validIPv4Address, validPortNumber } from "@/utils/validators";
|
||||||
|
|
||||||
type InspectNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForInspect>;
|
type InspectNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForInspect>;
|
||||||
|
|
||||||
@ -29,6 +29,8 @@ const initFormModel = (): InspectNodeConfigFormFieldValues => {
|
|||||||
return {
|
return {
|
||||||
domain: "",
|
domain: "",
|
||||||
port: "443",
|
port: "443",
|
||||||
|
path: "",
|
||||||
|
host: "",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,12 +39,14 @@ const InspectNodeConfigForm = forwardRef<InspectNodeConfigFormInstance, InspectN
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
domain: z.string().refine((val) => validDomainName(val), {
|
host: z.string().refine((val) => validIPv4Address(val) || validDomainName(val), {
|
||||||
message: t("workflow_node.inspect.form.domain.placeholder"),
|
message: t("workflow_node.inspect.form.host.placeholder"),
|
||||||
}),
|
}),
|
||||||
|
domain: z.string().optional(),
|
||||||
port: z.string().refine((val) => validPortNumber(val), {
|
port: z.string().refine((val) => validPortNumber(val), {
|
||||||
message: t("workflow_node.inspect.form.port.placeholder"),
|
message: t("workflow_node.inspect.form.port.placeholder"),
|
||||||
}),
|
}),
|
||||||
|
path: z.string().optional(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
const { form: formInst, formProps } = useAntdForm({
|
const { form: formInst, formProps } = useAntdForm({
|
||||||
@ -70,13 +74,21 @@ const InspectNodeConfigForm = forwardRef<InspectNodeConfigFormInstance, InspectN
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
<Form.Item name="domain" label={t("workflow_node.inspect.form.domain.label")} rules={[formRule]}>
|
<Form.Item name="host" label={t("workflow_node.inspect.form.host.label")} rules={[formRule]}>
|
||||||
<Input variant="filled" placeholder={t("workflow_node.inspect.form.domain.placeholder")} />
|
<Input variant="filled" placeholder={t("workflow_node.inspect.form.host.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="port" label={t("workflow_node.inspect.form.port.label")} rules={[formRule]}>
|
<Form.Item name="port" label={t("workflow_node.inspect.form.port.label")} rules={[formRule]}>
|
||||||
<Input variant="filled" placeholder={t("workflow_node.inspect.form.port.placeholder")} />
|
<Input variant="filled" placeholder={t("workflow_node.inspect.form.port.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="domain" label={t("workflow_node.inspect.form.domain.label")} rules={[formRule]}>
|
||||||
|
<Input variant="filled" placeholder={t("workflow_node.inspect.form.domain.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="path" label={t("workflow_node.inspect.form.path.label")} rules={[formRule]}>
|
||||||
|
<Input variant="filled" placeholder={t("workflow_node.inspect.form.path.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ const workflowNodeTypeDefaultInputs: Map<WorkflowNodeType, WorkflowNodeIO[]> = n
|
|||||||
name: "certificate",
|
name: "certificate",
|
||||||
type: "certificate",
|
type: "certificate",
|
||||||
required: true,
|
required: true,
|
||||||
label: "证书",
|
label: i18n.t("workflow.variables.certificate.label"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -82,7 +82,7 @@ const workflowNodeTypeDefaultOutputs: Map<WorkflowNodeType, WorkflowNodeIO[]> =
|
|||||||
name: "certificate",
|
name: "certificate",
|
||||||
type: "certificate",
|
type: "certificate",
|
||||||
required: true,
|
required: true,
|
||||||
label: "证书",
|
label: i18n.t("workflow.variables.certificate.label"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -93,7 +93,7 @@ const workflowNodeTypeDefaultOutputs: Map<WorkflowNodeType, WorkflowNodeIO[]> =
|
|||||||
name: "certificate",
|
name: "certificate",
|
||||||
type: "certificate",
|
type: "certificate",
|
||||||
required: true,
|
required: true,
|
||||||
label: "证书",
|
label: i18n.t("workflow.variables.certificate.label"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -104,7 +104,7 @@ const workflowNodeTypeDefaultOutputs: Map<WorkflowNodeType, WorkflowNodeIO[]> =
|
|||||||
name: "certificate",
|
name: "certificate",
|
||||||
type: "certificate",
|
type: "certificate",
|
||||||
required: true,
|
required: true,
|
||||||
label: "证书",
|
label: i18n.t("workflow.variables.certificate.label"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -161,6 +161,8 @@ export type WorkflowNodeConfigForUpload = {
|
|||||||
export type WorkflowNodeConfigForInspect = {
|
export type WorkflowNodeConfigForInspect = {
|
||||||
domain: string;
|
domain: string;
|
||||||
port: string;
|
port: string;
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowNodeConfigForDeploy = {
|
export type WorkflowNodeConfigForDeploy = {
|
||||||
@ -200,14 +202,20 @@ export type WorkflowNodeIO = {
|
|||||||
valueSelector?: WorkflowNodeIOValueSelector;
|
valueSelector?: WorkflowNodeIOValueSelector;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const VALUE_TYPES = Object.freeze({
|
||||||
|
STRING: "string",
|
||||||
|
NUMBER: "number",
|
||||||
|
BOOLEAN: "boolean",
|
||||||
|
} as const);
|
||||||
|
|
||||||
|
export type WorkflowNodeIoValueType = (typeof VALUE_TYPES)[keyof typeof VALUE_TYPES];
|
||||||
|
|
||||||
export type WorkflowNodeIOValueSelector = {
|
export type WorkflowNodeIOValueSelector = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: WorkflowNodeIoValueType;
|
type: WorkflowNodeIoValueType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowNodeIoValueType = "string" | "number" | "boolean";
|
|
||||||
|
|
||||||
type WorkflowNodeIOOptions = {
|
type WorkflowNodeIOOptions = {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
@ -224,12 +232,12 @@ export const workflowNodeIOOptions = (node: WorkflowNode) => {
|
|||||||
switch (output.type) {
|
switch (output.type) {
|
||||||
case "certificate":
|
case "certificate":
|
||||||
rs.options.push({
|
rs.options.push({
|
||||||
label: `${node.name} - ${output.label} - 是否有效`,
|
label: `${node.name} - ${output.label} - ${i18n.t("workflow.variables.is_validated.label")}`,
|
||||||
value: `${node.id}#${output.name}.validated#boolean`,
|
value: `${node.id}#${output.name}.validated#boolean`,
|
||||||
});
|
});
|
||||||
|
|
||||||
rs.options.push({
|
rs.options.push({
|
||||||
label: `${node.name} - ${output.label} - 剩余天数`,
|
label: `${node.name} - ${output.label} - ${i18n.t("workflow.variables.days_left.label")}`,
|
||||||
value: `${node.id}#${output.name}.daysLeft#number`,
|
value: `${node.id}#${output.name}.daysLeft#number`,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -254,22 +262,34 @@ export type Value = string | number | boolean;
|
|||||||
|
|
||||||
export type ComparisonOperator = ">" | "<" | ">=" | "<=" | "==" | "!=" | "is";
|
export type ComparisonOperator = ">" | "<" | ">=" | "<=" | "==" | "!=" | "is";
|
||||||
|
|
||||||
export type LogicalOperator = "and" | "or" | "not";
|
export enum LogicalOperator {
|
||||||
|
And = "and",
|
||||||
|
Or = "or",
|
||||||
|
Not = "not",
|
||||||
|
}
|
||||||
|
|
||||||
export type ConstExpr = { type: "const"; value: Value; valueType: WorkflowNodeIoValueType };
|
export enum ExprType {
|
||||||
export type VarExpr = { type: "var"; selector: WorkflowNodeIOValueSelector };
|
Const = "const",
|
||||||
export type CompareExpr = { type: "compare"; op: ComparisonOperator; left: Expr; right: Expr };
|
Var = "var",
|
||||||
export type LogicalExpr = { type: "logical"; op: LogicalOperator; left: Expr; right: Expr };
|
Compare = "compare",
|
||||||
export type NotExpr = { type: "not"; expr: Expr };
|
Logical = "logical",
|
||||||
|
Not = "not",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConstExpr = { type: ExprType.Const; value: string; valueType: WorkflowNodeIoValueType };
|
||||||
|
export type VarExpr = { type: ExprType.Var; selector: WorkflowNodeIOValueSelector };
|
||||||
|
export type CompareExpr = { type: ExprType.Compare; op: ComparisonOperator; left: Expr; right: Expr };
|
||||||
|
export type LogicalExpr = { type: ExprType.Logical; op: LogicalOperator; left: Expr; right: Expr };
|
||||||
|
export type NotExpr = { type: ExprType.Not; expr: Expr };
|
||||||
|
|
||||||
export type Expr = ConstExpr | VarExpr | CompareExpr | LogicalExpr | NotExpr;
|
export type Expr = ConstExpr | VarExpr | CompareExpr | LogicalExpr | NotExpr;
|
||||||
|
|
||||||
export const isConstExpr = (expr: Expr): expr is ConstExpr => {
|
export const isConstExpr = (expr: Expr): expr is ConstExpr => {
|
||||||
return expr.type === "const";
|
return expr.type === ExprType.Const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isVarExpr = (expr: Expr): expr is VarExpr => {
|
export const isVarExpr = (expr: Expr): expr is VarExpr => {
|
||||||
return expr.type === "var";
|
return expr.type === ExprType.Var;
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -53,5 +53,9 @@
|
|||||||
"workflow.detail.orchestration.action.run": "Run",
|
"workflow.detail.orchestration.action.run": "Run",
|
||||||
"workflow.detail.orchestration.action.run.confirm": "You have unreleased changes. Do you really want to run this workflow based on the latest released version?",
|
"workflow.detail.orchestration.action.run.confirm": "You have unreleased changes. Do you really want to run this workflow based on the latest released version?",
|
||||||
"workflow.detail.orchestration.action.run.prompt": "Running... Please check the history later",
|
"workflow.detail.orchestration.action.run.prompt": "Running... Please check the history later",
|
||||||
"workflow.detail.runs.tab": "History runs"
|
"workflow.detail.runs.tab": "History runs",
|
||||||
|
|
||||||
|
"workflow.variables.is_validated.label": "Is valid",
|
||||||
|
"workflow.variables.days_left.label": "Days left",
|
||||||
|
"workflow.variables.certificate.label": "Certificate"
|
||||||
}
|
}
|
||||||
|
@ -806,6 +806,10 @@
|
|||||||
"workflow_node.inspect.form.domain.placeholder": "Please enter domain name",
|
"workflow_node.inspect.form.domain.placeholder": "Please enter domain name",
|
||||||
"workflow_node.inspect.form.port.label": "Port",
|
"workflow_node.inspect.form.port.label": "Port",
|
||||||
"workflow_node.inspect.form.port.placeholder": "Please enter port",
|
"workflow_node.inspect.form.port.placeholder": "Please enter port",
|
||||||
|
"workflow_node.inspect.form.host.label": "Host",
|
||||||
|
"workflow_node.inspect.form.host.placeholder": "Please enter host",
|
||||||
|
"workflow_node.inspect.form.path.label": "Path",
|
||||||
|
"workflow_node.inspect.form.path.placeholder": "Please enter path",
|
||||||
|
|
||||||
"workflow_node.notify.label": "Notification",
|
"workflow_node.notify.label": "Notification",
|
||||||
"workflow_node.notify.form.subject.label": "Subject",
|
"workflow_node.notify.form.subject.label": "Subject",
|
||||||
|
@ -53,5 +53,9 @@
|
|||||||
"workflow.detail.orchestration.action.run": "执行",
|
"workflow.detail.orchestration.action.run": "执行",
|
||||||
"workflow.detail.orchestration.action.run.confirm": "你有尚未发布的更改。确定要以最近一次发布的版本继续执行吗?",
|
"workflow.detail.orchestration.action.run.confirm": "你有尚未发布的更改。确定要以最近一次发布的版本继续执行吗?",
|
||||||
"workflow.detail.orchestration.action.run.prompt": "执行中……请稍后查看执行历史",
|
"workflow.detail.orchestration.action.run.prompt": "执行中……请稍后查看执行历史",
|
||||||
"workflow.detail.runs.tab": "执行历史"
|
"workflow.detail.runs.tab": "执行历史",
|
||||||
|
|
||||||
|
"workflow.variables.is_validated.label": "是否有效",
|
||||||
|
"workflow.variables.days_left.label": "剩余天数",
|
||||||
|
"workflow.variables.certificate.label": "证书"
|
||||||
}
|
}
|
||||||
|
@ -805,6 +805,10 @@
|
|||||||
"workflow_node.inspect.form.domain.placeholder": "请输入要检查的网站域名",
|
"workflow_node.inspect.form.domain.placeholder": "请输入要检查的网站域名",
|
||||||
"workflow_node.inspect.form.port.label": "端口号",
|
"workflow_node.inspect.form.port.label": "端口号",
|
||||||
"workflow_node.inspect.form.port.placeholder": "请输入要检查的端口号",
|
"workflow_node.inspect.form.port.placeholder": "请输入要检查的端口号",
|
||||||
|
"workflow_node.inspect.form.host.label": "Host",
|
||||||
|
"workflow_node.inspect.form.host.placeholder": "请输入 Host",
|
||||||
|
"workflow_node.inspect.form.path.label": "Path",
|
||||||
|
"workflow_node.inspect.form.path.placeholder": "请输入 Path",
|
||||||
|
|
||||||
"workflow_node.notify.label": "推送通知",
|
"workflow_node.notify.label": "推送通知",
|
||||||
"workflow_node.notify.form.subject.label": "通知主题",
|
"workflow_node.notify.form.subject.label": "通知主题",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user