mirror of
https://github.com/usual2970/certimate.git
synced 2025-07-08 20:19:55 +00:00
Compare commits
9 Commits
59dc4b0030
...
bc69ab43f5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bc69ab43f5 | ||
![]() |
94408e8a9f | ||
![]() |
f3c7d096bc | ||
![]() |
774ed5d31e | ||
![]() |
b07174b533 | ||
![]() |
1f6b33f4f6 | ||
![]() |
049707acdc | ||
![]() |
e019bfe136 | ||
![]() |
57f8db010b |
@ -5,7 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -59,7 +59,7 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error) {
|
func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error) {
|
||||||
certificate, err := s.certRepo.GetById(ctx, req.CertificateId)
|
certificate, err := s.certRepo.GetById(ctx, req.CertificateId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -69,6 +69,10 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
zipWriter := zip.NewWriter(&buf)
|
zipWriter := zip.NewWriter(&buf)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
resp := &dtos.CertificateArchiveFileResp{
|
||||||
|
FileFormat: "zip",
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToUpper(req.Format) {
|
switch strings.ToUpper(req.Format) {
|
||||||
case "", "PEM":
|
case "", "PEM":
|
||||||
{
|
{
|
||||||
@ -97,7 +101,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "PFX":
|
case "PFX":
|
||||||
@ -134,7 +139,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "JKS":
|
case "JKS":
|
||||||
@ -171,7 +177,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
resp.FileBytes = buf.Bytes()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -180,25 +187,30 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
|
func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) {
|
||||||
info, err := certs.ParseCertificateFromPEM(req.Certificate)
|
certX509, err := certs.ParseCertificateFromPEM(req.Certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if time.Now().After(certX509.NotAfter) {
|
||||||
|
return nil, fmt.Errorf("certificate has expired at %s", certX509.NotAfter.UTC().Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dtos.CertificateValidateCertificateResp{
|
||||||
|
IsValid: true,
|
||||||
|
Domains: strings.Join(certX509.DNSNames, ";"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error) {
|
||||||
|
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(info.NotAfter) {
|
return &dtos.CertificateValidatePrivateKeyResp{
|
||||||
return nil, errors.New("证书已过期")
|
IsValid: true,
|
||||||
}
|
|
||||||
|
|
||||||
return &dtos.CertificateValidateCertificateResp{
|
|
||||||
Domains: strings.Join(info.DNSNames, ";"),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error {
|
|
||||||
_, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
|
func buildExpireSoonNotification(certificates []*domain.Certificate) *struct {
|
||||||
Subject string
|
Subject string
|
||||||
Message string
|
Message string
|
||||||
|
@ -5,22 +5,24 @@ type CertificateArchiveFileReq struct {
|
|||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CertificateArchiveFileResp struct {
|
||||||
|
FileBytes []byte `json:"fileBytes"`
|
||||||
|
FileFormat string `json:"fileFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
type CertificateValidateCertificateReq struct {
|
type CertificateValidateCertificateReq struct {
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateValidateCertificateResp struct {
|
type CertificateValidateCertificateResp struct {
|
||||||
Domains string `json:"domains"`
|
IsValid bool `json:"isValid"`
|
||||||
|
Domains string `json:"domains,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateValidatePrivateKeyReq struct {
|
type CertificateValidatePrivateKeyReq struct {
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateUploadReq struct {
|
type CertificateValidatePrivateKeyResp struct {
|
||||||
WorkflowId string `json:"workflowId"`
|
IsValid bool `json:"isValid"`
|
||||||
WorkflowNodeId string `json:"workflowNodeId"`
|
|
||||||
CertificateId string `json:"certificateId"`
|
|
||||||
Certificate string `json:"certificate"`
|
|
||||||
PrivateKey string `json:"privateKey"`
|
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ type WorkflowNode struct {
|
|||||||
type WorkflowNodeConfigForApply struct {
|
type WorkflowNodeConfigForApply struct {
|
||||||
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
|
Domains string `json:"domains"` // 域名列表,以半角逗号分隔
|
||||||
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
ContactEmail string `json:"contactEmail"` // 联系邮箱
|
||||||
|
ChallengeType string `json:"challengeType"` // TODO: 验证方式。目前仅支持 dns-01
|
||||||
Provider string `json:"provider"` // DNS 提供商
|
Provider string `json:"provider"` // DNS 提供商
|
||||||
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
ProviderAccessId string `json:"providerAccessId"` // DNS 提供商授权记录 ID
|
||||||
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置
|
||||||
|
@ -131,6 +131,11 @@ func (d *K8sSecretDeployer) Deploy(ctx context.Context, certPem string, privkeyP
|
|||||||
secretPayload.ObjectMeta.Annotations[k] = v
|
secretPayload.ObjectMeta.Annotations[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if secretPayload.Data == nil {
|
||||||
|
secretPayload.Data = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
secretPayload.Data[d.config.SecretDataKeyForCrt] = []byte(certPem)
|
||||||
|
secretPayload.Data[d.config.SecretDataKeyForKey] = []byte(privkeyPem)
|
||||||
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8sMeta.UpdateOptions{})
|
secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Update(context.TODO(), secretPayload, k8sMeta.UpdateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to update k8s secret")
|
return nil, xerrors.Wrap(err, "failed to update k8s secret")
|
||||||
|
@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type certificateService interface {
|
type certificateService interface {
|
||||||
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error)
|
ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error)
|
||||||
ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error)
|
ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error)
|
||||||
ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error
|
ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertificateHandler struct {
|
type CertificateHandler struct {
|
||||||
@ -38,10 +38,10 @@ func (handler *CertificateHandler) archiveFile(e *core.RequestEvent) error {
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bt, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, bt)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +51,10 @@ func (handler *CertificateHandler) validateCertificate(e *core.RequestEvent) err
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, rs)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ func (handler *CertificateHandler) validatePrivateKey(e *core.RequestEvent) erro
|
|||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil {
|
if res, err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil {
|
||||||
return resp.Err(e, err)
|
return resp.Err(e, err)
|
||||||
} else {
|
} else {
|
||||||
return resp.Ok(e, nil)
|
return resp.Ok(e, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,11 +142,11 @@ func (w *WorkflowDispatcher) Shutdown() {
|
|||||||
w.workerMutex.Lock()
|
w.workerMutex.Lock()
|
||||||
for _, worker := range w.workers {
|
for _, worker := range w.workers {
|
||||||
worker.Cancel()
|
worker.Cancel()
|
||||||
|
delete(w.workers, worker.Data.WorkflowId)
|
||||||
|
delete(w.workerIdMap, worker.Data.RunId)
|
||||||
}
|
}
|
||||||
w.workerMutex.Unlock()
|
w.workerMutex.Unlock()
|
||||||
w.wg.Wait()
|
w.wg.Wait()
|
||||||
w.workers = make(map[string]*workflowWorker)
|
|
||||||
w.workerIdMap = make(map[string]string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkflowDispatcher) enqueueWorker(data *WorkflowWorkerData) {
|
func (w *WorkflowDispatcher) enqueueWorker(data *WorkflowWorkerData) {
|
||||||
|
58
migrations/1739263253_updated_workflow_run.go
Normal file
58
migrations/1739263253_updated_workflow_run.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "m8xfsyyy",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("qjp8lygssgwyqyz")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "m8xfsyyy",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
92
migrations/1739263264_updated_workflow_output.go
Normal file
92
migrations/1739263264_updated_workflow_output.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "jka88auc",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation821863227",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "runId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("bqnxb95f2cooowp")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(1, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "tovyif5ax6j62ur",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "jka88auc",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "workflowId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "qjp8lygssgwyqyz",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation821863227",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "runId",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
@ -3,10 +3,14 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
import { type CertificateFormatType } from "@/domain/certificate";
|
import { type CertificateFormatType } from "@/domain/certificate";
|
||||||
import { getPocketBase } from "@/repository/_pocketbase";
|
import { getPocketBase } from "@/repository/_pocketbase";
|
||||||
|
|
||||||
|
type ArchiveRespData = {
|
||||||
|
fileBytes: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const archive = async (certificateId: string, format?: CertificateFormatType) => {
|
export const archive = async (certificateId: string, format?: CertificateFormatType) => {
|
||||||
const pb = getPocketBase();
|
const pb = getPocketBase();
|
||||||
|
|
||||||
const resp = await pb.send<BaseResponse<string>>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, {
|
const resp = await pb.send<BaseResponse<ArchiveRespData>>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -24,6 +28,7 @@ export const archive = async (certificateId: string, format?: CertificateFormatT
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ValidateCertificateResp = {
|
type ValidateCertificateResp = {
|
||||||
|
isValid: boolean;
|
||||||
domains: string;
|
domains: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,9 +51,13 @@ export const validateCertificate = async (certificate: string) => {
|
|||||||
return resp;
|
return resp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ValidatePrivateKeyResp = {
|
||||||
|
isValid: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const validatePrivateKey = async (privateKey: string) => {
|
export const validatePrivateKey = async (privateKey: string) => {
|
||||||
const pb = getPocketBase();
|
const pb = getPocketBase();
|
||||||
const resp = await pb.send<BaseResponse>(`/api/certificates/validate/private-key`, {
|
const resp = await pb.send<BaseResponse<ValidatePrivateKeyResp>>(`/api/certificates/validate/private-key`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -22,7 +22,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
const handleDownloadClick = async (format: CertificateFormatType) => {
|
const handleDownloadClick = async (format: CertificateFormatType) => {
|
||||||
try {
|
try {
|
||||||
const res = await archiveCertificate(data.id, format);
|
const res = await archiveCertificate(data.id, format);
|
||||||
const bstr = atob(res.data);
|
const bstr = atob(res.data.fileBytes);
|
||||||
const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0));
|
const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0));
|
||||||
const blob = new Blob([u8arr], { type: "application/zip" });
|
const blob = new Blob([u8arr], { type: "application/zip" });
|
||||||
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);
|
||||||
|
@ -56,6 +56,7 @@ const ApplyNode = ({ node, disabled }: ApplyNodeProps) => {
|
|||||||
const newNode = produce(node, (draft) => {
|
const newNode = produce(node, (draft) => {
|
||||||
draft.config = {
|
draft.config = {
|
||||||
...newValues,
|
...newValues,
|
||||||
|
challengeType: newValues.challengeType || "dns-01", // 默认使用 DNS-01 认证
|
||||||
};
|
};
|
||||||
draft.validated = true;
|
draft.validated = true;
|
||||||
});
|
});
|
||||||
|
@ -56,6 +56,7 @@ const MULTIPLE_INPUT_DELIMITER = ";";
|
|||||||
|
|
||||||
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
||||||
return {
|
return {
|
||||||
|
challengeType: "dns-01",
|
||||||
keyAlgorithm: "RSA2048",
|
keyAlgorithm: "RSA2048",
|
||||||
skipBeforeExpiryDays: 20,
|
skipBeforeExpiryDays: 20,
|
||||||
};
|
};
|
||||||
@ -74,6 +75,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
.every((e) => validDomainName(e, { allowWildcard: true }));
|
.every((e) => validDomainName(e, { allowWildcard: true }));
|
||||||
}, t("common.errmsg.domain_invalid")),
|
}, t("common.errmsg.domain_invalid")),
|
||||||
contactEmail: z.string({ message: t("workflow_node.apply.form.contact_email.placeholder") }).email(t("common.errmsg.email_invalid")),
|
contactEmail: z.string({ message: t("workflow_node.apply.form.contact_email.placeholder") }).email(t("common.errmsg.email_invalid")),
|
||||||
|
challengeType: z.string().nullish(),
|
||||||
provider: z.string({ message: t("workflow_node.apply.form.provider.placeholder") }).nonempty(t("workflow_node.apply.form.provider.placeholder")),
|
provider: z.string({ message: t("workflow_node.apply.form.provider.placeholder") }).nonempty(t("workflow_node.apply.form.provider.placeholder")),
|
||||||
providerAccessId: z
|
providerAccessId: z
|
||||||
.string({ message: t("workflow_node.apply.form.provider_access.placeholder") })
|
.string({ message: t("workflow_node.apply.form.provider_access.placeholder") })
|
||||||
@ -235,6 +237,16 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
<EmailInput placeholder={t("workflow_node.apply.form.contact_email.placeholder")} />
|
<EmailInput placeholder={t("workflow_node.apply.form.contact_email.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="challengeType" label={t("workflow_node.apply.form.challenge_type.label")} rules={[formRule]} hidden>
|
||||||
|
<Select
|
||||||
|
options={["DNS-01"].map((e) => ({
|
||||||
|
label: e,
|
||||||
|
value: e.toLowerCase(),
|
||||||
|
}))}
|
||||||
|
placeholder={t("workflow_node.apply.form.challenge_type.placeholder")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="provider" label={t("workflow_node.apply.form.provider.label")} hidden rules={[formRule]}>
|
<Form.Item name="provider" label={t("workflow_node.apply.form.provider.label")} hidden rules={[formRule]}>
|
||||||
<ApplyDNSProviderSelect
|
<ApplyDNSProviderSelect
|
||||||
allowClear
|
allowClear
|
||||||
|
@ -1 +1 @@
|
|||||||
export const version = "v0.3.0-alpha.10";
|
export const version = "v0.3.0-alpha.11";
|
||||||
|
@ -122,6 +122,7 @@ export type WorkflowNodeConfigForStart = {
|
|||||||
export type WorkflowNodeConfigForApply = {
|
export type WorkflowNodeConfigForApply = {
|
||||||
domains: string;
|
domains: string;
|
||||||
contactEmail: string;
|
contactEmail: string;
|
||||||
|
challengeType: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
providerAccessId: string;
|
providerAccessId: string;
|
||||||
providerConfig?: Record<string, unknown>;
|
providerConfig?: Record<string, unknown>;
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
|
line-height: 1.5;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
@ -15,8 +10,7 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
padding: 0;
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import dayjsUtc from "dayjs/plugin/utc";
|
|||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./i18n";
|
import "./i18n";
|
||||||
|
import "./index.css";
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
|
|
||||||
dayjs.extend(dayjsUtc);
|
dayjs.extend(dayjsUtc);
|
||||||
|
@ -6,3 +6,11 @@ export const getPocketBase = () => {
|
|||||||
pb = new PocketBase("/");
|
pb = new PocketBase("/");
|
||||||
return pb;
|
return pb;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const COLLECTION_NAME_ADMIN = "_superusers";
|
||||||
|
export const COLLECTION_NAME_ACCESS = "access";
|
||||||
|
export const COLLECTION_NAME_CERTIFICATE = "certificate";
|
||||||
|
export const COLLECTION_NAME_SETTINGS = "settings";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW = "workflow";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW_RUN = "workflow_run";
|
||||||
|
export const COLLECTION_NAME_WORKFLOW_OUTPUT = "workflow_output";
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { type AccessModel } from "@/domain/access";
|
import { type AccessModel } from "@/domain/access";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_ACCESS, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "access";
|
|
||||||
|
|
||||||
export const list = async () => {
|
export const list = async () => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).getFullList<AccessModel>({
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).getFullList<AccessModel>({
|
||||||
filter: "deleted=null",
|
filter: "deleted=null",
|
||||||
sort: "-created",
|
sort: "-created",
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
@ -15,15 +13,15 @@ export const list = async () => {
|
|||||||
|
|
||||||
export const save = async (record: MaybeModelRecord<AccessModel>) => {
|
export const save = async (record: MaybeModelRecord<AccessModel>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id, record);
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).update<AccessModel>(record.id, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<AccessModel>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_ACCESS).create<AccessModel>(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<AccessModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<AccessModel>) => {
|
||||||
await getPocketBase()
|
await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_ACCESS)
|
||||||
.update<AccessModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
.update<AccessModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_ADMIN, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "_superusers";
|
|
||||||
|
|
||||||
export const authWithPassword = (username: string, password: string) => {
|
export const authWithPassword = (username: string, password: string) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).authWithPassword(username, password);
|
return getPocketBase().collection(COLLECTION_NAME_ADMIN).authWithPassword(username, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAuthStore = () => {
|
export const getAuthStore = () => {
|
||||||
@ -12,6 +10,6 @@ export const getAuthStore = () => {
|
|||||||
|
|
||||||
export const save = (data: { email: string } | { password: string; passwordConfirm: string }) => {
|
export const save = (data: { email: string } | { password: string; passwordConfirm: string }) => {
|
||||||
return getPocketBase()
|
return getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_ADMIN)
|
||||||
.update(getAuthStore().record?.id || "", data);
|
.update(getAuthStore().record?.id || "", data);
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,7 @@ import dayjs from "dayjs";
|
|||||||
import { type RecordListOptions } from "pocketbase";
|
import { type RecordListOptions } from "pocketbase";
|
||||||
|
|
||||||
import { type CertificateModel } from "@/domain/certificate";
|
import { type CertificateModel } from "@/domain/certificate";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "certificate";
|
|
||||||
|
|
||||||
export type ListCertificateRequest = {
|
export type ListCertificateRequest = {
|
||||||
page?: number;
|
page?: number;
|
||||||
@ -35,7 +33,7 @@ export const list = async (request: ListCertificateRequest) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb.collection(COLLECTION_NAME).getList<CertificateModel>(page, perPage, options);
|
return pb.collection(COLLECTION_NAME_CERTIFICATE).getList<CertificateModel>(page, perPage, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listByWorkflowRunId = async (workflowRunId: string) => {
|
export const listByWorkflowRunId = async (workflowRunId: string) => {
|
||||||
@ -48,7 +46,7 @@ export const listByWorkflowRunId = async (workflowRunId: string) => {
|
|||||||
sort: "-created",
|
sort: "-created",
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
};
|
};
|
||||||
const items = await pb.collection(COLLECTION_NAME).getFullList<CertificateModel>(options);
|
const items = await pb.collection(COLLECTION_NAME_CERTIFICATE).getFullList<CertificateModel>(options);
|
||||||
return {
|
return {
|
||||||
totalItems: items.length,
|
totalItems: items.length,
|
||||||
items: items,
|
items: items,
|
||||||
@ -57,7 +55,7 @@ export const listByWorkflowRunId = async (workflowRunId: string) => {
|
|||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<CertificateModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<CertificateModel>) => {
|
||||||
await getPocketBase()
|
await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_CERTIFICATE)
|
||||||
.update<CertificateModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
.update<CertificateModel>(record.id!, { deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") });
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { type SettingsModel, type SettingsNames } from "@/domain/settings";
|
import { type SettingsModel, type SettingsNames } from "@/domain/settings";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_SETTINGS, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "settings";
|
|
||||||
|
|
||||||
export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) => {
|
export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) => {
|
||||||
try {
|
try {
|
||||||
const resp = await getPocketBase().collection(COLLECTION_NAME).getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
const resp = await getPocketBase().collection(COLLECTION_NAME_SETTINGS).getFirstListItem<SettingsModel<T>>(`name='${name}'`, {
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
});
|
});
|
||||||
return resp;
|
return resp;
|
||||||
@ -25,8 +23,8 @@ export const get = async <T extends NonNullable<unknown>>(name: SettingsNames) =
|
|||||||
|
|
||||||
export const save = async <T extends NonNullable<unknown>>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
export const save = async <T extends NonNullable<unknown>>(record: MaybeModelRecordWithId<SettingsModel<T>>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).update<SettingsModel<T>>(record.id, record);
|
return await getPocketBase().collection(COLLECTION_NAME_SETTINGS).update<SettingsModel<T>>(record.id, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<SettingsModel<T>>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_SETTINGS).create<SettingsModel<T>>(record);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { type RecordListOptions, type RecordSubscription } from "pocketbase";
|
import { type RecordListOptions, type RecordSubscription } from "pocketbase";
|
||||||
|
|
||||||
import { type WorkflowModel } from "@/domain/workflow";
|
import { type WorkflowModel } from "@/domain/workflow";
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_WORKFLOW, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "workflow";
|
|
||||||
|
|
||||||
export type ListWorkflowRequest = {
|
export type ListWorkflowRequest = {
|
||||||
page?: number;
|
page?: number;
|
||||||
@ -26,11 +24,11 @@ export const list = async (request: ListWorkflowRequest) => {
|
|||||||
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled });
|
options.filter = pb.filter("enabled={:enabled}", { enabled: request.enabled });
|
||||||
}
|
}
|
||||||
|
|
||||||
return await pb.collection(COLLECTION_NAME).getList<WorkflowModel>(page, perPage, options);
|
return await pb.collection(COLLECTION_NAME_WORKFLOW).getList<WorkflowModel>(page, perPage, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const get = async (id: string) => {
|
export const get = async (id: string) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).getOne<WorkflowModel>(id, {
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne<WorkflowModel>(id, {
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -38,21 +36,21 @@ export const get = async (id: string) => {
|
|||||||
export const save = async (record: MaybeModelRecord<WorkflowModel>) => {
|
export const save = async (record: MaybeModelRecord<WorkflowModel>) => {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
return await getPocketBase()
|
return await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_WORKFLOW)
|
||||||
.update<WorkflowModel>(record.id as string, record);
|
.update<WorkflowModel>(record.id as string, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).create<WorkflowModel>(record);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).create<WorkflowModel>(record);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<WorkflowModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<WorkflowModel>) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).delete(record.id);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).delete(record.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowModel>) => void) => {
|
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowModel>) => void) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).subscribe(id, cb);
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW).subscribe(id, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unsubscribe = async (id: string) => {
|
export const unsubscribe = async (id: string) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).unsubscribe(id);
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW).unsubscribe(id);
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
|
|
||||||
import { getPocketBase } from "./_pocketbase";
|
import { COLLECTION_NAME_WORKFLOW_RUN, getPocketBase } from "./_pocketbase";
|
||||||
|
|
||||||
const COLLECTION_NAME = "workflow_run";
|
|
||||||
|
|
||||||
export type ListWorkflowRunsRequest = {
|
export type ListWorkflowRunsRequest = {
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
@ -25,7 +23,7 @@ export const list = async (request: ListWorkflowRunsRequest) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await getPocketBase()
|
return await getPocketBase()
|
||||||
.collection(COLLECTION_NAME)
|
.collection(COLLECTION_NAME_WORKFLOW_RUN)
|
||||||
.getList<WorkflowRunModel>(page, perPage, {
|
.getList<WorkflowRunModel>(page, perPage, {
|
||||||
filter: getPocketBase().filter(filter, params),
|
filter: getPocketBase().filter(filter, params),
|
||||||
sort: "-created",
|
sort: "-created",
|
||||||
@ -35,13 +33,13 @@ export const list = async (request: ListWorkflowRunsRequest) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {
|
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {
|
||||||
return await getPocketBase().collection(COLLECTION_NAME).delete(record.id);
|
return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowRunModel>) => void) => {
|
export const subscribe = async (id: string, cb: (e: RecordSubscription<WorkflowRunModel>) => void) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).subscribe(id, cb);
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).subscribe(id, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unsubscribe = async (id: string) => {
|
export const unsubscribe = async (id: string) => {
|
||||||
return getPocketBase().collection(COLLECTION_NAME).unsubscribe(id);
|
return getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).unsubscribe(id);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user