From ae11d5ee3db14015540599932ffa881a2eabb827 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 4 Jan 2025 16:29:14 +0800 Subject: [PATCH] feat: rename `san` to `subjectAltNames`, `workflow` to `workflowId`, `nodeId` to `workflowNodeId`, `output` to `workflowOutputId`, `log` to `logs`, `succeed` to `succeeded` --- internal/domain/certificate.go | 43 +-- internal/domain/workflow_output.go | 10 +- internal/domain/workflow_run_log.go | 17 +- internal/repository/workflow.go | 6 +- internal/repository/workflow_output.go | 56 ++-- internal/workflow/event.go | 2 +- .../workflow/node-processor/apply_node.go | 30 +- .../workflow/node-processor/deploy_node.go | 16 +- internal/workflow/node-processor/processor.go | 1 + internal/workflow/service.go | 16 +- migrations/1735976342_updated_certificate.go | 298 ++++++++++++++++++ .../1735977005_updated_workflow_output.go | 144 +++++++++ .../1735977021_updated_workflow_run_log.go | 144 +++++++++ migrations/1735977530_updated_certificate.go | 57 ++++ .../certificate/CertificateDetail.tsx | 16 +- .../components/workflow/WorkflowElement.tsx | 4 +- .../workflow/WorkflowRunDetailDrawer.tsx | 4 +- ui/src/components/workflow/WorkflowRuns.tsx | 2 +- ui/src/domain/certificate.ts | 20 +- ui/src/domain/workflow.ts | 5 +- ui/src/domain/workflowRun.ts | 9 +- ui/src/i18n/locales/en/nls.certificate.json | 18 +- ui/src/i18n/locales/zh/nls.certificate.json | 18 +- ui/src/pages/certificates/CertificateList.tsx | 58 ++-- ui/src/repository/certificate.ts | 2 +- ui/src/repository/workflowRun.ts | 2 +- 26 files changed, 823 insertions(+), 175 deletions(-) create mode 100644 migrations/1735976342_updated_certificate.go create mode 100644 migrations/1735977005_updated_workflow_output.go create mode 100644 migrations/1735977021_updated_workflow_run_log.go create mode 100644 migrations/1735977530_updated_certificate.go diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go index a4e20d66..aa3f974b 100644 --- a/internal/domain/certificate.go +++ b/internal/domain/certificate.go @@ -2,42 +2,25 @@ package domain import "time" -var ValidityDuration = time.Hour * 24 * 10 - type Certificate struct { Meta - SubjectAltNames string `json:"san" db:"san"` + Source string `json:"source" db:"source"` + SubjectAltNames string `json:"subjectAltNames" db:"subjectAltNames"` Certificate string `json:"certificate" db:"certificate"` PrivateKey string `json:"privateKey" db:"privateKey"` IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"` - CertUrl string `json:"certUrl" db:"certUrl"` - CertStableUrl string `json:"certStableUrl" db:"certStableUrl"` - WorkflowId string `json:"workflow" db:"workflow"` - WorkflowNodeId string `json:"nodeId" db:"nodeId"` - WorkflowOutputId string `json:"output" db:"output"` + EffectAt time.Time `json:"effectAt" db:"effectAt"` ExpireAt time.Time `json:"expireAt" db:"expireAt"` + AcmeCertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"` + AcmeCertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"` + WorkflowId string `json:"workflowId" db:"workflowId"` + WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"` + WorkflowOutputId string `json:"workflowOutputId" db:"workflowOutputId"` } -type CertificateMeta 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 CertificateSourceType string -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"` -} +const ( + CERTIFICATE_SOURCE_WORKFLOW = CertificateSourceType("workflow") + CERTIFICATE_SOURCE_UPLOAD = CertificateSourceType("upload") +) diff --git a/internal/domain/workflow_output.go b/internal/domain/workflow_output.go index 7ce0a005..81e1ae19 100644 --- a/internal/domain/workflow_output.go +++ b/internal/domain/workflow_output.go @@ -4,9 +4,9 @@ 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"` + WorkflowId string `json:"workflowId" db:"workflowId"` + NodeId string `json:"nodeId" db:"nodeId"` + Node *WorkflowNode `json:"node" db:"node"` + Outputs []WorkflowNodeIO `json:"outputs" db:"outputs"` + Succeeded bool `json:"succeeded"db:"succeeded"` } diff --git a/internal/domain/workflow_run_log.go b/internal/domain/workflow_run_log.go index 91a40963..057010a0 100644 --- a/internal/domain/workflow_run_log.go +++ b/internal/domain/workflow_run_log.go @@ -1,5 +1,13 @@ package domain +type WorkflowRunLog struct { + Meta + WorkflowId string `json:"workflowId" db:"workflowId"` + Logs []RunLog `json:"logs" db:"logs"` + Succeeded bool `json:"succeeded" db:"succeeded"` + Error string `json:"error" db:"error"` +} + type RunLogOutput struct { Time string `json:"time"` Title string `json:"title"` @@ -8,19 +16,12 @@ type RunLogOutput struct { } type RunLog struct { + NodeId string `json:"nodeId"` NodeName string `json:"nodeName"` Error string `json:"error"` Outputs []RunLogOutput `json:"outputs"` } -type WorkflowRunLog struct { - Meta - Workflow string `json:"workflow"` - Log []RunLog `json:"log"` - Succeed bool `json:"succeed"` - Error string `json:"error"` -} - type RunLogs []RunLog func (r RunLogs) Error() string { diff --git a/internal/repository/workflow.go b/internal/repository/workflow.go index 681b177e..cebae222 100644 --- a/internal/repository/workflow.go +++ b/internal/repository/workflow.go @@ -44,9 +44,9 @@ func (w *WorkflowRepository) SaveRunLog(ctx context.Context, log *domain.Workflo } record := models.NewRecord(collection) - record.Set("workflow", log.Workflow) - record.Set("log", log.Log) - record.Set("succeed", log.Succeed) + record.Set("workflowId", log.WorkflowId) + record.Set("logs", log.Logs) + record.Set("succeeded", log.Succeeded) record.Set("error", log.Error) return app.GetApp().Dao().SaveRecord(record) diff --git a/internal/repository/workflow_output.go b/internal/repository/workflow_output.go index 34376c73..e9d8aba0 100644 --- a/internal/repository/workflow_output.go +++ b/internal/repository/workflow_output.go @@ -35,8 +35,8 @@ func (w *WorkflowOutputRepository) Get(ctx context.Context, nodeId string) (*dom return nil, errors.New("failed to unmarshal node") } - output := make([]domain.WorkflowNodeIO, 0) - if err := record.UnmarshalJSONField("output", &output); err != nil { + outputs := make([]domain.WorkflowNodeIO, 0) + if err := record.UnmarshalJSONField("outputs", &outputs); err != nil { return nil, errors.New("failed to unmarshal output") } @@ -46,18 +46,18 @@ func (w *WorkflowOutputRepository) Get(ctx context.Context, nodeId string) (*dom CreatedAt: record.GetCreated().Time(), UpdatedAt: record.GetUpdated().Time(), }, - Workflow: record.GetString("workflow"), - NodeId: record.GetString("nodeId"), - Node: node, - Output: output, - Succeed: record.GetBool("succeed"), + WorkflowId: record.GetString("workflowId"), + NodeId: record.GetString("nodeId"), + Node: node, + Outputs: outputs, + Succeeded: record.GetBool("succeeded"), } return rs, nil } func (w *WorkflowOutputRepository) GetCertificate(ctx context.Context, nodeId string) (*domain.Certificate, error) { - records, err := app.GetApp().Dao().FindRecordsByFilter("certificate", "nodeId={:nodeId}", "-created", 1, 0, dbx.Params{"nodeId": nodeId}) + records, err := app.GetApp().Dao().FindRecordsByFilter("certificate", "workflowNodeId={:workflowNodeId}", "-created", 1, 0, dbx.Params{"workflowNodeId": nodeId}) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, domain.ErrRecordNotFound @@ -76,16 +76,18 @@ func (w *WorkflowOutputRepository) GetCertificate(ctx context.Context, nodeId st CreatedAt: record.GetDateTime("created").Time(), UpdatedAt: record.GetDateTime("updated").Time(), }, + Source: record.GetString("source"), + SubjectAltNames: record.GetString("subjectAltNames"), Certificate: record.GetString("certificate"), PrivateKey: record.GetString("privateKey"), IssuerCertificate: record.GetString("issuerCertificate"), - SubjectAltNames: record.GetString("san"), - WorkflowOutputId: record.GetString("output"), + EffectAt: record.GetDateTime("effectAt").Time(), ExpireAt: record.GetDateTime("expireAt").Time(), - CertUrl: record.GetString("certUrl"), - CertStableUrl: record.GetString("certStableUrl"), - WorkflowId: record.GetString("workflow"), - WorkflowNodeId: record.GetString("nodeId"), + AcmeCertUrl: record.GetString("acmeCertUrl"), + AcmeCertStableUrl: record.GetString("acmeCertStableUrl"), + WorkflowId: record.GetString("workflowId"), + WorkflowNodeId: record.GetString("workflowNodeId"), + WorkflowOutputId: record.GetString("workflowOutputId"), } return rs, nil } @@ -107,11 +109,11 @@ func (w *WorkflowOutputRepository) Save(ctx context.Context, output *domain.Work return err } } - record.Set("workflow", output.Workflow) + record.Set("workflowId", output.WorkflowId) record.Set("nodeId", output.NodeId) record.Set("node", output.Node) - record.Set("output", output.Output) - record.Set("succeed", output.Succeed) + record.Set("outputs", output.Outputs) + record.Set("succeeded", output.Succeeded) if err := app.GetApp().Dao().SaveRecord(record); err != nil { return err @@ -128,30 +130,32 @@ func (w *WorkflowOutputRepository) Save(ctx context.Context, output *domain.Work } certRecord := models.NewRecord(certCollection) + certRecord.Set("source", certificate.Source) + certRecord.Set("subjectAltNames", certificate.SubjectAltNames) certRecord.Set("certificate", certificate.Certificate) certRecord.Set("privateKey", certificate.PrivateKey) certRecord.Set("issuerCertificate", certificate.IssuerCertificate) - certRecord.Set("san", certificate.SubjectAltNames) - certRecord.Set("output", certificate.WorkflowOutputId) + certRecord.Set("effectAt", certificate.EffectAt) certRecord.Set("expireAt", certificate.ExpireAt) - certRecord.Set("certUrl", certificate.CertUrl) - certRecord.Set("certStableUrl", certificate.CertStableUrl) - certRecord.Set("workflow", certificate.WorkflowId) - certRecord.Set("nodeId", certificate.WorkflowNodeId) + certRecord.Set("acmeCertUrl", certificate.AcmeCertUrl) + certRecord.Set("acmeCertStableUrl", certificate.AcmeCertStableUrl) + certRecord.Set("workflowId", certificate.WorkflowId) + certRecord.Set("workflowNodeId", certificate.WorkflowNodeId) + certRecord.Set("workflowOutputId", certificate.WorkflowOutputId) if err := app.GetApp().Dao().SaveRecord(certRecord); err != nil { return err } // 更新 certificate - for i, item := range output.Output { + for i, item := range output.Outputs { if item.Name == "certificate" { - output.Output[i].Value = certRecord.GetId() + output.Outputs[i].Value = certRecord.GetId() break } } - record.Set("output", output.Output) + record.Set("outputs", output.Outputs) if err := app.GetApp().Dao().SaveRecord(record); err != nil { return err diff --git a/internal/workflow/event.go b/internal/workflow/event.go index a15861ab..8a08510d 100644 --- a/internal/workflow/event.go +++ b/internal/workflow/event.go @@ -64,7 +64,7 @@ func update(ctx context.Context, record *models.Record) error { app.GetApp().Logger().Error("add cron job failed", "err", err) return fmt.Errorf("add cron job failed: %w", err) } - app.GetApp().Logger().Error("add cron job failed", "san", record.GetString("san")) + app.GetApp().Logger().Error("add cron job failed", "subjectAltNames", record.GetString("subjectAltNames")) scheduler.Start() return nil diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index fe415026..ea832f44 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -17,6 +17,8 @@ type applyNode struct { *Logger } +var validityDuration = time.Hour * 24 * 10 + func NewApplyNode(node *domain.WorkflowNode) *applyNode { return &applyNode{ node: node, @@ -46,14 +48,14 @@ func (a *applyNode) Run(ctx context.Context) error { return err } - if output != nil && output.Succeed { + if output != nil && output.Succeeded { cert, err := a.outputRepo.GetCertificate(ctx, a.node.Id) if err != nil { a.AddOutput(ctx, a.node.Name, "获取证书失败", err.Error()) return err } - if time.Until(cert.ExpireAt) > domain.ValidityDuration { + if time.Until(cert.ExpireAt) > validityDuration { a.AddOutput(ctx, a.node.Name, "已申请过证书,且证书在有效期内") return nil } @@ -81,28 +83,30 @@ func (a *applyNode) Run(ctx context.Context) error { outputId = output.Id } output = &domain.WorkflowOutput{ - Workflow: GetWorkflowId(ctx), - NodeId: a.node.Id, - Node: a.node, - Succeed: true, - Output: a.node.Output, - Meta: domain.Meta{Id: outputId}, + Meta: domain.Meta{Id: outputId}, + WorkflowId: GetWorkflowId(ctx), + NodeId: a.node.Id, + Node: a.node, + Succeeded: true, + Outputs: a.node.Output, } - cert, err := x509.ParseCertificateFromPEM(certificate.Certificate) + certX509, err := x509.ParseCertificateFromPEM(certificate.Certificate) if err != nil { a.AddOutput(ctx, a.node.Name, "解析证书失败", err.Error()) return err } certificateRecord := &domain.Certificate{ - SubjectAltNames: strings.Join(cert.DNSNames, ";"), + Source: string(domain.CERTIFICATE_SOURCE_WORKFLOW), + SubjectAltNames: strings.Join(certX509.DNSNames, ";"), Certificate: certificate.Certificate, PrivateKey: certificate.PrivateKey, IssuerCertificate: certificate.IssuerCertificate, - CertUrl: certificate.CertUrl, - CertStableUrl: certificate.CertStableUrl, - ExpireAt: cert.NotAfter, + AcmeCertUrl: certificate.CertUrl, + AcmeCertStableUrl: certificate.CertStableUrl, + EffectAt: certX509.NotBefore, + ExpireAt: certX509.NotAfter, WorkflowId: GetWorkflowId(ctx), WorkflowNodeId: a.node.Id, } diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go index e93d0eaf..965cc3f2 100644 --- a/internal/workflow/node-processor/deploy_node.go +++ b/internal/workflow/node-processor/deploy_node.go @@ -71,8 +71,8 @@ func (d *deployNode) Run(ctx context.Context) error { AccessConfig: access.Config, AccessRecord: access, Certificate: applicant.Certificate{ - CertUrl: cert.CertUrl, - CertStableUrl: cert.CertStableUrl, + CertUrl: cert.AcmeCertUrl, + CertStableUrl: cert.AcmeCertStableUrl, PrivateKey: cert.PrivateKey, Certificate: cert.Certificate, IssuerCertificate: cert.IssuerCertificate, @@ -105,11 +105,11 @@ func (d *deployNode) Run(ctx context.Context) error { outputId = output.Id } output = &domain.WorkflowOutput{ - Workflow: GetWorkflowId(ctx), - NodeId: d.node.Id, - Node: d.node, - Succeed: true, - Meta: domain.Meta{Id: outputId}, + Meta: domain.Meta{Id: outputId}, + WorkflowId: GetWorkflowId(ctx), + NodeId: d.node.Id, + Node: d.node, + Succeeded: true, } if err := d.outputRepo.Save(ctx, output, nil, nil); err != nil { @@ -123,5 +123,5 @@ func (d *deployNode) Run(ctx context.Context) error { } func (d *deployNode) deployed(output *domain.WorkflowOutput) bool { - return output != nil && output.Succeed + return output != nil && output.Succeeded } diff --git a/internal/workflow/node-processor/processor.go b/internal/workflow/node-processor/processor.go index 48616844..828a5d19 100644 --- a/internal/workflow/node-processor/processor.go +++ b/internal/workflow/node-processor/processor.go @@ -21,6 +21,7 @@ type Logger struct { func NewLogger(node *domain.WorkflowNode) *Logger { return &Logger{ log: &domain.RunLog{ + NodeId: node.Id, NodeName: node.Name, Outputs: make([]domain.RunLogOutput, 0), }, diff --git a/internal/workflow/service.go b/internal/workflow/service.go index 1bd3c0ca..3b604326 100644 --- a/internal/workflow/service.go +++ b/internal/workflow/service.go @@ -69,10 +69,10 @@ func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) e processor := nodeprocessor.NewWorkflowProcessor(workflow) if err := processor.Run(ctx); err != nil { log := &domain.WorkflowRunLog{ - Workflow: workflow.Id, - Log: processor.Log(ctx), - Succeed: false, - Error: err.Error(), + WorkflowId: workflow.Id, + Logs: processor.Log(ctx), + Succeeded: false, + Error: err.Error(), } if err := s.repo.SaveRunLog(ctx, log); err != nil { app.GetApp().Logger().Error("failed to save run log", "err", err) @@ -89,10 +89,10 @@ func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) e succeed = false } log := &domain.WorkflowRunLog{ - Workflow: workflow.Id, - Log: processor.Log(ctx), - Error: runErr, - Succeed: succeed, + WorkflowId: workflow.Id, + Logs: processor.Log(ctx), + Error: runErr, + Succeeded: succeed, } if err := s.repo.SaveRunLog(ctx, log); err != nil { app.GetApp().Logger().Error("failed to save run log", "err", err) diff --git a/migrations/1735976342_updated_certificate.go b/migrations/1735976342_updated_certificate.go new file mode 100644 index 00000000..2537f86c --- /dev/null +++ b/migrations/1735976342_updated_certificate.go @@ -0,0 +1,298 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + // add + new_effectAt := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "v40aqzpd", + "name": "effectAt", + "type": "date", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": "", + "max": "" + } + }`), new_effectAt); err != nil { + return err + } + collection.Schema.AddField(new_effectAt) + + // update + edit_subjectAltNames := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "fugxf58p", + "name": "subjectAltNames", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), edit_subjectAltNames); err != nil { + return err + } + collection.Schema.AddField(edit_subjectAltNames) + + // update + edit_acmeCertUrl := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "ayyjy5ve", + "name": "acmeCertUrl", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }`), edit_acmeCertUrl); err != nil { + return err + } + collection.Schema.AddField(edit_acmeCertUrl) + + // update + edit_acmeCertStableUrl := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "3x5heo8e", + "name": "acmeCertStableUrl", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }`), edit_acmeCertStableUrl); err != nil { + return err + } + collection.Schema.AddField(edit_acmeCertStableUrl) + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "uvqfamb1", + "name": "workflowId", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_workflowNodeId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "uqldzldw", + "name": "workflowNodeId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), edit_workflowNodeId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowNodeId) + + // update + edit_workflowOutputId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2ohlr0yd", + "name": "workflowOutputId", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bqnxb95f2cooowp", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowOutputId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowOutputId) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("v40aqzpd") + + // update + edit_subjectAltNames := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "fugxf58p", + "name": "san", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), edit_subjectAltNames); err != nil { + return err + } + collection.Schema.AddField(edit_subjectAltNames) + + // update + edit_acmeCertUrl := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "ayyjy5ve", + "name": "certUrl", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }`), edit_acmeCertUrl); err != nil { + return err + } + collection.Schema.AddField(edit_acmeCertUrl) + + // update + edit_acmeCertStableUrl := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "3x5heo8e", + "name": "certStableUrl", + "type": "url", + "required": false, + "presentable": false, + "unique": false, + "options": { + "exceptDomains": null, + "onlyDomains": null + } + }`), edit_acmeCertStableUrl); err != nil { + return err + } + collection.Schema.AddField(edit_acmeCertStableUrl) + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "uvqfamb1", + "name": "workflow", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_workflowNodeId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "uqldzldw", + "name": "nodeId", + "type": "text", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "pattern": "" + } + }`), edit_workflowNodeId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowNodeId) + + // update + edit_workflowOutputId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2ohlr0yd", + "name": "output", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "bqnxb95f2cooowp", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowOutputId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowOutputId) + + return dao.SaveCollection(collection) + }) +} diff --git a/migrations/1735977005_updated_workflow_output.go b/migrations/1735977005_updated_workflow_output.go new file mode 100644 index 00000000..26795e2e --- /dev/null +++ b/migrations/1735977005_updated_workflow_output.go @@ -0,0 +1,144 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("bqnxb95f2cooowp") + if err != nil { + return err + } + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "jka88auc", + "name": "workflowId", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_outputs := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "he4cceqb", + "name": "outputs", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }`), edit_outputs); err != nil { + return err + } + collection.Schema.AddField(edit_outputs) + + // update + edit_succeeded := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2yfxbxuf", + "name": "succeeded", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }`), edit_succeeded); err != nil { + return err + } + collection.Schema.AddField(edit_succeeded) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("bqnxb95f2cooowp") + if err != nil { + return err + } + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "jka88auc", + "name": "workflow", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_outputs := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "he4cceqb", + "name": "output", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }`), edit_outputs); err != nil { + return err + } + collection.Schema.AddField(edit_outputs) + + // update + edit_succeeded := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2yfxbxuf", + "name": "succeed", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }`), edit_succeeded); err != nil { + return err + } + collection.Schema.AddField(edit_succeeded) + + return dao.SaveCollection(collection) + }) +} diff --git a/migrations/1735977021_updated_workflow_run_log.go b/migrations/1735977021_updated_workflow_run_log.go new file mode 100644 index 00000000..1cafab81 --- /dev/null +++ b/migrations/1735977021_updated_workflow_run_log.go @@ -0,0 +1,144 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("qjp8lygssgwyqyz") + if err != nil { + return err + } + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "m8xfsyyy", + "name": "workflowId", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_logs := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2m9byaa9", + "name": "logs", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }`), edit_logs); err != nil { + return err + } + collection.Schema.AddField(edit_logs) + + // update + edit_succeeded := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "cht6kqw9", + "name": "succeeded", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }`), edit_succeeded); err != nil { + return err + } + collection.Schema.AddField(edit_succeeded) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("qjp8lygssgwyqyz") + if err != nil { + return err + } + + // update + edit_workflowId := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "m8xfsyyy", + "name": "workflow", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "tovyif5ax6j62ur", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }`), edit_workflowId); err != nil { + return err + } + collection.Schema.AddField(edit_workflowId) + + // update + edit_logs := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "2m9byaa9", + "name": "log", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }`), edit_logs); err != nil { + return err + } + collection.Schema.AddField(edit_logs) + + // update + edit_succeeded := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "cht6kqw9", + "name": "succeed", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + }`), edit_succeeded); err != nil { + return err + } + collection.Schema.AddField(edit_succeeded) + + return dao.SaveCollection(collection) + }) +} diff --git a/migrations/1735977530_updated_certificate.go b/migrations/1735977530_updated_certificate.go new file mode 100644 index 00000000..8812c593 --- /dev/null +++ b/migrations/1735977530_updated_certificate.go @@ -0,0 +1,57 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + // add + new_source := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "by9hetqi", + "name": "source", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "workflow", + "upload" + ] + } + }`), new_source); err != nil { + return err + } + collection.Schema.AddField(new_source) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("by9hetqi") + + return dao.SaveCollection(collection) + }) +} diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index 425f8f88..6faff028 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -19,14 +19,14 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { const [messageApi, MessageContextHolder] = message.useMessage(); const handleDownloadPEMClick = async () => { - const zipName = `${data.id}-${data.san}.zip`; + const zipName = `${data.id}-${data.subjectAltNames}.zip`; const files = [ { - name: `${data.san}.pem`, + name: `${data.subjectAltNames}.pem`, content: data.certificate ?? "", }, { - name: `${data.san}.key`, + name: `${data.subjectAltNames}.key`, content: data.privateKey ?? "", }, ]; @@ -39,17 +39,17 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { {MessageContextHolder}
- - + + - - + +
- + { return ( - {t(provider?.name ?? " ")} + {t(provider?.name ?? "")} ); } @@ -80,7 +80,7 @@ const WorkflowElement = ({ node, disabled }: NodeProps) => {
{t(channel?.name ?? " ")} - {(node.config?.subject as string) ?? ""} + {config.subject ?? ""}
); diff --git a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx index ae48231f..0466f74f 100644 --- a/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx +++ b/ui/src/components/workflow/WorkflowRunDetailDrawer.tsx @@ -32,7 +32,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR setOpen(false)}> - + {t("workflow_run.props.status.succeeded")}} /> @@ -42,7 +42,7 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
- {data!.log.map((item, i) => { + {data!.logs.map((item, i) => { return (
{item.nodeName}
diff --git a/ui/src/components/workflow/WorkflowRuns.tsx b/ui/src/components/workflow/WorkflowRuns.tsx index 65357188..b8a97da8 100644 --- a/ui/src/components/workflow/WorkflowRuns.tsx +++ b/ui/src/components/workflow/WorkflowRuns.tsx @@ -46,7 +46,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { title: t("workflow_run.props.status"), ellipsis: true, render: (_, record) => { - if (record.succeed) { + if (record.succeeded) { return ( diff --git a/ui/src/domain/certificate.ts b/ui/src/domain/certificate.ts index 9a399bdb..33d6571e 100644 --- a/ui/src/domain/certificate.ts +++ b/ui/src/domain/certificate.ts @@ -1,17 +1,21 @@ import { type WorkflowModel } from "./workflow"; export interface CertificateModel extends BaseModel { - san: string; + source: string; + subjectAltNames: string; certificate: string; privateKey: string; - issuerCertificate: string; - certUrl: string; - certStableUrl: string; - output: string; + effectAt: ISO8601String; expireAt: ISO8601String; - workflow: string; - nodeId: string; + workflowId: string; expand: { - workflow?: WorkflowModel; + workflowId?: WorkflowModel; // TODO: ugly, maybe to use an alias? }; } + +export const CERTIFICATE_SOURCES = Object.freeze({ + WORKFLOW: "workflow", + UPLOAD: "upload", +} as const); + +export type CertificateSourceType = (typeof CERTIFICATE_SOURCES)[keyof typeof CERTIFICATE_SOURCES]; diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 996fa6d4..afb47ac1 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -400,9 +400,10 @@ export const isAllNodesValidated = (node: WorkflowNode): boolean => { */ export const getExecuteMethod = (node: WorkflowNode): { trigger: string; triggerCron: string } => { if (node.type === WorkflowNodeType.Start) { + const config = node.config as WorkflowNodeConfigAsStart; return { - trigger: (node.config?.trigger as string) ?? "", - triggerCron: (node.config?.triggerCron as string) ?? "", + trigger: config.trigger ?? "", + triggerCron: config.triggerCron ?? "", }; } else { return { diff --git a/ui/src/domain/workflowRun.ts b/ui/src/domain/workflowRun.ts index 045acabf..29ed790f 100644 --- a/ui/src/domain/workflowRun.ts +++ b/ui/src/domain/workflowRun.ts @@ -1,14 +1,15 @@ export interface WorkflowRunModel extends BaseModel { - workflow: string; - log: WorkflowRunLog[]; + workflowId: string; + logs: WorkflowRunLog[]; error: string; - succeed: boolean; + succeeded: boolean; } export type WorkflowRunLog = { + nodeId: string; nodeName: string; - error: string; outputs: WorkflowRunLogOutput[]; + error: string; }; export type WorkflowRunLogOutput = { diff --git a/ui/src/i18n/locales/en/nls.certificate.json b/ui/src/i18n/locales/en/nls.certificate.json index e707a863..dd9aa4c2 100644 --- a/ui/src/i18n/locales/en/nls.certificate.json +++ b/ui/src/i18n/locales/en/nls.certificate.json @@ -7,17 +7,17 @@ "certificate.action.delete": "Delete certificate", "certificate.action.download": "Download certificate", - "certificate.props.san": "Name", - "certificate.props.expiry": "Expiry", - "certificate.props.expiry.left_days": "{{left}} / {{total}} days left", - "certificate.props.expiry.expired": "Expired", - "certificate.props.expiry.expiration": "Expire on {{date}}", - "certificate.props.expiry.filter.expire_soon": "Expire soon", - "certificate.props.expiry.filter.expired": "Expired", - "certificate.props.workflow": "Workflow", + "certificate.props.subject_alt_names": "Name", + "certificate.props.validity": "Expiry", + "certificate.props.validity.left_days": "{{left}} / {{total}} days left", + "certificate.props.validity.expired": "Expired", + "certificate.props.validity.expiration": "Expire on {{date}}", + "certificate.props.validity.filter.expire_soon": "Expire soon", + "certificate.props.validity.filter.expired": "Expired", "certificate.props.source": "Source", "certificate.props.source.workflow": "Workflow", - "certificate.props.certificate_chain": "Certificate chain", + "certificate.props.source.upload": "Upload", + "certificate.props.certificate": "Certificate chain", "certificate.props.private_key": "Private key", "certificate.props.created_at": "Created at", "certificate.props.updated_at": "Updated at" diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json index 0353b38d..bf14655b 100644 --- a/ui/src/i18n/locales/zh/nls.certificate.json +++ b/ui/src/i18n/locales/zh/nls.certificate.json @@ -7,17 +7,17 @@ "certificate.action.delete": "删除证书", "certificate.action.download": "下载证书", - "certificate.props.san": "名称", - "certificate.props.expiry": "有效期限", - "certificate.props.expiry.left_days": "{{left}} / {{total}} 天", - "certificate.props.expiry.expired": "已到期", - "certificate.props.expiry.expiration": "{{date}} 到期", - "certificate.props.expiry.filter.expire_soon": "即将到期", - "certificate.props.expiry.filter.expired": "已到期", - "certificate.props.workflow": "所属工作流", + "certificate.props.subject_alt_names": "名称", + "certificate.props.validity": "有效期限", + "certificate.props.validity.left_days": "{{left}} / {{total}} 天", + "certificate.props.validity.expired": "已到期", + "certificate.props.validity.expiration": "{{date}} 到期", + "certificate.props.validity.filter.expire_soon": "即将到期", + "certificate.props.validity.filter.expired": "已到期", "certificate.props.source": "来源", "certificate.props.source.workflow": "工作流", - "certificate.props.certificate_chain": "证书内容", + "certificate.props.source.upload": "用户上传", + "certificate.props.certificate": "证书内容", "certificate.props.private_key": "私钥内容", "certificate.props.created_at": "创建时间", "certificate.props.updated_at": "更新时间" diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index e95cb4ae..0e2a958e 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -9,7 +9,7 @@ import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer"; -import { type CertificateModel } from "@/domain/certificate"; +import { CERTIFICATE_SOURCES, type CertificateModel } from "@/domain/certificate"; import { type ListCertificateRequest, list as listCertificate } from "@/repository/certificate"; import { getErrMsg } from "@/utils/error"; @@ -33,18 +33,18 @@ const CertificateList = () => { }, { key: "name", - title: t("certificate.props.san"), - render: (_, record) => {record.san}, + title: t("certificate.props.subject_alt_names"), + render: (_, record) => {record.subjectAltNames}, }, { key: "expiry", - title: t("certificate.props.expiry"), + title: t("certificate.props.validity"), ellipsis: true, defaultFilteredValue: searchParams.has("state") ? [searchParams.get("state") as string] : undefined, filterDropdown: ({ setSelectedKeys, confirm, clearFilters }) => { const items: Required["items"] = [ - ["expireSoon", "certificate.props.expiry.filter.expire_soon"], - ["expired", "certificate.props.expiry.filter.expired"], + ["expireSoon", "certificate.props.validity.filter.expire_soon"], + ["expired", "certificate.props.validity.filter.expired"], ].map(([key, label]) => { return { key, @@ -94,13 +94,13 @@ const CertificateList = () => { return ( {left > 0 ? ( - {t("certificate.props.expiry.left_days", { left, total })} + {t("certificate.props.validity.left_days", { left, total })} ) : ( - {t("certificate.props.expiry.expired")} + {t("certificate.props.validity.expired")} )} - {t("certificate.props.expiry.expiration", { date: dayjs(record.expireAt).format("YYYY-MM-DD") })} + {t("certificate.props.validity.expiration", { date: dayjs(record.expireAt).format("YYYY-MM-DD") })} ); @@ -111,23 +111,29 @@ const CertificateList = () => { title: t("certificate.props.source"), ellipsis: true, render: (_, record) => { - const workflowId = record.workflow; - return workflowId ? ( - - {t("certificate.props.source.workflow")} - { - navigate(`/workflows/${workflowId}`); - }} - > - {record.expand?.workflow?.name ?? ""} - - - ) : ( - <>TODO: 支持手动上传 - ); + if (record.source === CERTIFICATE_SOURCES.WORKFLOW) { + const workflowId = record.workflowId; + return ( + + {t("certificate.props.source.workflow")} + { + if (workflowId) { + navigate(`/workflows/${workflowId}`); + } + }} + > + {record.expand?.workflowId?.name ?? `#${workflowId}`} + + + ); + } else if (record.source === CERTIFICATE_SOURCES.UPLOAD) { + return {t("certificate.props.source.upload")}; + } + + return <>; }, }, { diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts index 7a3ac1d6..0589c116 100644 --- a/ui/src/repository/certificate.ts +++ b/ui/src/repository/certificate.ts @@ -20,7 +20,7 @@ export const list = async (request: ListCertificateRequest) => { const options: RecordListOptions = { sort: "-created", - expand: "workflow", + expand: "workflowId", requestKey: null, }; diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts index d51b7ea9..4d5794b5 100644 --- a/ui/src/repository/workflowRun.ts +++ b/ui/src/repository/workflowRun.ts @@ -17,7 +17,7 @@ export const list = async (request: ListWorkflowRunsRequest) => { return await getPocketBase() .collection(COLLECTION_NAME) .getList(page, perPage, { - filter: getPocketBase().filter("workflow={:workflowId}", { workflowId: request.workflowId }), + filter: getPocketBase().filter("workflowId={:workflowId}", { workflowId: request.workflowId }), sort: "-created", requestKey: null, });