diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index a1f84373..f74d97e9 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -1,6 +1,7 @@ package applicant import ( + "context" "crypto" "crypto/ecdsa" "crypto/elliptic" @@ -170,7 +171,38 @@ func Get(record *models.Record) (Applicant, error) { DisableFollowCNAME: applyConfig.DisableFollowCNAME, } - switch access.GetString("configType") { + return GetWithTypeOption(access.GetString("configType"), option) +} + +func GetWithApplyNode(node *domain.WorkflowNode) (Applicant, error) { + // 获取授权配置 + accessRepo := repository.NewAccessRepository() + + access, err := accessRepo.GetById(context.Background(), node.GetConfigString("access")) + if err != nil { + return nil, fmt.Errorf("access record not found: %w", err) + } + + timeout := node.GetConfigInt64("timeout") + if timeout == 0 { + timeout = defaultTimeout + } + + applyConfig := &ApplyOption{ + Email: node.GetConfigString("email"), + Domain: node.GetConfigString("domain"), + Access: access.Config, + KeyAlgorithm: node.GetConfigString("keyAlgorithm"), + Nameservers: node.GetConfigString("nameservers"), + Timeout: timeout, + DisableFollowCNAME: node.GetConfigBool("disableFollowCNAME"), + } + + return GetWithTypeOption(access.ConfigType, applyConfig) +} + +func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) { + switch t { case configTypeAliyun: return NewAliyun(option), nil case configTypeTencent: diff --git a/internal/domain/access.go b/internal/domain/access.go index bf2515d3..18b87a00 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -1,5 +1,16 @@ package domain +import "time" + +type Access struct { + Meta + Name string `json:"name"` + Config string `json:"config"` + ConfigType string `json:"configType"` + Deleted time.Time `json:"deleted"` + Usage string `json:"usage"` +} + type AliyunAccess struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go new file mode 100644 index 00000000..ae35500e --- /dev/null +++ b/internal/domain/certificate.go @@ -0,0 +1,39 @@ +package domain + +import "time" + +type Certificate struct { + Meta + SAN string `json:"san"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privateKey"` + IssuerCertificate string `json:"issuerCertificate"` + CertUrl string `json:"certUrl"` + CertStableUrl string `json:"certStableUrl"` + Output string `json:"output"` + ExpireAt time.Time `json:"ExpireAt"` +} + +type MetaData struct { + Version string `json:"version"` + SerialNumber string `json:"serialNumber"` + Validity CertificateValidity `json:"validity"` + SignatureAlgorithm string `json:"signatureAlgorithm"` + Issuer CertificateIssuer `json:"issuer"` + Subject CertificateSubject `json:"subject"` +} + +type CertificateIssuer struct { + Country string `json:"country"` + Organization string `json:"organization"` + CommonName string `json:"commonName"` +} + +type CertificateSubject struct { + CN string `json:"CN"` +} + +type CertificateValidity struct { + NotBefore string `json:"notBefore"` + NotAfter string `json:"notAfter"` +} diff --git a/internal/domain/common.go b/internal/domain/common.go new file mode 100644 index 00000000..1b53b122 --- /dev/null +++ b/internal/domain/common.go @@ -0,0 +1,9 @@ +package domain + +import "time" + +type Meta struct { + Id string `json:"id"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} diff --git a/internal/domain/err.go b/internal/domain/err.go index c45971f0..15d43186 100644 --- a/internal/domain/err.go +++ b/internal/domain/err.go @@ -1,6 +1,16 @@ package domain -var ErrAuthFailed = NewXError(4999, "auth failed") +var ( + ErrInvalidParams = NewXError(400, "invalid params") + ErrRecordNotFound = NewXError(404, "record not found") +) + +func IsRecordNotFound(err error) bool { + if e, ok := err.(*XError); ok { + return e.GetCode() == ErrRecordNotFound.GetCode() + } + return false +} type XError struct { Code int `json:"code"` diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 3266eee2..b1c0123f 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -1,9 +1,22 @@ package domain -import "time" +import ( + "fmt" + "strconv" +) + +const ( + WorkflowNodeTypeStart = "start" + WorkflowNodeTypeEnd = "end" + WorkflowNodeTypeApply = "apply" + WorkflowNodeTypeDeply = "deploy" + WorkflowNodeTypeNotify = "notify" + WorkflowNodeTypeBranch = "branch" + WorkflowNodeTypeCondition = "condition" +) type Workflow struct { - Id string `json:"id"` + Meta Name string `json:"name"` Description string `json:"description"` Type string `json:"type"` @@ -11,8 +24,6 @@ type Workflow struct { Draft *WorkflowNode `json:"draft"` Enabled bool `json:"enabled"` HasDraft bool `json:"hasDraft"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` } type WorkflowNode struct { @@ -29,11 +40,48 @@ type WorkflowNode struct { Branches []WorkflowNode `json:"branches"` } +func (n *WorkflowNode) GetConfigString(key string) string { + if v, ok := n.Config[key]; ok { + if s, ok := v.(string); ok { + return s + } + } + return "" +} + +func (n *WorkflowNode) GetConfigBool(key string) bool { + if v, ok := n.Config[key]; ok { + if b, ok := v.(bool); ok { + return b + } + } + return false +} + +func (n *WorkflowNode) GetConfigInt64(key string) int64 { + // 先转成字符串,再转成 int64 + if v, ok := n.Config[key]; ok { + temp := fmt.Sprintf("%v", v) + if i, err := strconv.ParseInt(temp, 10, 64); err == nil { + return i + } + } + + return 0 +} + type WorkflowNodeIo struct { - Label string `json:"label"` - Name string `json:"name"` - Type string `json:"type"` - Required bool `json:"required"` + Label string `json:"label"` + Name string `json:"name"` + Type string `json:"type"` + Required bool `json:"required"` + Value any `json:"value"` + ValueSelector WorkflowNodeIoValueSelector `json:"valueSelector"` +} + +type WorkflowNodeIoValueSelector struct { + Id string `json:"id"` + Name string `json:"name"` } type WorkflowRunReq struct { diff --git a/internal/domain/workflow_output.go b/internal/domain/workflow_output.go new file mode 100644 index 00000000..5ae09205 --- /dev/null +++ b/internal/domain/workflow_output.go @@ -0,0 +1,12 @@ +package domain + +const WorkflowOutputCertificate = "certificate" + +type WorkflowOutput struct { + Meta + Workflow string `json:"workflow"` + NodeId string `json:"nodeId"` + Node *WorkflowNode `json:"node"` + Output []WorkflowNodeIo `json:"output"` + Succeed bool `json:"succeed"` +} diff --git a/internal/repository/access.go b/internal/repository/access.go new file mode 100644 index 00000000..2fcca47f --- /dev/null +++ b/internal/repository/access.go @@ -0,0 +1,17 @@ +package repository + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" +) + +type AccessRepository struct{} + +func NewAccessRepository() *AccessRepository { + return &AccessRepository{} +} + +func (a *AccessRepository) GetById(ctx context.Context, id string) (*domain.Access, error) { + return nil, nil +} diff --git a/internal/repository/workflow.go b/internal/repository/workflow.go new file mode 100644 index 00000000..b39d8ac0 --- /dev/null +++ b/internal/repository/workflow.go @@ -0,0 +1,54 @@ +package repository + +import ( + "context" + "database/sql" + "errors" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/app" +) + +type WorkflowRepository struct{} + +func NewWorkflowRepository() *WorkflowRepository { + return &WorkflowRepository{} +} + +func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workflow, error) { + record, err := app.GetApp().Dao().FindRecordById("workflow", id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, domain.ErrRecordNotFound + } + return nil, err + } + + content := &domain.WorkflowNode{} + if err := record.UnmarshalJSONField("content", content); err != nil { + return nil, err + } + + draft := &domain.WorkflowNode{} + if err := record.UnmarshalJSONField("draft", draft); err != nil { + return nil, err + } + + workflow := &domain.Workflow{ + Meta: domain.Meta{ + Id: record.GetId(), + Created: record.GetTime("created"), + Updated: record.GetTime("updated"), + }, + Name: record.GetString("name"), + Description: record.GetString("description"), + Type: record.GetString("type"), + Enabled: record.GetBool("enabled"), + HasDraft: record.GetBool("hasDraft"), + + Content: content, + Draft: draft, + } + + return workflow, nil +} diff --git a/internal/repository/workflow_output.go b/internal/repository/workflow_output.go new file mode 100644 index 00000000..c2be5d4a --- /dev/null +++ b/internal/repository/workflow_output.go @@ -0,0 +1,105 @@ +package repository + +import ( + "context" + "database/sql" + "errors" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/models" + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/app" +) + +type WorkflowOutputRepository struct{} + +func NewWorkflowOutputRepository() *WorkflowOutputRepository { + return &WorkflowOutputRepository{} +} + +func (w *WorkflowOutputRepository) Get(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error) { + record, err := app.GetApp().Dao().FindFirstRecordByFilter("workflow_output", "nodeId={:nodeId}", dbx.Params{"nodeId": nodeId}) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, domain.ErrRecordNotFound + } + return nil, err + } + + node := &domain.WorkflowNode{} + if err := record.UnmarshalJSONField("node", node); err != nil { + return nil, errors.New("failed to unmarshal node") + } + + output := make([]domain.WorkflowNodeIo, 0) + if err := record.UnmarshalJSONField("output", &output); err != nil { + return nil, errors.New("failed to unmarshal output") + } + + rs := &domain.WorkflowOutput{ + Meta: domain.Meta{ + Id: record.GetId(), + Created: record.GetTime("created"), + Updated: record.GetTime("updated"), + }, + Workflow: record.GetString("workflow"), + NodeId: record.GetString("nodeId"), + Node: node, + Output: output, + } + + return rs, nil +} + +// 保存节点输出 +func (w *WorkflowOutputRepository) Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error { + var record *models.Record + var err error + + if output.Id == "" { + collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_output") + if err != nil { + return err + } + record = models.NewRecord(collection) + } else { + record, err = app.GetApp().Dao().FindRecordById("workflow_output", output.Id) + if err != nil { + return err + } + } + record.Set("workflow", output.Workflow) + record.Set("nodeId", output.NodeId) + record.Set("node", output.Node) + record.Set("output", output.Output) + + if err := app.GetApp().Dao().SaveRecord(record); err != nil { + return err + } + + if cb != nil && certificate != nil { + if err := cb(record.GetId()); err != nil { + return err + } + + certCollection, err := app.GetApp().Dao().FindCollectionByNameOrId("certificate") + if err != nil { + return err + } + + certRecord := models.NewRecord(certCollection) + certRecord.Set("certificate", certificate.Certificate) + certRecord.Set("privateKey", certificate.PrivateKey) + certRecord.Set("issuerCertificate", certificate.IssuerCertificate) + certRecord.Set("san", certificate.SAN) + certRecord.Set("workflowOutput", certificate.Output) + certRecord.Set("expireAt", certificate.ExpireAt) + certRecord.Set("certUrl", certificate.CertUrl) + certRecord.Set("certStableUrl", certificate.CertStableUrl) + + if err := app.GetApp().Dao().SaveRecord(certRecord); err != nil { + return err + } + } + return nil +} diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go new file mode 100644 index 00000000..de537b8e --- /dev/null +++ b/internal/workflow/node-processor/apply_node.go @@ -0,0 +1,103 @@ +package nodeprocessor + +import ( + "context" + + "github.com/usual2970/certimate/internal/applicant" + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/utils/x509" + "github.com/usual2970/certimate/internal/repository" + "github.com/usual2970/certimate/internal/utils/xtime" +) + +type applyNode struct { + node *domain.WorkflowNode + outputRepo WorkflowOutputRepository + *Logger +} + +func NewApplyNode(node *domain.WorkflowNode) *applyNode { + return &applyNode{ + node: node, + Logger: NewLogger(node), + outputRepo: repository.NewWorkflowOutputRepository(), + } +} + +type WorkflowOutputRepository interface { + // 查询节点输出 + Get(ctx context.Context, nodeId string) (*domain.WorkflowOutput, error) + + // 保存节点输出 + Save(ctx context.Context, output *domain.WorkflowOutput, certificate *domain.Certificate, cb func(id string) error) error +} + +// 申请节点根据申请类型执行不同的操作 +func (a *applyNode) Run(ctx context.Context) error { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "开始执行") + // 查询是否申请过,已申请过则直接返回(先保持和 v0.2 一致) + output, err := a.outputRepo.Get(ctx, a.node.Id) + if err != nil && !domain.IsRecordNotFound(err) { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "查询申请记录失败", err.Error()) + return err + } + + if output != nil && output.Succeed { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "已申请过") + return nil + } + + // 获取Applicant + apply, err := applicant.GetWithApplyNode(a.node) + if err != nil { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "获取申请对象失败", err.Error()) + return err + } + + // 申请 + certificate, err := apply.Apply() + if err != nil { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "申请失败", err.Error()) + return err + } + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "申请成功") + + // 记录申请结果 + output = &domain.WorkflowOutput{ + Workflow: GetWorkflowId(ctx), + NodeId: a.node.Id, + Node: a.node, + Succeed: true, + } + + cert, err := x509.ParseCertificateFromPEM(certificate.Certificate) + if err != nil { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "解析证书失败", err.Error()) + return err + } + + certificateRecord := &domain.Certificate{ + SAN: cert.Subject.CommonName, + Certificate: certificate.Certificate, + PrivateKey: certificate.PrivateKey, + IssuerCertificate: certificate.IssuerCertificate, + CertUrl: certificate.CertUrl, + CertStableUrl: certificate.CertStableUrl, + ExpireAt: cert.NotAfter, + } + + if err := a.outputRepo.Save(ctx, output, certificateRecord, func(id string) error { + if certificateRecord != nil { + certificateRecord.Id = id + } + + return nil + }); err != nil { + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "保存申请记录失败", err.Error()) + return err + } + + a.AddOutput(ctx, xtime.BeijingTimeStr(), a.node.Name, "保存申请记录成功") + + return nil +} diff --git a/internal/workflow/node-processor/condition_node.go b/internal/workflow/node-processor/condition_node.go new file mode 100644 index 00000000..2bebe9ac --- /dev/null +++ b/internal/workflow/node-processor/condition_node.go @@ -0,0 +1,29 @@ +package nodeprocessor + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/xtime" +) + +type conditionNode struct { + node *domain.WorkflowNode + *Logger +} + +func NewConditionNode(node *domain.WorkflowNode) *conditionNode { + return &conditionNode{ + node: node, + Logger: NewLogger(node), + } +} + +// 条件节点没有任何操作 +func (c *conditionNode) Run(ctx context.Context) error { + c.AddOutput(ctx, xtime.BeijingTimeStr(), + c.node.Name, + "完成", + ) + return nil +} diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go new file mode 100644 index 00000000..e6c794e8 --- /dev/null +++ b/internal/workflow/node-processor/deploy_node.go @@ -0,0 +1 @@ +package nodeprocessor \ No newline at end of file diff --git a/internal/workflow/node-processor/notify_node.go b/internal/workflow/node-processor/notify_node.go new file mode 100644 index 00000000..e6c794e8 --- /dev/null +++ b/internal/workflow/node-processor/notify_node.go @@ -0,0 +1 @@ +package nodeprocessor \ No newline at end of file diff --git a/internal/workflow/node-processor/processor.go b/internal/workflow/node-processor/processor.go new file mode 100644 index 00000000..1358d352 --- /dev/null +++ b/internal/workflow/node-processor/processor.go @@ -0,0 +1,69 @@ +package nodeprocessor + +import ( + "context" + "errors" + + "github.com/usual2970/certimate/internal/domain" +) + +type RunLog struct { + NodeName string `json:"node_name"` + Err string `json:"err"` + Outputs []RunLogOutput `json:"outputs"` +} + +type RunLogOutput struct { + Time string `json:"time"` + Title string `json:"title"` + Content string `json:"content"` + Error string `json:"error"` +} + +type NodeProcessor interface { + Run(ctx context.Context) error + Log(ctx context.Context) *RunLog + AddOutput(ctx context.Context, time, title, content string, err ...string) +} + +type Logger struct { + log *RunLog +} + +func NewLogger(node *domain.WorkflowNode) *Logger { + return &Logger{ + log: &RunLog{ + NodeName: node.Name, + Outputs: make([]RunLogOutput, 0), + }, + } +} + +func (l *Logger) Log(ctx context.Context) *RunLog { + return l.log +} + +func (l *Logger) AddOutput(ctx context.Context, time, title, content string, err ...string) { + output := RunLogOutput{ + Time: time, + Title: title, + Content: content, + } + if len(err) > 0 { + output.Error = err[0] + l.log.Err = err[0] + } + l.log.Outputs = append(l.log.Outputs, output) +} + +func GetProcessor(node *domain.WorkflowNode) (NodeProcessor, error) { + switch node.Type { + case domain.WorkflowNodeTypeStart: + return NewStartNode(node), nil + case domain.WorkflowNodeTypeCondition: + return NewConditionNode(node), nil + case domain.WorkflowNodeTypeApply: + return NewApplyNode(node), nil + } + return nil, errors.New("not implemented") +} diff --git a/internal/workflow/node-processor/start_node.go b/internal/workflow/node-processor/start_node.go new file mode 100644 index 00000000..e880a08d --- /dev/null +++ b/internal/workflow/node-processor/start_node.go @@ -0,0 +1,29 @@ +package nodeprocessor + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/xtime" +) + +type startNode struct { + node *domain.WorkflowNode + *Logger +} + +func NewStartNode(node *domain.WorkflowNode) *startNode { + return &startNode{ + node: node, + Logger: NewLogger(node), + } +} + +// 开始节点没有任何操作 +func (s *startNode) Run(ctx context.Context) error { + s.AddOutput(ctx, xtime.BeijingTimeStr(), + s.node.Name, + "完成", + ) + return nil +} diff --git a/internal/workflow/node-processor/workflow_processor.go b/internal/workflow/node-processor/workflow_processor.go new file mode 100644 index 00000000..6360458d --- /dev/null +++ b/internal/workflow/node-processor/workflow_processor.go @@ -0,0 +1,64 @@ +package nodeprocessor + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" +) + +type workflowProcessor struct { + workflow *domain.Workflow + logs []RunLog +} + +func NewWorkflowProcessor(workflow *domain.Workflow) *workflowProcessor { + return &workflowProcessor{ + workflow: workflow, + } +} + +func (w *workflowProcessor) Run(ctx context.Context) error { + ctx = WithWorkflowId(ctx, w.workflow.Id) + return w.runNode(ctx, w.workflow.Content) +} + +func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNode) error { + current := node + for current != nil { + if current.Type == domain.WorkflowNodeTypeBranch { + for _, branch := range current.Branches { + if err := w.runNode(ctx, &branch); err != nil { + continue + } + } + } + + if current.Type != domain.WorkflowNodeTypeBranch { + processor, err := GetProcessor(current) + if err != nil { + return err + } + + err = processor.Run(ctx) + + log := processor.Log(ctx) + if log != nil { + w.logs = append(w.logs, *log) + } + + if err != nil { + return err + } + } + + } + return nil +} + +func WithWorkflowId(ctx context.Context, id string) context.Context { + return context.WithValue(ctx, "workflow_id", id) +} + +func GetWorkflowId(ctx context.Context) string { + return ctx.Value("workflow_id").(string) +} diff --git a/internal/workflow/service.go b/internal/workflow/service.go new file mode 100644 index 00000000..d14ffe07 --- /dev/null +++ b/internal/workflow/service.go @@ -0,0 +1,52 @@ +package workflow + +import ( + "context" + "fmt" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/app" + nodeprocessor "github.com/usual2970/certimate/internal/workflow/node-processor" +) + +type WorkflowRepository interface { + Get(ctx context.Context, id string) (*domain.Workflow, error) +} + +type WorkflowService struct { + repo WorkflowRepository +} + +func NewWorkflowService(repo WorkflowRepository) *WorkflowService { + return &WorkflowService{ + repo: repo, + } +} + +func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) error { + // 查询 + if req.Id == "" { + return domain.ErrInvalidParams + } + + workflow, err := s.repo.Get(ctx, req.Id) + if err != nil { + app.GetApp().Logger().Error("failed to get workflow", "id", req.Id, "err", err) + return err + } + + // 执行 + if !workflow.Enabled { + app.GetApp().Logger().Error("workflow is disabled", "id", req.Id) + return fmt.Errorf("workflow is disabled") + } + + processor := nodeprocessor.NewWorkflowProcessor(workflow) + if err := processor.Run(ctx); err != nil { + return fmt.Errorf("failed to run workflow: %w", err) + } + + // 保存执行日志 + + return nil +} diff --git a/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx index 228d87ef..88ec446b 100644 --- a/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx +++ b/ui/src/components/workflow/WorkflowBaseInfoEditDialog.tsx @@ -8,7 +8,8 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from " import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { useTranslation } from "react-i18next"; -import { memo, useState } from "react"; +import { memo, useEffect, useState } from "react"; +import { Textarea } from "../ui/textarea"; type WorkflowNameEditDialogProps = { trigger: React.ReactNode; @@ -30,6 +31,10 @@ const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => resolver: zodResolver(formSchema), }); + useEffect(() => { + form.reset({ name: workflow.name, description: workflow.description }); + }, [workflow]); + const { t } = useTranslation(); const [open, setOpen] = useState(false); @@ -71,6 +76,7 @@ const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => { form.setValue("name", e.target.value); @@ -90,9 +96,10 @@ const WorkflowNameBaseInfoDialog = ({ trigger }: WorkflowNameEditDialogProps) => 说明 - { form.setValue("description", e.target.value); diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 72967ba6..308928e3 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -155,6 +155,10 @@ export const newWorkflowNode = (type: WorkflowNodeType, options: NewWorkflowNode }; } + if (type == WorkflowNodeType.Condition) { + rs.validated = true; + } + if (type === WorkflowNodeType.Branch) { rs = { ...rs, @@ -350,6 +354,20 @@ export const allNodesValidated = (node: WorkflowNode | WorkflowBranchNode): bool return true; }; +export const getExecuteMethod = (node: WorkflowNode): { type: string; crontab: string } => { + if (node.type === WorkflowNodeType.Start) { + return { + type: (node.config?.executionMethod as string) ?? "", + crontab: (node.config?.crontab as string) ?? "", + }; + } else { + return { + type: "", + crontab: "", + }; + } +}; + export type WorkflowBranchNode = { id: string; name: string; @@ -428,4 +446,3 @@ export const workflowNodeDropdownList: WorkflowwNodeDropdwonItem[] = [ }, }, ]; - diff --git a/ui/src/pages/workflow/WorkflowDetail.tsx b/ui/src/pages/workflow/WorkflowDetail.tsx index cbd818d6..1fcc2f40 100644 --- a/ui/src/pages/workflow/WorkflowDetail.tsx +++ b/ui/src/pages/workflow/WorkflowDetail.tsx @@ -96,8 +96,8 @@ const WorkflowDetail = () => { -
{workflow.name ? workflow.name : "未命名工作流"}
-
{workflow.description ? workflow.description : "添加流程说明"}
+
{workflow.name ? workflow.name : "未命名工作流"}
+
{workflow.description ? workflow.description : "添加流程说明"}
} /> diff --git a/ui/src/pages/workflow/index.tsx b/ui/src/pages/workflow/index.tsx index 8cd2970c..9aadc064 100644 --- a/ui/src/pages/workflow/index.tsx +++ b/ui/src/pages/workflow/index.tsx @@ -43,7 +43,7 @@ const Workflow = () => { if (!name) { name = "未命名工作流"; } - return
{name}
; + return
{name}
; }, }, { @@ -54,7 +54,7 @@ const Workflow = () => { if (!description) { description = "-"; } - return description; + return
{description}
; }, }, { @@ -211,4 +211,3 @@ const Workflow = () => { }; export default Workflow; - diff --git a/ui/src/providers/workflow/index.ts b/ui/src/providers/workflow/index.ts index 84d6bc60..c85414ce 100644 --- a/ui/src/providers/workflow/index.ts +++ b/ui/src/providers/workflow/index.ts @@ -1,6 +1,7 @@ import { addBranch, addNode, + getExecuteMethod, getWorkflowOutputBeforeId, initWorkflow, removeBranch, @@ -76,11 +77,15 @@ export const useWorkflowStore = create((set, get) => ({ }); }, switchEnable: async () => { + const root = get().workflow.draft as WorkflowNode; + const executeMethod = getExecuteMethod(root); const resp = await save({ id: (get().workflow.id as string) ?? "", - content: get().workflow.draft as WorkflowNode, + content: root, enabled: !get().workflow.enabled, hasDraft: false, + type: executeMethod.type, + crontab: executeMethod.crontab, }); set((state: WorkflowState) => { return { @@ -90,15 +95,21 @@ export const useWorkflowStore = create((set, get) => ({ content: resp.content, enabled: resp.enabled, hasDraft: false, + type: resp.type, + crontab: resp.crontab, }, }; }); }, save: async () => { + const root = get().workflow.draft as WorkflowNode; + const executeMethod = getExecuteMethod(root); const resp = await save({ id: (get().workflow.id as string) ?? "", - content: get().workflow.draft as WorkflowNode, + content: root, hasDraft: false, + type: executeMethod.type, + crontab: executeMethod.crontab, }); set((state: WorkflowState) => { return { @@ -107,6 +118,8 @@ export const useWorkflowStore = create((set, get) => ({ id: resp.id, content: resp.content, hasDraft: false, + type: resp.type, + crontab: resp.crontab, }, }; }); @@ -205,4 +218,3 @@ export const useWorkflowStore = create((set, get) => ({ return getWorkflowOutputBeforeId(get().workflow.draft as WorkflowNode, id, type); }, })); -