mirror of https://github.com/usual2970/certimate
				
				
				
			details improvement and unnecessary files deletion
							parent
							
								
									37df882ed3
								
							
						
					
					
						commit
						9ff3e22c80
					
				| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
package certificate
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domain"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/notify"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/repository"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
 | 
			
		||||
	defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CertificateRepository interface {
 | 
			
		||||
	GetExpireSoon(ctx context.Context) ([]domain.Certificate, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type certificateService struct {
 | 
			
		||||
	repo CertificateRepository
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCertificateService(repo CertificateRepository) *certificateService {
 | 
			
		||||
	return &certificateService{
 | 
			
		||||
		repo: repo,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *certificateService) InitSchedule(ctx context.Context) error {
 | 
			
		||||
	scheduler := app.GetScheduler()
 | 
			
		||||
 | 
			
		||||
	err := scheduler.Add("certificate", "0 0 * * *", func() {
 | 
			
		||||
		certs, err := s.repo.GetExpireSoon(context.Background())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			app.GetApp().Logger().Error("failed to get expire soon certificate", "err", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		msg := buildMsg(certs)
 | 
			
		||||
		if err := notify.SendToAllChannels(msg.Subject, msg.Message); err != nil {
 | 
			
		||||
			app.GetApp().Logger().Error("failed to send expire soon certificate", "err", err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.GetApp().Logger().Error("failed to add schedule", "err", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	scheduler.Start()
 | 
			
		||||
	app.GetApp().Logger().Info("certificate schedule started")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildMsg(records []domain.Certificate) *domain.NotifyMessage {
 | 
			
		||||
	if len(records) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 查询模板信息
 | 
			
		||||
	settingRepo := repository.NewSettingRepository()
 | 
			
		||||
	setting, err := settingRepo.GetByName(context.Background(), "templates")
 | 
			
		||||
 | 
			
		||||
	subject := defaultExpireSubject
 | 
			
		||||
	message := defaultExpireMessage
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		var templates *domain.NotifyTemplates
 | 
			
		||||
 | 
			
		||||
		json.Unmarshal([]byte(setting.Content), &templates)
 | 
			
		||||
 | 
			
		||||
		if templates != nil && len(templates.NotifyTemplates) > 0 {
 | 
			
		||||
			subject = templates.NotifyTemplates[0].Title
 | 
			
		||||
			message = templates.NotifyTemplates[0].Content
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 替换变量
 | 
			
		||||
	count := len(records)
 | 
			
		||||
	domains := make([]string, count)
 | 
			
		||||
 | 
			
		||||
	for i, record := range records {
 | 
			
		||||
		domains[i] = record.SAN
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	countStr := strconv.Itoa(count)
 | 
			
		||||
	domainStr := strings.Join(domains, ";")
 | 
			
		||||
 | 
			
		||||
	subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
 | 
			
		||||
	subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
 | 
			
		||||
 | 
			
		||||
	message = strings.ReplaceAll(message, "{COUNT}", countStr)
 | 
			
		||||
	message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
 | 
			
		||||
 | 
			
		||||
	// 返回消息
 | 
			
		||||
	return &domain.NotifyMessage{
 | 
			
		||||
		Subject: subject,
 | 
			
		||||
		Message: message,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,16 +6,16 @@ var ValidityDuration = time.Hour * 24 * 10
 | 
			
		|||
 | 
			
		||||
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"`
 | 
			
		||||
	Workflow          string    `json:"workflow"`
 | 
			
		||||
	ExpireAt          time.Time `json:"ExpireAt"`
 | 
			
		||||
	NodeId            string    `json:"nodeId"`
 | 
			
		||||
	SAN               string    `json:"san" db:"san"`
 | 
			
		||||
	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"`
 | 
			
		||||
	Output            string    `json:"output" db:"output"`
 | 
			
		||||
	Workflow          string    `json:"workflow" db:"workflow"`
 | 
			
		||||
	ExpireAt          time.Time `json:"ExpireAt" db:"expireAt"`
 | 
			
		||||
	NodeId            string    `json:"nodeId" db:"nodeId"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetaData struct {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ package domain
 | 
			
		|||
import "time"
 | 
			
		||||
 | 
			
		||||
type Meta struct {
 | 
			
		||||
	Id      string    `json:"id"`
 | 
			
		||||
	Created time.Time `json:"created"`
 | 
			
		||||
	Updated time.Time `json:"updated"`
 | 
			
		||||
	Id      string    `json:"id" db:"id"`
 | 
			
		||||
	Created time.Time `json:"created" db:"created"`
 | 
			
		||||
	Updated time.Time `json:"updated" db:"updated"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,3 +29,17 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
 | 
			
		|||
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NotifyTemplates struct {
 | 
			
		||||
	NotifyTemplates []NotifyTemplate `json:"notifyTemplates"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NotifyTemplate struct {
 | 
			
		||||
	Title   string `json:"title"`
 | 
			
		||||
	Content string `json:"content"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NotifyMessage struct {
 | 
			
		||||
	Subject string
 | 
			
		||||
	Message string
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,11 +15,17 @@ const (
 | 
			
		|||
	WorkflowNodeTypeCondition = "condition"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	WorkflowTypeAuto   = "auto"
 | 
			
		||||
	WorkflowTypeManual = "manual"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Workflow struct {
 | 
			
		||||
	Meta
 | 
			
		||||
	Name        string        `json:"name"`
 | 
			
		||||
	Description string        `json:"description"`
 | 
			
		||||
	Type        string        `json:"type"`
 | 
			
		||||
	Crontab     string        `json:"crontab"`
 | 
			
		||||
	Content     *WorkflowNode `json:"content"`
 | 
			
		||||
	Draft       *WorkflowNode `json:"draft"`
 | 
			
		||||
	Enabled     bool          `json:"enabled"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,38 +0,0 @@
 | 
			
		|||
package domains
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/notify"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitSchedule() {
 | 
			
		||||
	// 查询所有启用的域名
 | 
			
		||||
	records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "enabled=true", "-id", 500, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.GetApp().Logger().Error("查询所有启用的域名失败", "err", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 加入到定时任务
 | 
			
		||||
	for _, record := range records {
 | 
			
		||||
		if err := app.GetScheduler().Add(record.Id, record.GetString("crontab"), func() {
 | 
			
		||||
			if err := deploy(context.Background(), record); err != nil {
 | 
			
		||||
				app.GetApp().Logger().Error("部署失败", "err", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			app.GetApp().Logger().Error("加入到定时任务失败", "err", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 过期提醒
 | 
			
		||||
	app.GetScheduler().Add("expire", "0 0 * * *", func() {
 | 
			
		||||
		notify.PushExpireMsg()
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 启动定时任务
 | 
			
		||||
	app.GetScheduler().Start()
 | 
			
		||||
	app.GetApp().Logger().Info("定时任务启动成功", "total", app.GetScheduler().Total())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,96 +0,0 @@
 | 
			
		|||
package notify
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pocketbase/dbx"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/xtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
 | 
			
		||||
	defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PushExpireMsg() {
 | 
			
		||||
	// 查询即将过期的证书
 | 
			
		||||
	records, err := app.GetApp().Dao().FindRecordsByFilter("certificate", "expireAt<{:time}&&certUrl!=''", "-created", 500, 0,
 | 
			
		||||
		dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 20)})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.GetApp().Logger().Error("find expired domains by filter", "error", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 组装消息
 | 
			
		||||
	msg := buildMsg(records)
 | 
			
		||||
	if msg == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 发送通知
 | 
			
		||||
	if err := SendToAllChannels(msg.Subject, msg.Message); err != nil {
 | 
			
		||||
		app.GetApp().Logger().Error("send expire msg", "error", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type notifyTemplates struct {
 | 
			
		||||
	NotifyTemplates []notifyTemplate `json:"notifyTemplates"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type notifyTemplate struct {
 | 
			
		||||
	Title   string `json:"title"`
 | 
			
		||||
	Content string `json:"content"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type notifyMessage struct {
 | 
			
		||||
	Subject string
 | 
			
		||||
	Message string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildMsg(records []*models.Record) *notifyMessage {
 | 
			
		||||
	if len(records) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 查询模板信息
 | 
			
		||||
	templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
 | 
			
		||||
	subject := defaultExpireSubject
 | 
			
		||||
	message := defaultExpireMessage
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		var templates *notifyTemplates
 | 
			
		||||
		templateRecord.UnmarshalJSONField("content", templates)
 | 
			
		||||
		if templates != nil && len(templates.NotifyTemplates) > 0 {
 | 
			
		||||
			subject = templates.NotifyTemplates[0].Title
 | 
			
		||||
			message = templates.NotifyTemplates[0].Content
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 替换变量
 | 
			
		||||
	count := len(records)
 | 
			
		||||
	domains := make([]string, count)
 | 
			
		||||
 | 
			
		||||
	for i, record := range records {
 | 
			
		||||
		domains[i] = record.GetString("san")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	countStr := strconv.Itoa(count)
 | 
			
		||||
	domainStr := strings.Join(domains, ";")
 | 
			
		||||
 | 
			
		||||
	subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
 | 
			
		||||
	subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
 | 
			
		||||
 | 
			
		||||
	message = strings.ReplaceAll(message, "{COUNT}", countStr)
 | 
			
		||||
	message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
 | 
			
		||||
 | 
			
		||||
	// 返回消息
 | 
			
		||||
	return ¬ifyMessage{
 | 
			
		||||
		Subject: subject,
 | 
			
		||||
		Message: message,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package repository
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domain"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CertificateRepository struct{}
 | 
			
		||||
 | 
			
		||||
func NewCertificateRepository() *CertificateRepository {
 | 
			
		||||
	return &CertificateRepository{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *CertificateRepository) GetExpireSoon(ctx context.Context) ([]domain.Certificate, error) {
 | 
			
		||||
	rs := []domain.Certificate{}
 | 
			
		||||
	if err := app.GetApp().Dao().DB().
 | 
			
		||||
		NewQuery("select * from certificate where expireAt > datetime('now') and expireAt < datetime('now', '+20 days')").
 | 
			
		||||
		All(&rs); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import (
 | 
			
		|||
	"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"
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,26 @@ func NewWorkflowRepository() *WorkflowRepository {
 | 
			
		|||
	return &WorkflowRepository{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]domain.Workflow, error) {
 | 
			
		||||
	records, err := app.GetApp().Dao().FindRecordsByFilter(
 | 
			
		||||
		"workflow",
 | 
			
		||||
		"enabled={:enabled} && type={:type}",
 | 
			
		||||
		"-created", 1000, 0, dbx.Params{"enabled": true, "type": domain.WorkflowTypeAuto},
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	rs := make([]domain.Workflow, 0)
 | 
			
		||||
	for _, record := range records {
 | 
			
		||||
		workflow, err := record2Workflow(record)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		rs = append(rs, *workflow)
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *WorkflowRepository) SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error {
 | 
			
		||||
	collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_run_log")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +61,10 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return record2Workflow(record)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func record2Workflow(record *models.Record) (*domain.Workflow, error) {
 | 
			
		||||
	content := &domain.WorkflowNode{}
 | 
			
		||||
	if err := record.UnmarshalJSONField("content", content); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +84,7 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
 | 
			
		|||
		Name:        record.GetString("name"),
 | 
			
		||||
		Description: record.GetString("description"),
 | 
			
		||||
		Type:        record.GetString("type"),
 | 
			
		||||
		Crontab:     record.GetString("crontab"),
 | 
			
		||||
		Enabled:     record.GetBool("enabled"),
 | 
			
		||||
		HasDraft:    record.GetBool("hasDraft"),
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
package scheduler
 | 
			
		||||
 | 
			
		||||
import "context"
 | 
			
		||||
 | 
			
		||||
type CertificateService interface {
 | 
			
		||||
	InitSchedule(ctx context.Context) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCertificateScheduler(service CertificateService) error {
 | 
			
		||||
	return service.InitSchedule(context.Background())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package scheduler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/usual2970/certimate/internal/certificate"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/repository"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/workflow"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Register() {
 | 
			
		||||
	workflowRepo := repository.NewWorkflowRepository()
 | 
			
		||||
	workflowSvc := workflow.NewWorkflowService(workflowRepo)
 | 
			
		||||
 | 
			
		||||
	certificateRepo := repository.NewCertificateRepository()
 | 
			
		||||
	certificateSvc := certificate.NewCertificateService(certificateRepo)
 | 
			
		||||
 | 
			
		||||
	NewCertificateScheduler(certificateSvc)
 | 
			
		||||
 | 
			
		||||
	NewWorkflowScheduler(workflowSvc)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
package scheduler
 | 
			
		||||
 | 
			
		||||
import "context"
 | 
			
		||||
 | 
			
		||||
type WorkflowService interface {
 | 
			
		||||
	InitSchedule(ctx context.Context) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWorkflowScheduler(service WorkflowService) error {
 | 
			
		||||
	return service.InitSchedule(context.Background())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ package app
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pocketbase/pocketbase/tools/cron"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,10 @@ var scheduler *cron.Cron
 | 
			
		|||
func GetScheduler() *cron.Cron {
 | 
			
		||||
	schedulerOnce.Do(func() {
 | 
			
		||||
		scheduler = cron.New()
 | 
			
		||||
		location, err := time.LoadLocation("Asia/Shanghai")
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			scheduler.SetTimezone(location)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return scheduler
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
package workflow
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/pocketbase/pocketbase/core"
 | 
			
		||||
	"github.com/pocketbase/pocketbase/models"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domain"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/repository"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const tableName = "workflow"
 | 
			
		||||
 | 
			
		||||
func AddEvent() error {
 | 
			
		||||
	app := app.GetApp()
 | 
			
		||||
 | 
			
		||||
	app.OnRecordAfterCreateRequest(tableName).Add(func(e *core.RecordCreateEvent) error {
 | 
			
		||||
		return update(e.HttpContext.Request().Context(), e.Record)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	app.OnRecordAfterUpdateRequest(tableName).Add(func(e *core.RecordUpdateEvent) error {
 | 
			
		||||
		return update(e.HttpContext.Request().Context(), e.Record)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	app.OnRecordAfterDeleteRequest(tableName).Add(func(e *core.RecordDeleteEvent) error {
 | 
			
		||||
		return delete(e.HttpContext.Request().Context(), e.Record)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func delete(_ context.Context, record *models.Record) error {
 | 
			
		||||
	id := record.Id
 | 
			
		||||
	scheduler := app.GetScheduler()
 | 
			
		||||
	scheduler.Remove(id)
 | 
			
		||||
	scheduler.Start()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func update(ctx context.Context, record *models.Record) error {
 | 
			
		||||
	// 是不是自动
 | 
			
		||||
	// 是不是 enabled
 | 
			
		||||
 | 
			
		||||
	id := record.Id
 | 
			
		||||
	enabled := record.GetBool("enabled")
 | 
			
		||||
	executeMethod := record.GetString("type")
 | 
			
		||||
 | 
			
		||||
	scheduler := app.GetScheduler()
 | 
			
		||||
	if !enabled || executeMethod == domain.WorkflowTypeManual {
 | 
			
		||||
		scheduler.Remove(id)
 | 
			
		||||
		scheduler.Start()
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := scheduler.Add(id, record.GetString("crontab"), func() {
 | 
			
		||||
		NewWorkflowService(repository.NewWorkflowRepository()).Run(ctx, &domain.WorkflowRunReq{
 | 
			
		||||
			Id: id,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		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"))
 | 
			
		||||
 | 
			
		||||
	scheduler.Start()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import (
 | 
			
		|||
type WorkflowRepository interface {
 | 
			
		||||
	Get(ctx context.Context, id string) (*domain.Workflow, error)
 | 
			
		||||
	SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error
 | 
			
		||||
	ListEnabledAuto(ctx context.Context) ([]domain.Workflow, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WorkflowService struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,29 @@ func NewWorkflowService(repo WorkflowRepository) *WorkflowService {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *WorkflowService) InitSchedule(ctx context.Context) error {
 | 
			
		||||
	// 查询所有的 enabled auto workflow
 | 
			
		||||
	workflows, err := s.repo.ListEnabledAuto(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	scheduler := app.GetScheduler()
 | 
			
		||||
	for _, workflow := range workflows {
 | 
			
		||||
		err := scheduler.Add(workflow.Id, workflow.Crontab, func() {
 | 
			
		||||
			s.Run(ctx, &domain.WorkflowRunReq{
 | 
			
		||||
				Id: workflow.Id,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			app.GetApp().Logger().Error("failed to add schedule", "err", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	scheduler.Start()
 | 
			
		||||
	app.GetApp().Logger().Info("workflow schedule started")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) error {
 | 
			
		||||
	// 查询
 | 
			
		||||
	if req.Id == "" {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								main.go
								
								
								
								
							
							
						
						
									
										9
									
								
								main.go
								
								
								
								
							| 
						 | 
				
			
			@ -13,9 +13,10 @@ import (
 | 
			
		|||
 | 
			
		||||
	_ "github.com/usual2970/certimate/migrations"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/domains"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/routes"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/scheduler"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/utils/app"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/workflow"
 | 
			
		||||
	"github.com/usual2970/certimate/ui"
 | 
			
		||||
 | 
			
		||||
	_ "time/tzdata"
 | 
			
		||||
| 
						 | 
				
			
			@ -38,13 +39,13 @@ func main() {
 | 
			
		|||
		Automigrate: isGoRun,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	domains.AddEvent()
 | 
			
		||||
	workflow.AddEvent()
 | 
			
		||||
 | 
			
		||||
	app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
 | 
			
		||||
		domains.InitSchedule()
 | 
			
		||||
 | 
			
		||||
		routes.Register(e.Router)
 | 
			
		||||
 | 
			
		||||
		scheduler.Register()
 | 
			
		||||
 | 
			
		||||
		e.Router.GET(
 | 
			
		||||
			"/*",
 | 
			
		||||
			echo.StaticDirectoryHandler(ui.DistDirFS, false),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,109 +0,0 @@
 | 
			
		|||
import { useState } from "react";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { ClientResponseError } from "pocketbase";
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { PbErrorData } from "@/domain/base";
 | 
			
		||||
import { update } from "@/repository/access_group";
 | 
			
		||||
import { useConfigContext } from "@/providers/config";
 | 
			
		||||
 | 
			
		||||
type AccessGroupEditProps = {
 | 
			
		||||
  className?: string;
 | 
			
		||||
  trigger: React.ReactNode;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
 | 
			
		||||
  const { reloadAccessGroups } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    name: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(1, "access.group.form.name.errmsg.empty")
 | 
			
		||||
      .max(64, t("common.errmsg.string_max", { max: 64 })),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      name: "",
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (data: z.infer<typeof formSchema>) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await update({
 | 
			
		||||
        name: data.name,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // 更新本地状态
 | 
			
		||||
      reloadAccessGroups();
 | 
			
		||||
 | 
			
		||||
      setOpen(false);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      const err = e as ClientResponseError;
 | 
			
		||||
 | 
			
		||||
      Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
 | 
			
		||||
        form.setError(key as keyof z.infer<typeof formSchema>, {
 | 
			
		||||
          type: "manual",
 | 
			
		||||
          message: value.message,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog onOpenChange={setOpen} open={open}>
 | 
			
		||||
      <DialogTrigger asChild className={cn(className)}>
 | 
			
		||||
        {trigger}
 | 
			
		||||
      </DialogTrigger>
 | 
			
		||||
      <DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
 | 
			
		||||
        <DialogHeader>
 | 
			
		||||
          <DialogTitle>{t("access.group.add")}</DialogTitle>
 | 
			
		||||
        </DialogHeader>
 | 
			
		||||
 | 
			
		||||
        <div className="container py-3">
 | 
			
		||||
          <Form {...form}>
 | 
			
		||||
            <form
 | 
			
		||||
              onSubmit={(e) => {
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                form.handleSubmit(onSubmit)(e);
 | 
			
		||||
              }}
 | 
			
		||||
              className="space-y-8"
 | 
			
		||||
            >
 | 
			
		||||
              <FormField
 | 
			
		||||
                control={form.control}
 | 
			
		||||
                name="name"
 | 
			
		||||
                render={({ field }) => (
 | 
			
		||||
                  <FormItem>
 | 
			
		||||
                    <FormLabel>{t("access.group.form.name.label")}</FormLabel>
 | 
			
		||||
                    <FormControl>
 | 
			
		||||
                      <Input placeholder={t("access.group.form.name.errmsg.empty")} {...field} type="text" />
 | 
			
		||||
                    </FormControl>
 | 
			
		||||
 | 
			
		||||
                    <FormMessage />
 | 
			
		||||
                  </FormItem>
 | 
			
		||||
                )}
 | 
			
		||||
              />
 | 
			
		||||
 | 
			
		||||
              <div className="flex justify-end">
 | 
			
		||||
                <Button type="submit">{t("common.save")}</Button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </form>
 | 
			
		||||
          </Form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AccessGroupEdit;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,171 +0,0 @@
 | 
			
		|||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Group } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import {
 | 
			
		||||
  AlertDialog,
 | 
			
		||||
  AlertDialogAction,
 | 
			
		||||
  AlertDialogCancel,
 | 
			
		||||
  AlertDialogContent,
 | 
			
		||||
  AlertDialogDescription,
 | 
			
		||||
  AlertDialogFooter,
 | 
			
		||||
  AlertDialogHeader,
 | 
			
		||||
  AlertDialogTitle,
 | 
			
		||||
  AlertDialogTrigger,
 | 
			
		||||
} from "@/components/ui/alert-dialog";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { ScrollArea } from "@/components/ui/scroll-area";
 | 
			
		||||
import { useToast } from "@/components/ui/use-toast";
 | 
			
		||||
import AccessGroupEdit from "./AccessGroupEdit";
 | 
			
		||||
import { accessProvidersMap } from "@/domain/access";
 | 
			
		||||
import { getErrMessage } from "@/lib/error";
 | 
			
		||||
import { useConfigContext } from "@/providers/config";
 | 
			
		||||
import { remove } from "@/repository/access_group";
 | 
			
		||||
 | 
			
		||||
const AccessGroupList = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    config: { accessGroups },
 | 
			
		||||
    reloadAccessGroups,
 | 
			
		||||
  } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const { toast } = useToast();
 | 
			
		||||
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const handleRemoveClick = async (id: string) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await remove(id);
 | 
			
		||||
      reloadAccessGroups();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      toast({
 | 
			
		||||
        title: t("common.delete.failed.message"),
 | 
			
		||||
        description: getErrMessage(e),
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleAddAccess = () => {
 | 
			
		||||
    navigate("/access");
 | 
			
		||||
  };
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="mt-10">
 | 
			
		||||
      <Show when={accessGroups.length == 0}>
 | 
			
		||||
        <>
 | 
			
		||||
          <div className="flex flex-col items-center mt-10">
 | 
			
		||||
            <span className="bg-orange-100 p-5 rounded-full">
 | 
			
		||||
              <Group size={40} className="text-primary" />
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <div className="text-center text-sm text-muted-foreground mt-3">{t("access.group.domains.nodata")}</div>
 | 
			
		||||
            <AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} className="mt-3" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </>
 | 
			
		||||
      </Show>
 | 
			
		||||
 | 
			
		||||
      <ScrollArea className="h-[75vh] overflow-hidden">
 | 
			
		||||
        <div className="flex gap-5 flex-wrap">
 | 
			
		||||
          {accessGroups.map((accessGroup) => (
 | 
			
		||||
            <Card className="w-full md:w-[350px]">
 | 
			
		||||
              <CardHeader>
 | 
			
		||||
                <CardTitle>{accessGroup.name}</CardTitle>
 | 
			
		||||
                <CardDescription>
 | 
			
		||||
                  {t("access.group.total", {
 | 
			
		||||
                    total: accessGroup.expand ? accessGroup.expand.access.length : 0,
 | 
			
		||||
                  })}
 | 
			
		||||
                </CardDescription>
 | 
			
		||||
              </CardHeader>
 | 
			
		||||
              <CardContent className="min-h-[180px]">
 | 
			
		||||
                {accessGroup.expand ? (
 | 
			
		||||
                  <>
 | 
			
		||||
                    {accessGroup.expand.access.slice(0, 3).map((access) => (
 | 
			
		||||
                      <div key={access.id} className="flex flex-col mb-3">
 | 
			
		||||
                        <div className="flex items-center">
 | 
			
		||||
                          <div className="">
 | 
			
		||||
                            <img src={accessProvidersMap.get(access.configType)!.icon} alt="provider" className="w-8 h-8"></img>
 | 
			
		||||
                          </div>
 | 
			
		||||
                          <div className="ml-3">
 | 
			
		||||
                            <div className="text-sm font-semibold text-gray-700 dark:text-gray-200">{access.name}</div>
 | 
			
		||||
                            <div className="text-xs text-muted-foreground">{accessProvidersMap.get(access.configType)!.name}</div>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    ))}
 | 
			
		||||
                  </>
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <>
 | 
			
		||||
                    <div className="flex text-gray-700 dark:text-gray-200 items-center">
 | 
			
		||||
                      <div>
 | 
			
		||||
                        <Group size={40} />
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div className="ml-2">{t("access.group.nodata")}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </>
 | 
			
		||||
                )}
 | 
			
		||||
              </CardContent>
 | 
			
		||||
              <CardFooter>
 | 
			
		||||
                <div className="flex justify-end w-full">
 | 
			
		||||
                  <Show when={accessGroup.expand && accessGroup.expand.access.length > 0 ? true : false}>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size="sm"
 | 
			
		||||
                        variant={"link"}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          navigate(`/access?accessGroupId=${accessGroup.id}&tab=access`, {
 | 
			
		||||
                            replace: true,
 | 
			
		||||
                          });
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        {t("access.group.domains")}
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Show>
 | 
			
		||||
 | 
			
		||||
                  <Show when={!accessGroup.expand || accessGroup.expand.access.length == 0 ? true : false}>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <Button size="sm" onClick={handleAddAccess}>
 | 
			
		||||
                        {t("access.authorization.add")}
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Show>
 | 
			
		||||
 | 
			
		||||
                  <div className="ml-3">
 | 
			
		||||
                    <AlertDialog>
 | 
			
		||||
                      <AlertDialogTrigger asChild>
 | 
			
		||||
                        <Button variant={"destructive"} size={"sm"}>
 | 
			
		||||
                          {t("common.delete")}
 | 
			
		||||
                        </Button>
 | 
			
		||||
                      </AlertDialogTrigger>
 | 
			
		||||
                      <AlertDialogContent>
 | 
			
		||||
                        <AlertDialogHeader>
 | 
			
		||||
                          <AlertDialogTitle className="dark:text-gray-200">{t("access.group.delete")}</AlertDialogTitle>
 | 
			
		||||
                          <AlertDialogDescription>{t("access.group.delete.confirm")}</AlertDialogDescription>
 | 
			
		||||
                        </AlertDialogHeader>
 | 
			
		||||
                        <AlertDialogFooter>
 | 
			
		||||
                          <AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
 | 
			
		||||
                          <AlertDialogAction
 | 
			
		||||
                            onClick={() => {
 | 
			
		||||
                              handleRemoveClick(accessGroup.id ? accessGroup.id : "");
 | 
			
		||||
                            }}
 | 
			
		||||
                          >
 | 
			
		||||
                            {t("common.confirm")}
 | 
			
		||||
                          </AlertDialogAction>
 | 
			
		||||
                        </AlertDialogFooter>
 | 
			
		||||
                      </AlertDialogContent>
 | 
			
		||||
                    </AlertDialog>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </CardFooter>
 | 
			
		||||
            </Card>
 | 
			
		||||
          ))}
 | 
			
		||||
        </div>
 | 
			
		||||
      </ScrollArea>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AccessGroupList;
 | 
			
		||||
| 
						 | 
				
			
			@ -3,16 +3,12 @@ import { useTranslation } from "react-i18next";
 | 
			
		|||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { Plus } from "lucide-react";
 | 
			
		||||
import { ClientResponseError } from "pocketbase";
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import AccessGroupEdit from "./AccessGroupEdit";
 | 
			
		||||
import { readFileContent } from "@/lib/file";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { PbErrorData } from "@/domain/base";
 | 
			
		||||
import { accessProvidersMap, accessTypeFormSchema, type Access, type SSHConfig } from "@/domain/access";
 | 
			
		||||
import { save } from "@/repository/access";
 | 
			
		||||
| 
						 | 
				
			
			@ -26,12 +22,7 @@ type AccessSSHFormProps = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
 | 
			
		||||
  const {
 | 
			
		||||
    addAccess,
 | 
			
		||||
    updateAccess,
 | 
			
		||||
    reloadAccessGroups,
 | 
			
		||||
    config: { accessGroups },
 | 
			
		||||
  } = useConfigContext();
 | 
			
		||||
  const { addAccess, updateAccess, reloadAccessGroups } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const fileInputRef = useRef<HTMLInputElement | null>(null);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -216,52 +207,6 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
 | 
			
		|||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="group"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel className="w-full flex justify-between">
 | 
			
		||||
                  <div>{t("access.authorization.form.ssh_group.label")}</div>
 | 
			
		||||
                  <AccessGroupEdit
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
                        {t("common.add")}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                </FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    defaultValue="emptyId"
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("group", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <SelectTrigger>
 | 
			
		||||
                      <SelectValue placeholder={t("access.authorization.form.access_group.placeholder")} />
 | 
			
		||||
                    </SelectTrigger>
 | 
			
		||||
                    <SelectContent>
 | 
			
		||||
                      <SelectItem value="emptyId">
 | 
			
		||||
                        <div className={cn("flex items-center space-x-2 rounded cursor-pointer")}>--</div>
 | 
			
		||||
                      </SelectItem>
 | 
			
		||||
                      {accessGroups.map((item) => (
 | 
			
		||||
                        <SelectItem value={item.id ? item.id : ""} key={item.id}>
 | 
			
		||||
                          <div className={cn("flex items-center space-x-2 rounded cursor-pointer")}>{item.name}</div>
 | 
			
		||||
                        </SelectItem>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectContent>
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="id"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
import { createContext, useContext, type Context as ReactContext } from "react";
 | 
			
		||||
 | 
			
		||||
import { type DeployConfig } from "@/domain/domain";
 | 
			
		||||
 | 
			
		||||
export type DeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]> = {
 | 
			
		||||
  config: Omit<DeployConfig, "config"> & { config: T };
 | 
			
		||||
  setConfig: (config: Omit<DeployConfig, "config"> & { config: T }) => void;
 | 
			
		||||
 | 
			
		||||
  errors: { [K in keyof T]?: string };
 | 
			
		||||
  setErrors: (error: { [K in keyof T]?: string }) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const Context = createContext<DeployEditContext>({} as DeployEditContext);
 | 
			
		||||
 | 
			
		||||
export function useDeployEditContext<T extends DeployConfig["config"] = DeployConfig["config"]>() {
 | 
			
		||||
  return useContext<DeployEditContext<T>>(Context as unknown as ReactContext<DeployEditContext<T>>);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,308 +0,0 @@
 | 
			
		|||
import { useCallback, useEffect, useState } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Plus } from "lucide-react";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { ScrollArea } from "@/components/ui/scroll-area";
 | 
			
		||||
import AccessEditDialog from "./AccessEditDialog";
 | 
			
		||||
import { Context as DeployEditContext, type DeployEditContext as DeployEditContextType } from "./DeployEdit";
 | 
			
		||||
import DeployToAliyunOSS from "./DeployToAliyunOSS";
 | 
			
		||||
import DeployToAliyunCDN from "./DeployToAliyunCDN";
 | 
			
		||||
import DeployToAliyunCLB from "./DeployToAliyunCLB";
 | 
			
		||||
import DeployToAliyunALB from "./DeployToAliyunALB";
 | 
			
		||||
import DeployToAliyunNLB from "./DeployToAliyunNLB";
 | 
			
		||||
import DeployToTencentCDN from "./DeployToTencentCDN";
 | 
			
		||||
import DeployToTencentCLB from "./DeployToTencentCLB";
 | 
			
		||||
import DeployToTencentCOS from "./DeployToTencentCOS";
 | 
			
		||||
import DeployToTencentTEO from "./DeployToTencentTEO";
 | 
			
		||||
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
 | 
			
		||||
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
 | 
			
		||||
import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN";
 | 
			
		||||
import DeployToQiniuCDN from "./DeployToQiniuCDN";
 | 
			
		||||
import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN";
 | 
			
		||||
import DeployToLocal from "./DeployToLocal";
 | 
			
		||||
import DeployToSSH from "./DeployToSSH";
 | 
			
		||||
import DeployToWebhook from "./DeployToWebhook";
 | 
			
		||||
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
 | 
			
		||||
import DeployToVolcengineLive from "./DeployToVolcengineLive";
 | 
			
		||||
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
 | 
			
		||||
import DeployToByteplusCDN from "./DeployToByteplusCDN";
 | 
			
		||||
import { deployTargetsMap, type DeployConfig } from "@/domain/domain";
 | 
			
		||||
import { accessProvidersMap } from "@/domain/access";
 | 
			
		||||
import { useConfigContext } from "@/providers/config";
 | 
			
		||||
 | 
			
		||||
type DeployEditDialogProps = {
 | 
			
		||||
  trigger: React.ReactNode;
 | 
			
		||||
  deployConfig?: DeployConfig;
 | 
			
		||||
  onSave: (deploy: DeployConfig) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    config: { accesses },
 | 
			
		||||
  } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const [deployType, setDeployType] = useState("");
 | 
			
		||||
 | 
			
		||||
  const [locDeployConfig, setLocDeployConfig] = useState<DeployConfig>({
 | 
			
		||||
    access: "",
 | 
			
		||||
    type: "",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [errors, setErrors] = useState<Record<string, string | undefined>>({});
 | 
			
		||||
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (deployConfig) {
 | 
			
		||||
      setLocDeployConfig({ ...deployConfig });
 | 
			
		||||
    } else {
 | 
			
		||||
      setLocDeployConfig({
 | 
			
		||||
        access: "",
 | 
			
		||||
        type: "",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [deployConfig]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setDeployType(locDeployConfig.type);
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, [locDeployConfig.type]);
 | 
			
		||||
 | 
			
		||||
  const setConfig = useCallback(
 | 
			
		||||
    (deploy: DeployConfig) => {
 | 
			
		||||
      if (deploy.type !== locDeployConfig.type) {
 | 
			
		||||
        setLocDeployConfig({ ...deploy, access: "", config: {} });
 | 
			
		||||
      } else {
 | 
			
		||||
        setLocDeployConfig({ ...deploy });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    [locDeployConfig.type]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const targetAccesses = accesses.filter((item) => {
 | 
			
		||||
    if (item.usage == "apply") {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (locDeployConfig.type == "") {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return item.configType === deployTargetsMap.get(locDeployConfig.type)?.provider;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const handleSaveClick = () => {
 | 
			
		||||
    // 验证数据
 | 
			
		||||
    const newError = { ...errors };
 | 
			
		||||
    newError.type = locDeployConfig.type === "" ? t("domain.deployment.form.access.placeholder") : "";
 | 
			
		||||
    newError.access = locDeployConfig.access === "" ? t("domain.deployment.form.access.placeholder") : "";
 | 
			
		||||
    setErrors(newError);
 | 
			
		||||
    if (Object.values(newError).some((e) => !!e)) return;
 | 
			
		||||
 | 
			
		||||
    // 保存数据
 | 
			
		||||
    onSave(locDeployConfig);
 | 
			
		||||
 | 
			
		||||
    // 清理数据
 | 
			
		||||
    setLocDeployConfig({
 | 
			
		||||
      access: "",
 | 
			
		||||
      type: "",
 | 
			
		||||
    });
 | 
			
		||||
    setErrors({});
 | 
			
		||||
 | 
			
		||||
    // 关闭弹框
 | 
			
		||||
    setOpen(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let childComponent = <></>;
 | 
			
		||||
  switch (deployType) {
 | 
			
		||||
    case "aliyun-oss":
 | 
			
		||||
      childComponent = <DeployToAliyunOSS />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "aliyun-cdn":
 | 
			
		||||
    case "aliyun-dcdn":
 | 
			
		||||
      childComponent = <DeployToAliyunCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "aliyun-clb":
 | 
			
		||||
      childComponent = <DeployToAliyunCLB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "aliyun-alb":
 | 
			
		||||
      childComponent = <DeployToAliyunALB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "aliyun-nlb":
 | 
			
		||||
      childComponent = <DeployToAliyunNLB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "tencent-cdn":
 | 
			
		||||
    case "tencent-ecdn":
 | 
			
		||||
      childComponent = <DeployToTencentCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "tencent-clb":
 | 
			
		||||
      childComponent = <DeployToTencentCLB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "tencent-cos":
 | 
			
		||||
      childComponent = <DeployToTencentCOS />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "tencent-teo":
 | 
			
		||||
      childComponent = <DeployToTencentTEO />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "huaweicloud-cdn":
 | 
			
		||||
      childComponent = <DeployToHuaweiCloudCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "huaweicloud-elb":
 | 
			
		||||
      childComponent = <DeployToHuaweiCloudELB />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "baiducloud-cdn":
 | 
			
		||||
      childComponent = <DeployToBaiduCloudCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "qiniu-cdn":
 | 
			
		||||
      childComponent = <DeployToQiniuCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "dogecloud-cdn":
 | 
			
		||||
      childComponent = <DeployToDogeCloudCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "local":
 | 
			
		||||
      childComponent = <DeployToLocal />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "ssh":
 | 
			
		||||
      childComponent = <DeployToSSH />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "webhook":
 | 
			
		||||
      childComponent = <DeployToWebhook />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "k8s-secret":
 | 
			
		||||
      childComponent = <DeployToKubernetesSecret />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "volcengine-live":
 | 
			
		||||
      childComponent = <DeployToVolcengineLive />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "volcengine-cdn":
 | 
			
		||||
      childComponent = <DeployToVolcengineCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
    case "byteplus-cdn":
 | 
			
		||||
      childComponent = <DeployToByteplusCDN />;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <DeployEditContext.Provider
 | 
			
		||||
      value={{
 | 
			
		||||
        config: locDeployConfig as DeployEditContextType["config"],
 | 
			
		||||
        setConfig: setConfig as DeployEditContextType["setConfig"],
 | 
			
		||||
        errors: errors as DeployEditContextType["errors"],
 | 
			
		||||
        setErrors: setErrors as DeployEditContextType["setErrors"],
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <Dialog open={open} onOpenChange={setOpen}>
 | 
			
		||||
        <DialogTrigger>{trigger}</DialogTrigger>
 | 
			
		||||
        <DialogContent
 | 
			
		||||
          className="dark:text-stone-200"
 | 
			
		||||
          onInteractOutside={(event) => {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <DialogHeader>
 | 
			
		||||
            <DialogTitle>{t("domain.deployment.tab")}</DialogTitle>
 | 
			
		||||
            <DialogDescription></DialogDescription>
 | 
			
		||||
          </DialogHeader>
 | 
			
		||||
 | 
			
		||||
          <ScrollArea className="max-h-[80vh]">
 | 
			
		||||
            <div className="container py-3">
 | 
			
		||||
              {/* 部署方式 */}
 | 
			
		||||
              <div>
 | 
			
		||||
                <Label>{t("domain.deployment.form.type.label")}</Label>
 | 
			
		||||
 | 
			
		||||
                <Select
 | 
			
		||||
                  value={locDeployConfig.type}
 | 
			
		||||
                  onValueChange={(val: string) => {
 | 
			
		||||
                    setConfig({ ...locDeployConfig, type: val });
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <SelectTrigger className="mt-2">
 | 
			
		||||
                    <SelectValue placeholder={t("domain.deployment.form.type.placeholder")} />
 | 
			
		||||
                  </SelectTrigger>
 | 
			
		||||
                  <SelectContent>
 | 
			
		||||
                    <SelectGroup>
 | 
			
		||||
                      <SelectLabel>{t("domain.deployment.form.type.list")}</SelectLabel>
 | 
			
		||||
                      {Array.from(deployTargetsMap.entries()).map(([key, target]) => (
 | 
			
		||||
                        <SelectItem key={key} value={key}>
 | 
			
		||||
                          <div className="flex items-center space-x-2">
 | 
			
		||||
                            <img className="w-6" src={target.icon} />
 | 
			
		||||
                            <div>{t(target.name)}</div>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </SelectItem>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectGroup>
 | 
			
		||||
                  </SelectContent>
 | 
			
		||||
                </Select>
 | 
			
		||||
 | 
			
		||||
                <div className="text-red-500 text-sm mt-1">{errors.type}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              {/* 授权配置 */}
 | 
			
		||||
              <div className="mt-8">
 | 
			
		||||
                <Label className="flex justify-between">
 | 
			
		||||
                  <div>{t("domain.deployment.form.access.label")}</div>
 | 
			
		||||
                  <AccessEditDialog
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
                        {t("common.add")}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                    op="add"
 | 
			
		||||
                  />
 | 
			
		||||
                </Label>
 | 
			
		||||
 | 
			
		||||
                <Select
 | 
			
		||||
                  value={locDeployConfig.access}
 | 
			
		||||
                  onValueChange={(val: string) => {
 | 
			
		||||
                    setConfig({ ...locDeployConfig, access: val });
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <SelectTrigger className="mt-2">
 | 
			
		||||
                    <SelectValue placeholder={t("domain.deployment.form.access.placeholder")} />
 | 
			
		||||
                  </SelectTrigger>
 | 
			
		||||
                  <SelectContent>
 | 
			
		||||
                    <SelectGroup>
 | 
			
		||||
                      <SelectLabel>{t("domain.deployment.form.access.list")}</SelectLabel>
 | 
			
		||||
                      {targetAccesses.map((item) => (
 | 
			
		||||
                        <SelectItem key={item.id} value={item.id}>
 | 
			
		||||
                          <div className="flex items-center space-x-2">
 | 
			
		||||
                            <img className="w-6" src={accessProvidersMap.get(item.configType)?.icon} />
 | 
			
		||||
                            <div>{item.name}</div>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </SelectItem>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectGroup>
 | 
			
		||||
                  </SelectContent>
 | 
			
		||||
                </Select>
 | 
			
		||||
 | 
			
		||||
                <div className="text-red-500 text-sm mt-1">{errors.access}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              {/* 其他参数 */}
 | 
			
		||||
              <div className="mt-8">{childComponent}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </ScrollArea>
 | 
			
		||||
 | 
			
		||||
          <DialogFooter>
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={(e) => {
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                handleSaveClick();
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {t("common.save")}
 | 
			
		||||
            </Button>
 | 
			
		||||
          </DialogFooter>
 | 
			
		||||
        </DialogContent>
 | 
			
		||||
      </Dialog>
 | 
			
		||||
    </DeployEditContext.Provider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployEditDialog;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,169 +0,0 @@
 | 
			
		|||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { nanoid } from "nanoid";
 | 
			
		||||
import { EditIcon, Trash2 } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import { Alert, AlertDescription } from "@/components/ui/alert";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import DeployEditDialog from "./DeployEditDialog";
 | 
			
		||||
import { DeployConfig } from "@/domain/domain";
 | 
			
		||||
import { accessProvidersMap } from "@/domain/access";
 | 
			
		||||
import { deployTargetsMap } from "@/domain/domain";
 | 
			
		||||
import { useConfigContext } from "@/providers/config";
 | 
			
		||||
 | 
			
		||||
type DeployItemProps = {
 | 
			
		||||
  item: DeployConfig;
 | 
			
		||||
  onDelete: () => void;
 | 
			
		||||
  onSave: (deploy: DeployConfig) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    config: { accesses },
 | 
			
		||||
  } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const access = accesses.find((access) => access.id === item.access);
 | 
			
		||||
 | 
			
		||||
  const getTypeIcon = () => {
 | 
			
		||||
    if (!access) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return accessProvidersMap.get(access.configType)?.icon || "";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getTypeName = () => {
 | 
			
		||||
    return t(deployTargetsMap.get(item.type)?.name || "");
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex justify-between text-sm p-3 items-center text-stone-700 dark:text-stone-200">
 | 
			
		||||
      <div className="flex space-x-2 items-center">
 | 
			
		||||
        <div>
 | 
			
		||||
          <img src={getTypeIcon()} className="w-9"></img>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="text-stone-600 flex-col flex space-y-0 dark:text-stone-200">
 | 
			
		||||
          <div>{getTypeName()}</div>
 | 
			
		||||
          <div>{access?.name}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="flex space-x-2">
 | 
			
		||||
        <DeployEditDialog
 | 
			
		||||
          trigger={<EditIcon size={16} className="cursor-pointer" />}
 | 
			
		||||
          deployConfig={item}
 | 
			
		||||
          onSave={(deploy: DeployConfig) => {
 | 
			
		||||
            onSave(deploy);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <Trash2
 | 
			
		||||
          size={16}
 | 
			
		||||
          className="cursor-pointer"
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            onDelete();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type DeployListProps = {
 | 
			
		||||
  deploys: DeployConfig[];
 | 
			
		||||
  onChange: (deploys: DeployConfig[]) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployList = ({ deploys, onChange }: DeployListProps) => {
 | 
			
		||||
  const [list, setList] = useState<DeployConfig[]>([]);
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setList(deploys);
 | 
			
		||||
  }, [deploys]);
 | 
			
		||||
 | 
			
		||||
  const handleAdd = (deploy: DeployConfig) => {
 | 
			
		||||
    deploy.id = nanoid();
 | 
			
		||||
 | 
			
		||||
    const newList = [...list, deploy];
 | 
			
		||||
 | 
			
		||||
    setList(newList);
 | 
			
		||||
 | 
			
		||||
    onChange(newList);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleDelete = (id: string) => {
 | 
			
		||||
    const newList = list.filter((item) => item.id !== id);
 | 
			
		||||
 | 
			
		||||
    setList(newList);
 | 
			
		||||
 | 
			
		||||
    onChange(newList);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSave = (deploy: DeployConfig) => {
 | 
			
		||||
    const newList = list.map((item) => {
 | 
			
		||||
      if (item.id === deploy.id) {
 | 
			
		||||
        return { ...deploy };
 | 
			
		||||
      }
 | 
			
		||||
      return item;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    setList(newList);
 | 
			
		||||
 | 
			
		||||
    onChange(newList);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Show
 | 
			
		||||
        when={list.length > 0}
 | 
			
		||||
        fallback={
 | 
			
		||||
          <Alert className="w-full border dark:border-stone-400">
 | 
			
		||||
            <AlertDescription className="flex flex-col items-center">
 | 
			
		||||
              <div>{t("domain.deployment.nodata")}</div>
 | 
			
		||||
              <div className="flex justify-end mt-2">
 | 
			
		||||
                <DeployEditDialog
 | 
			
		||||
                  onSave={(config: DeployConfig) => {
 | 
			
		||||
                    handleAdd(config);
 | 
			
		||||
                  }}
 | 
			
		||||
                  trigger={<Button size={"sm"}>{t("common.add")}</Button>}
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </AlertDescription>
 | 
			
		||||
          </Alert>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <div className="flex justify-end py-2 border-b dark:border-stone-400">
 | 
			
		||||
          <DeployEditDialog
 | 
			
		||||
            trigger={<Button size={"sm"}>{t("common.add")}</Button>}
 | 
			
		||||
            onSave={(config: DeployConfig) => {
 | 
			
		||||
              handleAdd(config);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className="w-full md:w-[35em] rounded mt-5 border dark:border-stone-400 dark:text-stone-200">
 | 
			
		||||
          <div className="">
 | 
			
		||||
            {list.map((item) => (
 | 
			
		||||
              <DeployItem
 | 
			
		||||
                key={item.id}
 | 
			
		||||
                item={item}
 | 
			
		||||
                onDelete={() => {
 | 
			
		||||
                  handleDelete(item.id ?? "");
 | 
			
		||||
                }}
 | 
			
		||||
                onSave={(deploy: DeployConfig) => {
 | 
			
		||||
                  handleSave(deploy);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Show>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployList;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,55 +0,0 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
 | 
			
		||||
import { Separator } from "@/components/ui/separator";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
 | 
			
		||||
type DeployProgressProps = {
 | 
			
		||||
  phase?: "check" | "apply" | "deploy";
 | 
			
		||||
  phaseSuccess?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  let step = 0;
 | 
			
		||||
 | 
			
		||||
  if (phase === "check") {
 | 
			
		||||
    step = 1;
 | 
			
		||||
  } else if (phase === "apply") {
 | 
			
		||||
    step = 2;
 | 
			
		||||
  } else if (phase === "deploy") {
 | 
			
		||||
    step = 3;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex items-center">
 | 
			
		||||
      <div className={cn("text-xs text-nowrap", step === 1 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "", step > 1 ? "text-green-600" : "")}>
 | 
			
		||||
        {t("history.props.stage.progress.check")}
 | 
			
		||||
      </div>
 | 
			
		||||
      <Separator className={cn("h-1 grow max-w-[60px]", step > 1 ? "bg-green-600" : "")} />
 | 
			
		||||
      <div
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "text-xs text-nowrap",
 | 
			
		||||
          step < 2 ? "text-muted-foreground" : "",
 | 
			
		||||
          step === 2 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
 | 
			
		||||
          step > 2 ? "text-green-600" : ""
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {t("history.props.stage.progress.apply")}
 | 
			
		||||
      </div>
 | 
			
		||||
      <Separator className={cn("h-1 grow max-w-[60px]", step > 2 ? "bg-green-600" : "")} />
 | 
			
		||||
      <div
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "text-xs text-nowrap",
 | 
			
		||||
          step < 3 ? "text-muted-foreground" : "",
 | 
			
		||||
          step === 3 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
 | 
			
		||||
          step > 3 ? "text-green-600" : ""
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        {t("history.props.stage.progress.deploy")}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployProgress;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,43 +0,0 @@
 | 
			
		|||
import { CircleCheck, CircleX } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 | 
			
		||||
import { Deployment } from "@/domain/deployment";
 | 
			
		||||
 | 
			
		||||
type DeployStateProps = {
 | 
			
		||||
  deployment: Deployment;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployState = ({ deployment }: DeployStateProps) => {
 | 
			
		||||
  // 获取指定阶段的错误信息
 | 
			
		||||
  const error = (state: "check" | "apply" | "deploy") => {
 | 
			
		||||
    if (!deployment.log[state]) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
    return deployment.log[state][deployment.log[state].length - 1].error;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {(deployment.phase === "deploy" && deployment.phaseSuccess) || deployment.wholeSuccess ? (
 | 
			
		||||
        <CircleCheck size={16} className="text-green-700" />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <>
 | 
			
		||||
          {error(deployment.phase).length ? (
 | 
			
		||||
            <TooltipProvider>
 | 
			
		||||
              <Tooltip>
 | 
			
		||||
                <TooltipTrigger asChild className="cursor-pointer">
 | 
			
		||||
                  <CircleX size={16} className="text-red-700" />
 | 
			
		||||
                </TooltipTrigger>
 | 
			
		||||
                <TooltipContent className="max-w-[35em]">{error(deployment.phase)}</TooltipContent>
 | 
			
		||||
              </Tooltip>
 | 
			
		||||
            </TooltipProvider>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <CircleX size={16} className="text-red-700" />
 | 
			
		||||
          )}
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployState;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,156 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToAliyunALBConfigParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  resourceType?: string;
 | 
			
		||||
  loadbalancerId?: string;
 | 
			
		||||
  listenerId?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunALB = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunALBConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-hangzhou",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.aliyun_alb_region.placeholder")),
 | 
			
		||||
      resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], {
 | 
			
		||||
        message: t("domain.deployment.form.aliyun_alb_resource_type.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      loadbalancerId: z.string().optional(),
 | 
			
		||||
      listenerId: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder"),
 | 
			
		||||
      path: ["loadbalancerId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_alb_listener_id.placeholder"),
 | 
			
		||||
      path: ["listenerId"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
      loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
      listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_alb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_alb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_alb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={config?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.aliyun_alb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_alb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.aliyun_alb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "loadbalancer" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.aliyun_alb_loadbalancer_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.loadbalancerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "listener" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.aliyun_alb_listener_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.aliyun_alb_listener_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.listenerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToAliyunALB;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToAliyunCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToAliyunCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,153 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToAliyunCLBConfigParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  resourceType?: string;
 | 
			
		||||
  loadbalancerId?: string;
 | 
			
		||||
  listenerPort?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunCLB = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunCLBConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-hangzhou",
 | 
			
		||||
          listenerPort: "443",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.aliyun_clb_region.placeholder")),
 | 
			
		||||
      resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], {
 | 
			
		||||
        message: t("domain.deployment.form.aliyun_clb_resource_type.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      loadbalancerId: z.string().optional(),
 | 
			
		||||
      listenerPort: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "loadbalancer" || data.resourceType === "listener" ? !!data.loadbalancerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder"),
 | 
			
		||||
      path: ["loadbalancerId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "listener" ? +data.listenerPort! > 0 && +data.listenerPort! < 65535 : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_clb_listener_port.placeholder"),
 | 
			
		||||
      path: ["listenerPort"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
      loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
      listenerPort: res.error?.errors?.find((e) => e.path[0] === "listenerPort")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_clb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_clb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_clb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={config?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value;
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.aliyun_clb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_clb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.aliyun_clb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_clb_loadbalancer_id.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.loadbalancerId}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "listener" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.aliyun_clb_listener_port.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.aliyun_clb_listener_port.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.listenerPort}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerPort = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.listenerPort}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToAliyunCLB;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,156 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToAliyunNLBConfigParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  resourceType?: string;
 | 
			
		||||
  loadbalancerId?: string;
 | 
			
		||||
  listenerId?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunNLB = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunNLBConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-hangzhou",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.aliyun_nlb_region.placeholder")),
 | 
			
		||||
      resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], {
 | 
			
		||||
        message: t("domain.deployment.form.aliyun_nlb_resource_type.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      loadbalancerId: z.string().optional(),
 | 
			
		||||
      listenerId: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder"),
 | 
			
		||||
      path: ["loadbalancerId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_nlb_listener_id.placeholder"),
 | 
			
		||||
      path: ["listenerId"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
      loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
      listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_nlb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_nlb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_nlb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={config?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value;
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.aliyun_nlb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.aliyun_nlb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "loadbalancer" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.aliyun_nlb_loadbalancer_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.loadbalancerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "listener" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.aliyun_nlb_listener_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.aliyun_nlb_listener_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.listenerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToAliyunNLB;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,114 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToAliyunOSSConfigParams = {
 | 
			
		||||
  endpoint?: string;
 | 
			
		||||
  bucket?: string;
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToAliyunOSS = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToAliyunOSSConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          endpoint: "oss.aliyuncs.com",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    endpoint: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_oss_endpoint.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    bucket: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.aliyun_oss_bucket.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      endpoint: res.error?.errors?.find((e) => e.path[0] === "endpoint")?.message,
 | 
			
		||||
      bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_oss_endpoint.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_oss_endpoint.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.endpoint}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.endpoint = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.endpoint}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.aliyun_oss_bucket.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.aliyun_oss_bucket.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.bucket}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.bucket = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.label")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToAliyunOSS;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToBaiduCloudCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToBaiduCloudCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToBaiduCloudCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToBaiduCloudCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToByteplusCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToByteplusCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToByteplusCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToByteplusCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToDogeCloudCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToDogeCloudCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToDogeCloudCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToDogeCloudCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,92 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToHuaweiCloudCDNConfigParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToHuaweiCloudCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-north-1",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    region: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_cdn_region.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_cdn_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.huaweicloud_cdn_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToHuaweiCloudCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,185 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToHuaweiCloudELBConfigParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  resourceType?: string;
 | 
			
		||||
  certificateId?: string;
 | 
			
		||||
  loadbalancerId?: string;
 | 
			
		||||
  listenerId?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToHuaweiCloudELB = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToHuaweiCloudELBConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "cn-north-1",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.huaweicloud_elb_region.placeholder")),
 | 
			
		||||
      resourceType: z.union([z.literal("certificate"), z.literal("loadbalancer"), z.literal("listener")], {
 | 
			
		||||
        message: t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      certificateId: z.string().optional(),
 | 
			
		||||
      loadbalancerId: z.string().optional(),
 | 
			
		||||
      listenerId: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "certificate" ? !!data.certificateId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder"),
 | 
			
		||||
      path: ["certificateId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder"),
 | 
			
		||||
      path: ["loadbalancerId"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder"),
 | 
			
		||||
      path: ["listenerId"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
      certificateId: res.error?.errors?.find((e) => e.path[0] === "certificateId")?.message,
 | 
			
		||||
      loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
      listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_elb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.huaweicloud_elb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.huaweicloud_elb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={config?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value;
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.huaweicloud_elb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="certificate">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.certificate.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.huaweicloud_elb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "certificate" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_certificate_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_certificate_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.certificateId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.certificateId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.certificateId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "loadbalancer" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_loadbalancer_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.loadbalancerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "listener" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.huaweicloud_elb_listener_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.huaweicloud_elb_listener_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.listenerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToHuaweiCloudELB;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,136 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToKubernetesSecretConfigParams = {
 | 
			
		||||
  namespace?: string;
 | 
			
		||||
  secretName?: string;
 | 
			
		||||
  secretDataKeyForCrt?: string;
 | 
			
		||||
  secretDataKeyForKey?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToKubernetesSecret = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToKubernetesSecretConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          namespace: "default",
 | 
			
		||||
          secretDataKeyForCrt: "tls.crt",
 | 
			
		||||
          secretDataKeyForKey: "tls.key",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    namespace: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.k8s_namespace.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    secretName: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.k8s_secret_name.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    secretDataKeyForCrt: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.k8s_secret_data_key_for_crt.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    secretDataKeyForKey: z.string().min(1, {
 | 
			
		||||
      message: t("domain.deployment.form.k8s_secret_data_key_for_key.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      namespace: res.error?.errors?.find((e) => e.path[0] === "namespace")?.message,
 | 
			
		||||
      secretName: res.error?.errors?.find((e) => e.path[0] === "secretName")?.message,
 | 
			
		||||
      secretDataKeyForCrt: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForCrt")?.message,
 | 
			
		||||
      secretDataKeyForKey: res.error?.errors?.find((e) => e.path[0] === "secretDataKeyForKey")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex flex-col space-y-8">
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.k8s_namespace.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.k8s_namespace.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.namespace}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.namespace = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.k8s_secret_name.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.k8s_secret_name.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.secretName}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.secretName = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.k8s_secret_data_key_for_crt.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.secretDataKeyForCrt}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.secretDataKeyForCrt = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.k8s_secret_data_key_for_key.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.k8s_secret_data_key_for_key.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.secretDataKeyForKey}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.secretDataKeyForKey = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToKubernetesSecret;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,481 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { Textarea } from "@/components/ui/textarea";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
 | 
			
		||||
type DeployToLocalConfigParams = {
 | 
			
		||||
  format?: string;
 | 
			
		||||
  certPath?: string;
 | 
			
		||||
  keyPath?: string;
 | 
			
		||||
  pfxPassword?: string;
 | 
			
		||||
  jksAlias?: string;
 | 
			
		||||
  jksKeypass?: string;
 | 
			
		||||
  jksStorepass?: string;
 | 
			
		||||
  shell?: string;
 | 
			
		||||
  preCommand?: string;
 | 
			
		||||
  command?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToLocal = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToLocalConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          format: "pem",
 | 
			
		||||
          certPath: "/etc/nginx/ssl/nginx.crt",
 | 
			
		||||
          keyPath: "/etc/nginx/ssl/nginx.key",
 | 
			
		||||
          shell: "sh",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], {
 | 
			
		||||
        message: t("domain.deployment.form.file_format.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      certPath: z
 | 
			
		||||
        .string()
 | 
			
		||||
        .min(1, t("domain.deployment.form.file_cert_path.placeholder"))
 | 
			
		||||
        .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      keyPath: z
 | 
			
		||||
        .string()
 | 
			
		||||
        .min(0, t("domain.deployment.form.file_key_path.placeholder"))
 | 
			
		||||
        .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      pfxPassword: z.string().optional(),
 | 
			
		||||
      jksAlias: z.string().optional(),
 | 
			
		||||
      jksKeypass: z.string().optional(),
 | 
			
		||||
      jksStorepass: z.string().optional(),
 | 
			
		||||
      shell: z.union([z.literal("sh"), z.literal("cmd"), z.literal("powershell")], {
 | 
			
		||||
        message: t("domain.deployment.form.shell.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      preCommand: z.string().optional(),
 | 
			
		||||
      command: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_key_path.placeholder"),
 | 
			
		||||
      path: ["keyPath"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "pfx" ? !!data.pfxPassword?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_pfx_password.placeholder"),
 | 
			
		||||
      path: ["pfxPassword"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksAlias?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_alias.placeholder"),
 | 
			
		||||
      path: ["jksAlias"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksKeypass?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_keypass.placeholder"),
 | 
			
		||||
      path: ["jksKeypass"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksStorepass?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_storepass.placeholder"),
 | 
			
		||||
      path: ["jksStorepass"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
 | 
			
		||||
      certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
 | 
			
		||||
      keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
 | 
			
		||||
      pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
 | 
			
		||||
      jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
 | 
			
		||||
      jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
 | 
			
		||||
      jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
 | 
			
		||||
      shell: res.error?.errors?.find((e) => e.path[0] === "shell")?.message,
 | 
			
		||||
      preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
 | 
			
		||||
      command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (config.config?.format === "pem") {
 | 
			
		||||
      if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else if (config.config?.format === "pfx") {
 | 
			
		||||
      if (/(.crt|.jks)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else if (config.config?.format === "jks") {
 | 
			
		||||
      if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [config.config?.format]);
 | 
			
		||||
 | 
			
		||||
  const getOptionCls = (val: string) => {
 | 
			
		||||
    if (config.config?.shell === val) {
 | 
			
		||||
      return "border-primary dark:border-primary";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleUsePresetScript = (key: string) => {
 | 
			
		||||
    switch (key) {
 | 
			
		||||
      case "reload_nginx":
 | 
			
		||||
        {
 | 
			
		||||
          setConfig(
 | 
			
		||||
            produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.shell = "sh";
 | 
			
		||||
              draft.config.command = "sudo service nginx reload";
 | 
			
		||||
            })
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "binding_iis":
 | 
			
		||||
        {
 | 
			
		||||
          setConfig(
 | 
			
		||||
            produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.shell = "powershell";
 | 
			
		||||
              draft.config.command = `
 | 
			
		||||
# 请将以下变量替换为实际值
 | 
			
		||||
$pfxPath = "<your-pfx-path>" # PFX 文件路径
 | 
			
		||||
$pfxPassword = "<your-pfx-password>" # PFX 密码
 | 
			
		||||
$siteName = "<your-site-name>" # IIS 网站名称
 | 
			
		||||
$domain = "<your-domain-name>" # 域名
 | 
			
		||||
$ipaddr = "<your-binding-ip>"  # 绑定 IP,“*”表示所有 IP 绑定
 | 
			
		||||
$port = "<your-binding-port>"  # 绑定端口
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 导入证书到本地计算机的个人存储区
 | 
			
		||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
 | 
			
		||||
# 获取 Thumbprint
 | 
			
		||||
$thumbprint = $cert.Thumbprint
 | 
			
		||||
# 导入 WebAdministration 模块
 | 
			
		||||
Import-Module WebAdministration
 | 
			
		||||
# 检查是否已存在 HTTPS 绑定
 | 
			
		||||
$existingBinding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -HostHeader "$domain" -ErrorAction SilentlyContinue
 | 
			
		||||
if (!$existingBinding) {
 | 
			
		||||
    # 添加新的 HTTPS 绑定
 | 
			
		||||
  New-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
 | 
			
		||||
}
 | 
			
		||||
# 获取绑定对象
 | 
			
		||||
$binding = Get-WebBinding -Name "$siteName" -Protocol "https" -Port $port -IPAddress "$ipaddr" -HostHeader "$domain"
 | 
			
		||||
# 绑定 SSL 证书
 | 
			
		||||
$binding.AddSslCertificate($thumbprint, "My")
 | 
			
		||||
# 删除目录下的证书文件
 | 
			
		||||
Remove-Item -Path "$pfxPath" -Force
 | 
			
		||||
            `.trim();
 | 
			
		||||
            })
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case "binding_netsh":
 | 
			
		||||
        {
 | 
			
		||||
          setConfig(
 | 
			
		||||
            produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.shell = "powershell";
 | 
			
		||||
              draft.config.command = `
 | 
			
		||||
# 请将以下变量替换为实际值
 | 
			
		||||
$pfxPath = "<your-pfx-path>" # PFX 文件路径
 | 
			
		||||
$pfxPassword = "<your-pfx-password>" # PFX 密码
 | 
			
		||||
$ipaddr = "<your-binding-ip>"  # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名。
 | 
			
		||||
$port = "<your-binding-port>"  # 绑定端口
 | 
			
		||||
 | 
			
		||||
$addr = $ipaddr + ":" + $port
 | 
			
		||||
 | 
			
		||||
# 导入证书到本地计算机的个人存储区
 | 
			
		||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
 | 
			
		||||
# 获取 Thumbprint
 | 
			
		||||
$thumbprint = $cert.Thumbprint
 | 
			
		||||
# 检测端口是否绑定证书,如绑定则删除绑定
 | 
			
		||||
$isExist = netsh http show sslcert ipport=$addr
 | 
			
		||||
if ($isExist -like "*$addr*"){ netsh http delete sslcert ipport=$addr }
 | 
			
		||||
# 绑定到端口
 | 
			
		||||
netsh http add sslcert ipport=$addr certhash=$thumbprint
 | 
			
		||||
# 删除目录下的证书文件
 | 
			
		||||
Remove-Item -Path "$pfxPath" -Force
 | 
			
		||||
            `.trim();
 | 
			
		||||
            })
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex flex-col space-y-8">
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.file_format.label")}</Label>
 | 
			
		||||
          <Select
 | 
			
		||||
            value={config?.config?.format}
 | 
			
		||||
            onValueChange={(value) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.format = value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <SelectTrigger>
 | 
			
		||||
              <SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
 | 
			
		||||
            </SelectTrigger>
 | 
			
		||||
            <SelectContent>
 | 
			
		||||
              <SelectGroup>
 | 
			
		||||
                <SelectItem value="pem">PEM</SelectItem>
 | 
			
		||||
                <SelectItem value="pfx">PFX</SelectItem>
 | 
			
		||||
                <SelectItem value="jks">JKS</SelectItem>
 | 
			
		||||
              </SelectGroup>
 | 
			
		||||
            </SelectContent>
 | 
			
		||||
          </Select>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.format}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.file_cert_path.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.certPath}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.certPath = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "pem" ? (
 | 
			
		||||
          <div>
 | 
			
		||||
            <Label>{t("domain.deployment.form.file_key_path.label")}</Label>
 | 
			
		||||
            <Input
 | 
			
		||||
              placeholder={t("domain.deployment.form.file_key_path.placeholder")}
 | 
			
		||||
              className="w-full mt-1"
 | 
			
		||||
              value={config?.config?.keyPath}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                const nv = produce(config, (draft) => {
 | 
			
		||||
                  draft.config ??= {};
 | 
			
		||||
                  draft.config.keyPath = e.target.value?.trim();
 | 
			
		||||
                });
 | 
			
		||||
                setConfig(nv);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "pfx" ? (
 | 
			
		||||
          <div>
 | 
			
		||||
            <Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
 | 
			
		||||
            <Input
 | 
			
		||||
              placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
 | 
			
		||||
              className="w-full mt-1"
 | 
			
		||||
              value={config?.config?.pfxPassword}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                const nv = produce(config, (draft) => {
 | 
			
		||||
                  draft.config ??= {};
 | 
			
		||||
                  draft.config.pfxPassword = e.target.value?.trim();
 | 
			
		||||
                });
 | 
			
		||||
                setConfig(nv);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "jks" ? (
 | 
			
		||||
          <>
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksAlias}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksAlias = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_keypass.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksKeypass}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksKeypass = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_storepass.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksStorepass}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksStorepass = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.shell.label")}</Label>
 | 
			
		||||
          <RadioGroup
 | 
			
		||||
            className="flex mt-1"
 | 
			
		||||
            value={config?.config?.shell}
 | 
			
		||||
            onValueChange={(val) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.shell = val;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <div className="flex items-center space-x-2">
 | 
			
		||||
              <RadioGroupItem value="sh" id="shellOptionSh" />
 | 
			
		||||
              <Label htmlFor="shellOptionSh">
 | 
			
		||||
                <div className={cn("flex items-center space-x-2 border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("sh"))}>
 | 
			
		||||
                  <div>POSIX Bash (Linux)</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </Label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="flex items-center space-x-2">
 | 
			
		||||
              <RadioGroupItem value="cmd" id="shellOptionCmd" />
 | 
			
		||||
              <Label htmlFor="shellOptionCmd">
 | 
			
		||||
                <div className={cn("border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("cmd"))}>
 | 
			
		||||
                  <div>CMD (Windows)</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </Label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="flex items-center space-x-2">
 | 
			
		||||
              <RadioGroupItem value="powershell" id="shellOptionPowerShell" />
 | 
			
		||||
              <Label htmlFor="shellOptionPowerShell">
 | 
			
		||||
                <div className={cn("border p-2 rounded cursor-pointer dark:border-stone-700", getOptionCls("powershell"))}>
 | 
			
		||||
                  <div>PowerShell (Windows)</div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </Label>
 | 
			
		||||
            </div>
 | 
			
		||||
          </RadioGroup>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.shell}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
 | 
			
		||||
          <Textarea
 | 
			
		||||
            className="mt-1"
 | 
			
		||||
            value={config?.config?.preCommand}
 | 
			
		||||
            placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.preCommand = e.target.value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          ></Textarea>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <div className="flex items-center justify-between">
 | 
			
		||||
            <Label>{t("domain.deployment.form.shell_command.label")}</Label>
 | 
			
		||||
            <DropdownMenu>
 | 
			
		||||
              <DropdownMenuTrigger asChild>
 | 
			
		||||
                <a className="text-xs text-blue-500 cursor-pointer">{t("domain.deployment.form.shell_preset_scripts.trigger")}</a>
 | 
			
		||||
              </DropdownMenuTrigger>
 | 
			
		||||
              <DropdownMenuContent>
 | 
			
		||||
                <DropdownMenuItem onClick={() => handleUsePresetScript("reload_nginx")}>
 | 
			
		||||
                  {t("domain.deployment.form.shell_preset_scripts.option.reload_nginx.label")}
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
                <DropdownMenuItem onClick={() => handleUsePresetScript("binding_iis")}>
 | 
			
		||||
                  {t("domain.deployment.form.shell_preset_scripts.option.binding_iis.label")}
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
                <DropdownMenuItem onClick={() => handleUsePresetScript("binding_netsh")}>
 | 
			
		||||
                  {t("domain.deployment.form.shell_preset_scripts.option.binding_netsh.label")}
 | 
			
		||||
                </DropdownMenuItem>
 | 
			
		||||
              </DropdownMenuContent>
 | 
			
		||||
            </DropdownMenu>
 | 
			
		||||
          </div>
 | 
			
		||||
          <Textarea
 | 
			
		||||
            className="mt-1"
 | 
			
		||||
            value={config?.config?.command}
 | 
			
		||||
            placeholder={t("domain.deployment.form.shell_command.placeholder")}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.command = e.target.value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          ></Textarea>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.command}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToLocal;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToQiniuCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToQiniuCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToQiniuCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToQiniuCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,319 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { Textarea } from "@/components/ui/textarea";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToSSHConfigParams = {
 | 
			
		||||
  format?: string;
 | 
			
		||||
  certPath?: string;
 | 
			
		||||
  keyPath?: string;
 | 
			
		||||
  pfxPassword?: string;
 | 
			
		||||
  jksAlias?: string;
 | 
			
		||||
  jksKeypass?: string;
 | 
			
		||||
  jksStorepass?: string;
 | 
			
		||||
  shell?: string;
 | 
			
		||||
  preCommand?: string;
 | 
			
		||||
  command?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToSSH = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToSSHConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          format: "pem",
 | 
			
		||||
          certPath: "/etc/nginx/ssl/nginx.crt",
 | 
			
		||||
          keyPath: "/etc/nginx/ssl/nginx.key",
 | 
			
		||||
          command: "sudo service nginx reload",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      format: z.union([z.literal("pem"), z.literal("pfx"), z.literal("jks")], {
 | 
			
		||||
        message: t("domain.deployment.form.file_format.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      certPath: z
 | 
			
		||||
        .string()
 | 
			
		||||
        .min(1, t("domain.deployment.form.file_cert_path.placeholder"))
 | 
			
		||||
        .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      keyPath: z
 | 
			
		||||
        .string()
 | 
			
		||||
        .min(0, t("domain.deployment.form.file_key_path.placeholder"))
 | 
			
		||||
        .max(255, t("common.errmsg.string_max", { max: 255 })),
 | 
			
		||||
      pfxPassword: z.string().optional(),
 | 
			
		||||
      jksAlias: z.string().optional(),
 | 
			
		||||
      jksKeypass: z.string().optional(),
 | 
			
		||||
      jksStorepass: z.string().optional(),
 | 
			
		||||
      preCommand: z.string().optional(),
 | 
			
		||||
      command: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "pem" ? !!data.keyPath?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_key_path.placeholder"),
 | 
			
		||||
      path: ["keyPath"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "pfx" ? !!data.pfxPassword?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_pfx_password.placeholder"),
 | 
			
		||||
      path: ["pfxPassword"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksAlias?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_alias.placeholder"),
 | 
			
		||||
      path: ["jksAlias"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksKeypass?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_keypass.placeholder"),
 | 
			
		||||
      path: ["jksKeypass"],
 | 
			
		||||
    })
 | 
			
		||||
    .refine((data) => (data.format === "jks" ? !!data.jksStorepass?.trim() : true), {
 | 
			
		||||
      message: t("domain.deployment.form.file_jks_storepass.placeholder"),
 | 
			
		||||
      path: ["jksStorepass"],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      format: res.error?.errors?.find((e) => e.path[0] === "format")?.message,
 | 
			
		||||
      certPath: res.error?.errors?.find((e) => e.path[0] === "certPath")?.message,
 | 
			
		||||
      keyPath: res.error?.errors?.find((e) => e.path[0] === "keyPath")?.message,
 | 
			
		||||
      pfxPassword: res.error?.errors?.find((e) => e.path[0] === "pfxPassword")?.message,
 | 
			
		||||
      jksAlias: res.error?.errors?.find((e) => e.path[0] === "jksAlias")?.message,
 | 
			
		||||
      jksKeypass: res.error?.errors?.find((e) => e.path[0] === "jksKeypass")?.message,
 | 
			
		||||
      jksStorepass: res.error?.errors?.find((e) => e.path[0] === "jksStorepass")?.message,
 | 
			
		||||
      preCommand: res.error?.errors?.find((e) => e.path[0] === "preCommand")?.message,
 | 
			
		||||
      command: res.error?.errors?.find((e) => e.path[0] === "command")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (config.config?.format === "pem") {
 | 
			
		||||
      if (/(.pfx|.jks)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.pfx|.jks)$/, ".crt");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else if (config.config?.format === "pfx") {
 | 
			
		||||
      if (/(.crt|.jks)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.crt|.jks)$/, ".pfx");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } else if (config.config?.format === "jks") {
 | 
			
		||||
      if (/(.crt|.pfx)$/.test(config.config.certPath!)) {
 | 
			
		||||
        setConfig(
 | 
			
		||||
          produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.certPath = config.config!.certPath!.replace(/(.crt|.pfx)$/, ".jks");
 | 
			
		||||
          })
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [config.config?.format]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="flex flex-col space-y-8">
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.file_format.label")}</Label>
 | 
			
		||||
          <Select
 | 
			
		||||
            value={config?.config?.format}
 | 
			
		||||
            onValueChange={(value) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.format = value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <SelectTrigger>
 | 
			
		||||
              <SelectValue placeholder={t("domain.deployment.form.file_format.placeholder")} />
 | 
			
		||||
            </SelectTrigger>
 | 
			
		||||
            <SelectContent>
 | 
			
		||||
              <SelectGroup>
 | 
			
		||||
                <SelectItem value="pem">PEM</SelectItem>
 | 
			
		||||
                <SelectItem value="pfx">PFX</SelectItem>
 | 
			
		||||
                <SelectItem value="jks">JKS</SelectItem>
 | 
			
		||||
              </SelectGroup>
 | 
			
		||||
            </SelectContent>
 | 
			
		||||
          </Select>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.format}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.file_cert_path.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.file_cert_path.label")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.certPath}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.certPath = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.certPath}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "pem" ? (
 | 
			
		||||
          <div>
 | 
			
		||||
            <Label>{t("domain.deployment.form.file_key_path.label")}</Label>
 | 
			
		||||
            <Input
 | 
			
		||||
              placeholder={t("domain.deployment.form.file_key_path.placeholder")}
 | 
			
		||||
              className="w-full mt-1"
 | 
			
		||||
              value={config?.config?.keyPath}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                const nv = produce(config, (draft) => {
 | 
			
		||||
                  draft.config ??= {};
 | 
			
		||||
                  draft.config.keyPath = e.target.value?.trim();
 | 
			
		||||
                });
 | 
			
		||||
                setConfig(nv);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <div className="text-red-600 text-sm mt-1">{errors?.keyPath}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "pfx" ? (
 | 
			
		||||
          <div>
 | 
			
		||||
            <Label>{t("domain.deployment.form.file_pfx_password.label")}</Label>
 | 
			
		||||
            <Input
 | 
			
		||||
              placeholder={t("domain.deployment.form.file_pfx_password.placeholder")}
 | 
			
		||||
              className="w-full mt-1"
 | 
			
		||||
              value={config?.config?.pfxPassword}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                const nv = produce(config, (draft) => {
 | 
			
		||||
                  draft.config ??= {};
 | 
			
		||||
                  draft.config.pfxPassword = e.target.value?.trim();
 | 
			
		||||
                });
 | 
			
		||||
                setConfig(nv);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <div className="text-red-600 text-sm mt-1">{errors?.pfxPassword}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {config.config?.format === "jks" ? (
 | 
			
		||||
          <>
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_alias.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_alias.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksAlias}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksAlias = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksAlias}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_keypass.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_keypass.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksKeypass}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksKeypass = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksKeypass}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
              <Label>{t("domain.deployment.form.file_jks_storepass.label")}</Label>
 | 
			
		||||
              <Input
 | 
			
		||||
                placeholder={t("domain.deployment.form.file_jks_storepass.placeholder")}
 | 
			
		||||
                className="w-full mt-1"
 | 
			
		||||
                value={config?.config?.jksStorepass}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  const nv = produce(config, (draft) => {
 | 
			
		||||
                    draft.config ??= {};
 | 
			
		||||
                    draft.config.jksStorepass = e.target.value?.trim();
 | 
			
		||||
                  });
 | 
			
		||||
                  setConfig(nv);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <div className="text-red-600 text-sm mt-1">{errors?.jksStorepass}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <></>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.shell_pre_command.label")}</Label>
 | 
			
		||||
          <Textarea
 | 
			
		||||
            className="mt-1"
 | 
			
		||||
            value={config?.config?.preCommand}
 | 
			
		||||
            placeholder={t("domain.deployment.form.shell_pre_command.placeholder")}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.preCommand = e.target.value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          ></Textarea>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.preCommand}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.shell_command.label")}</Label>
 | 
			
		||||
          <Textarea
 | 
			
		||||
            className="mt-1"
 | 
			
		||||
            value={config?.config?.command}
 | 
			
		||||
            placeholder={t("domain.deployment.form.shell_command.placeholder")}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.command = e.target.value;
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          ></Textarea>
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.command}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToSSH;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToTencentCDNParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToTencentCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCDNParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToTencentCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,220 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToTencentCLBParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  resourceType?: string;
 | 
			
		||||
  loadbalancerId?: string;
 | 
			
		||||
  listenerId?: string;
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToTencentCLB = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCLBParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "ap-guangzhou",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z
 | 
			
		||||
    .object({
 | 
			
		||||
      region: z.string().min(1, t("domain.deployment.form.tencent_clb_region.placeholder")),
 | 
			
		||||
      resourceType: z.union([z.literal("ssl-deploy"), z.literal("loadbalancer"), z.literal("listener"), z.literal("ruledomain")], {
 | 
			
		||||
        message: t("domain.deployment.form.tencent_clb_resource_type.placeholder"),
 | 
			
		||||
      }),
 | 
			
		||||
      loadbalancerId: z.string().min(1, t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")),
 | 
			
		||||
      listenerId: z.string().optional(),
 | 
			
		||||
      domain: z.string().optional(),
 | 
			
		||||
    })
 | 
			
		||||
    .refine(
 | 
			
		||||
      (data) => {
 | 
			
		||||
        switch (data.resourceType) {
 | 
			
		||||
          case "ssl-deploy":
 | 
			
		||||
          case "listener":
 | 
			
		||||
          case "ruledomain":
 | 
			
		||||
            return !!data.listenerId?.trim();
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        message: t("domain.deployment.form.tencent_clb_listener_id.placeholder"),
 | 
			
		||||
        path: ["listenerId"],
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
    .refine(
 | 
			
		||||
      (data) => {
 | 
			
		||||
        switch (data.resourceType) {
 | 
			
		||||
          case "ssl-deploy":
 | 
			
		||||
          case "ruledomain":
 | 
			
		||||
            return !!data.domain?.trim() && /^$|^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(data.domain);
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        message: t("domain.deployment.form.tencent_clb_ruledomain.placeholder"),
 | 
			
		||||
        path: ["domain"],
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      resourceType: res.error?.errors?.find((e) => e.path[0] === "resourceType")?.message,
 | 
			
		||||
      loadbalancerId: res.error?.errors?.find((e) => e.path[0] === "loadbalancerId")?.message,
 | 
			
		||||
      listenerId: res.error?.errors?.find((e) => e.path[0] === "listenerId")?.message,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_resource_type.label")}</Label>
 | 
			
		||||
        <Select
 | 
			
		||||
          value={config?.config?.resourceType}
 | 
			
		||||
          onValueChange={(value) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.resourceType = value;
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger>
 | 
			
		||||
            <SelectValue placeholder={t("domain.deployment.form.tencent_clb_resource_type.placeholder")} />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            <SelectGroup>
 | 
			
		||||
              <SelectItem value="ssl-deploy">{t("domain.deployment.form.tencent_clb_resource_type.option.ssl_deploy.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="loadbalancer">{t("domain.deployment.form.tencent_clb_resource_type.option.loadbalancer.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="listener">{t("domain.deployment.form.tencent_clb_resource_type.option.listener.label")}</SelectItem>
 | 
			
		||||
              <SelectItem value="ruledomain">{t("domain.deployment.form.tencent_clb_resource_type.option.ruledomain.label")}</SelectItem>
 | 
			
		||||
            </SelectGroup>
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.resourceType}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_clb_loadbalancer_id.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_clb_loadbalancer_id.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.loadbalancerId}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.loadbalancerId = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.loadbalancerId}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "ssl-deploy" || config?.config?.resourceType === "listener" || config?.config?.resourceType === "ruledomain" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.tencent_clb_listener_id.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.tencent_clb_listener_id.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.listenerId}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.listenerId = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.listenerId}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "ssl-deploy" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.tencent_clb_domain.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.tencent_clb_domain.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.domain}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.domain = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {config?.config?.resourceType === "ruledomain" ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <Label>{t("domain.deployment.form.tencent_clb_ruledomain.label")}</Label>
 | 
			
		||||
          <Input
 | 
			
		||||
            placeholder={t("domain.deployment.form.tencent_clb_ruledomain.placeholder")}
 | 
			
		||||
            className="w-full mt-1"
 | 
			
		||||
            value={config?.config?.domain}
 | 
			
		||||
            onChange={(e) => {
 | 
			
		||||
              const nv = produce(config, (draft) => {
 | 
			
		||||
                draft.config ??= {};
 | 
			
		||||
                draft.config.domain = e.target.value?.trim();
 | 
			
		||||
              });
 | 
			
		||||
              setConfig(nv);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <></>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToTencentCLB;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,110 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToTencentCOSParams = {
 | 
			
		||||
  region?: string;
 | 
			
		||||
  bucket?: string;
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToTencentCOS = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentCOSParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {
 | 
			
		||||
          region: "ap-guangzhou",
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    region: z.string().min(1, t("domain.deployment.form.tencent_cos_region.placeholder")),
 | 
			
		||||
    bucket: z.string().min(1, t("domain.deployment.form.tencent_cos_bucket.placeholder")),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      region: res.error?.errors?.find((e) => e.path[0] === "region")?.message,
 | 
			
		||||
      bucket: res.error?.errors?.find((e) => e.path[0] === "bucket")?.message,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_cos_region.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_cos_region.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.region}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.region = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.region}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_cos_bucket.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_cos_bucket.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.bucket}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.bucket = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.bucket}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToTencentCOS;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,89 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { Textarea } from "@/components/ui/textarea";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToTencentTEOParams = {
 | 
			
		||||
  zoneId?: string;
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToTencentTEO = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToTencentTEOParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    zoneId: z.string().min(1, t("domain.deployment.form.tencent_teo_zone_id.placeholder")),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      zoneId: res.error?.errors?.find((e) => e.path[0] === "zoneId")?.message,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_teo_zone_id.label")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_teo_zone_id.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.zoneId}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.zoneId = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.zoneId}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.tencent_teo_domain.label")}</Label>
 | 
			
		||||
        <Textarea
 | 
			
		||||
          placeholder={t("domain.deployment.form.tencent_teo_domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToTencentTEO;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToVolcengineCDNConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToVolcengineCDN = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineCDNConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToVolcengineCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Label } from "@/components/ui/label";
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
 | 
			
		||||
type DeployToVolcengineLiveConfigParams = {
 | 
			
		||||
  domain?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployToVolcengineLive = () => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const { config, setConfig, errors, setErrors } = useDeployEditContext<DeployToVolcengineLiveConfigParams>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const res = formSchema.safeParse(config.config);
 | 
			
		||||
    setErrors({
 | 
			
		||||
      ...errors,
 | 
			
		||||
      domain: res.error?.errors?.find((e) => e.path[0] === "domain")?.message,
 | 
			
		||||
    });
 | 
			
		||||
  }, [config]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col space-y-8">
 | 
			
		||||
      <div>
 | 
			
		||||
        <Label>{t("domain.deployment.form.domain.label.wildsupported")}</Label>
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder={t("domain.deployment.form.domain.placeholder")}
 | 
			
		||||
          className="w-full mt-1"
 | 
			
		||||
          value={config?.config?.domain}
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            const nv = produce(config, (draft) => {
 | 
			
		||||
              draft.config ??= {};
 | 
			
		||||
              draft.config.domain = e.target.value?.trim();
 | 
			
		||||
            });
 | 
			
		||||
            setConfig(nv);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="text-red-600 text-sm mt-1">{errors?.domain}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToVolcengineLive;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,40 +0,0 @@
 | 
			
		|||
import { useEffect } from "react";
 | 
			
		||||
import { produce } from "immer";
 | 
			
		||||
 | 
			
		||||
import { useDeployEditContext } from "./DeployEdit";
 | 
			
		||||
import KVList from "./KVList";
 | 
			
		||||
import { type KVType } from "@/domain/domain";
 | 
			
		||||
 | 
			
		||||
const DeployToWebhook = () => {
 | 
			
		||||
  const { config, setConfig, setErrors } = useDeployEditContext();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!config.id) {
 | 
			
		||||
      setConfig({
 | 
			
		||||
        ...config,
 | 
			
		||||
        config: {},
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setErrors({});
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <KVList
 | 
			
		||||
        variables={config?.config?.variables}
 | 
			
		||||
        onValueChange={(variables: KVType[]) => {
 | 
			
		||||
          const nv = produce(config, (draft) => {
 | 
			
		||||
            draft.config ??= {};
 | 
			
		||||
            draft.config.variables = variables;
 | 
			
		||||
          });
 | 
			
		||||
          setConfig(nv);
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToWebhook;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,9 @@ import DeployToTencentCOS from "./DeployToTencentCOS";
 | 
			
		|||
import DeployToTencentTEO from "./DeployToTencentTEO";
 | 
			
		||||
import DeployToSSH from "./DeployToSSH";
 | 
			
		||||
import DeployToLocal from "./DeployToLocal";
 | 
			
		||||
import DeployToByteplusCDN from "./DeployToByteplusCDN";
 | 
			
		||||
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
 | 
			
		||||
import DeployToVolcengineLive from "./DeployToVolcengineLive";
 | 
			
		||||
 | 
			
		||||
export type DeployFormProps = {
 | 
			
		||||
  data: WorkflowNode;
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +73,12 @@ const getForm = (data: WorkflowNode, defaultProivder?: string) => {
 | 
			
		|||
      return <DeployToSSH data={data} />;
 | 
			
		||||
    case "local":
 | 
			
		||||
      return <DeployToLocal data={data} />;
 | 
			
		||||
    case "byteplus-cdn":
 | 
			
		||||
      return <DeployToByteplusCDN data={data} />;
 | 
			
		||||
    case "volcengine-cdn":
 | 
			
		||||
      return <DeployToVolcengineCDN data={data} />;
 | 
			
		||||
    case "volcengine-live":
 | 
			
		||||
      return <DeployToVolcengineLive data={data} />;
 | 
			
		||||
    default:
 | 
			
		||||
      return <></>;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,6 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
 | 
			
		|||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
 | 
			
		||||
    console.log(rs);
 | 
			
		||||
    setBeforeOutput(rs);
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { DeployFormProps } from "./DeployForm";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
 | 
			
		||||
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
 | 
			
		||||
import { useShallow } from "zustand/shallow";
 | 
			
		||||
import { usePanel } from "./PanelProvider";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
 | 
			
		||||
import { SelectLabel } from "@radix-ui/react-select";
 | 
			
		||||
import AccessSelect from "./AccessSelect";
 | 
			
		||||
import AccessEditDialog from "../certimate/AccessEditDialog";
 | 
			
		||||
import { Plus } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
const selectState = (state: WorkflowState) => ({
 | 
			
		||||
  updateNode: state.updateNode,
 | 
			
		||||
  getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
 | 
			
		||||
});
 | 
			
		||||
const DeployToByteplusCDN = ({ data }: DeployFormProps) => {
 | 
			
		||||
  const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
 | 
			
		||||
  const { hidePanel } = usePanel();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [beforeOutput, setBeforeOutput] = useState<WorkflowNode[]>([]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
 | 
			
		||||
    setBeforeOutput(rs);
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    providerType: z.string(),
 | 
			
		||||
    access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
 | 
			
		||||
    certificate: z.string().min(1),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let config: WorkflowNodeConfig = {
 | 
			
		||||
    certificate: "",
 | 
			
		||||
    providerType: "byteplus-cdn",
 | 
			
		||||
    access: "",
 | 
			
		||||
 | 
			
		||||
    domain: "",
 | 
			
		||||
  };
 | 
			
		||||
  if (data) config = data.config ?? config;
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      providerType: "byteplus-cdn",
 | 
			
		||||
      access: config.access as string,
 | 
			
		||||
      certificate: config.certificate as string,
 | 
			
		||||
      domain: config.domain as string,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (config: z.infer<typeof formSchema>) => {
 | 
			
		||||
    updateNode({ ...data, config: { ...config }, validated: true });
 | 
			
		||||
    hidePanel();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Form {...form}>
 | 
			
		||||
        <form
 | 
			
		||||
          onSubmit={(e) => {
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            form.handleSubmit(onSubmit)(e);
 | 
			
		||||
          }}
 | 
			
		||||
          className="space-y-8"
 | 
			
		||||
        >
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="access"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel className="flex justify-between">
 | 
			
		||||
                  <div>{t("domain.deployment.form.access.label")}</div>
 | 
			
		||||
 | 
			
		||||
                  <AccessEditDialog
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
                        {t("common.add")}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                    op="add"
 | 
			
		||||
                    outConfigType="byteplus"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <AccessSelect
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("access", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                    providerType="byteplus-cdn"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="certificate"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("workflow.common.certificate.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("certificate", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <SelectTrigger>
 | 
			
		||||
                      <SelectValue placeholder={t("workflow.common.certificate.placeholder")} />
 | 
			
		||||
                    </SelectTrigger>
 | 
			
		||||
                    <SelectContent>
 | 
			
		||||
                      {beforeOutput.map((item) => (
 | 
			
		||||
                        <>
 | 
			
		||||
                          <SelectGroup key={item.id}>
 | 
			
		||||
                            <SelectLabel>{item.name}</SelectLabel>
 | 
			
		||||
                            {item.output?.map((output) => (
 | 
			
		||||
                              <SelectItem key={output.name} value={`${item.id}#${output.name}`}>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  {item.name}-{output.label}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </SelectItem>
 | 
			
		||||
                            ))}
 | 
			
		||||
                          </SelectGroup>
 | 
			
		||||
                        </>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectContent>
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="domain"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div className="flex justify-end">
 | 
			
		||||
            <Button type="submit">{t("common.save")}</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToByteplusCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { DeployFormProps } from "./DeployForm";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
 | 
			
		||||
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
 | 
			
		||||
import { useShallow } from "zustand/shallow";
 | 
			
		||||
import { usePanel } from "./PanelProvider";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
 | 
			
		||||
import { SelectLabel } from "@radix-ui/react-select";
 | 
			
		||||
import AccessSelect from "./AccessSelect";
 | 
			
		||||
import AccessEditDialog from "../certimate/AccessEditDialog";
 | 
			
		||||
import { Plus } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
const selectState = (state: WorkflowState) => ({
 | 
			
		||||
  updateNode: state.updateNode,
 | 
			
		||||
  getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
 | 
			
		||||
});
 | 
			
		||||
const DeployToVolcengineCDN = ({ data }: DeployFormProps) => {
 | 
			
		||||
  const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
 | 
			
		||||
  const { hidePanel } = usePanel();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [beforeOutput, setBeforeOutput] = useState<WorkflowNode[]>([]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
 | 
			
		||||
    setBeforeOutput(rs);
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    providerType: z.string(),
 | 
			
		||||
    access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
 | 
			
		||||
    certificate: z.string().min(1),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let config: WorkflowNodeConfig = {
 | 
			
		||||
    certificate: "",
 | 
			
		||||
    providerType: "volcengine-cdn",
 | 
			
		||||
    access: "",
 | 
			
		||||
 | 
			
		||||
    domain: "",
 | 
			
		||||
  };
 | 
			
		||||
  if (data) config = data.config ?? config;
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      providerType: "volcengine-cdn",
 | 
			
		||||
      access: config.access as string,
 | 
			
		||||
      certificate: config.certificate as string,
 | 
			
		||||
      domain: config.domain as string,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (config: z.infer<typeof formSchema>) => {
 | 
			
		||||
    updateNode({ ...data, config: { ...config }, validated: true });
 | 
			
		||||
    hidePanel();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Form {...form}>
 | 
			
		||||
        <form
 | 
			
		||||
          onSubmit={(e) => {
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            form.handleSubmit(onSubmit)(e);
 | 
			
		||||
          }}
 | 
			
		||||
          className="space-y-8"
 | 
			
		||||
        >
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="access"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel className="flex justify-between">
 | 
			
		||||
                  <div>{t("domain.deployment.form.access.label")}</div>
 | 
			
		||||
 | 
			
		||||
                  <AccessEditDialog
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
                        {t("common.add")}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                    op="add"
 | 
			
		||||
                    outConfigType="volcengine"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <AccessSelect
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("access", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                    providerType="volcengine-cdn"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="certificate"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("workflow.common.certificate.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("certificate", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <SelectTrigger>
 | 
			
		||||
                      <SelectValue placeholder={t("workflow.common.certificate.placeholder")} />
 | 
			
		||||
                    </SelectTrigger>
 | 
			
		||||
                    <SelectContent>
 | 
			
		||||
                      {beforeOutput.map((item) => (
 | 
			
		||||
                        <>
 | 
			
		||||
                          <SelectGroup key={item.id}>
 | 
			
		||||
                            <SelectLabel>{item.name}</SelectLabel>
 | 
			
		||||
                            {item.output?.map((output) => (
 | 
			
		||||
                              <SelectItem key={output.name} value={`${item.id}#${output.name}`}>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  {item.name}-{output.label}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </SelectItem>
 | 
			
		||||
                            ))}
 | 
			
		||||
                          </SelectGroup>
 | 
			
		||||
                        </>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectContent>
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="domain"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div className="flex justify-end">
 | 
			
		||||
            <Button type="submit">{t("common.save")}</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToVolcengineCDN;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { DeployFormProps } from "./DeployForm";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { WorkflowNode, WorkflowNodeConfig } from "@/domain/workflow";
 | 
			
		||||
import { useWorkflowStore, WorkflowState } from "@/providers/workflow";
 | 
			
		||||
import { useShallow } from "zustand/shallow";
 | 
			
		||||
import { usePanel } from "./PanelProvider";
 | 
			
		||||
import { Button } from "../ui/button";
 | 
			
		||||
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
 | 
			
		||||
import { SelectLabel } from "@radix-ui/react-select";
 | 
			
		||||
import AccessSelect from "./AccessSelect";
 | 
			
		||||
import AccessEditDialog from "../certimate/AccessEditDialog";
 | 
			
		||||
import { Plus } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
const selectState = (state: WorkflowState) => ({
 | 
			
		||||
  updateNode: state.updateNode,
 | 
			
		||||
  getWorkflowOuptutBeforeId: state.getWorkflowOuptutBeforeId,
 | 
			
		||||
});
 | 
			
		||||
const DeployToVolcengineLive = ({ data }: DeployFormProps) => {
 | 
			
		||||
  const { updateNode, getWorkflowOuptutBeforeId } = useWorkflowStore(useShallow(selectState));
 | 
			
		||||
  const { hidePanel } = usePanel();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [beforeOutput, setBeforeOutput] = useState<WorkflowNode[]>([]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
 | 
			
		||||
    setBeforeOutput(rs);
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    providerType: z.string(),
 | 
			
		||||
    access: z.string().min(1, t("domain.deployment.form.access.placeholder")),
 | 
			
		||||
    certificate: z.string().min(1),
 | 
			
		||||
    domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
 | 
			
		||||
      message: t("common.errmsg.domain_invalid"),
 | 
			
		||||
    }),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let config: WorkflowNodeConfig = {
 | 
			
		||||
    certificate: "",
 | 
			
		||||
    providerType: "volcengine-live",
 | 
			
		||||
    access: "",
 | 
			
		||||
 | 
			
		||||
    domain: "",
 | 
			
		||||
  };
 | 
			
		||||
  if (data) config = data.config ?? config;
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      providerType: "volcengine-live",
 | 
			
		||||
      access: config.access as string,
 | 
			
		||||
      certificate: config.certificate as string,
 | 
			
		||||
      domain: config.domain as string,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (config: z.infer<typeof formSchema>) => {
 | 
			
		||||
    updateNode({ ...data, config: { ...config }, validated: true });
 | 
			
		||||
    hidePanel();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Form {...form}>
 | 
			
		||||
        <form
 | 
			
		||||
          onSubmit={(e) => {
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            form.handleSubmit(onSubmit)(e);
 | 
			
		||||
          }}
 | 
			
		||||
          className="space-y-8"
 | 
			
		||||
        >
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="access"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel className="flex justify-between">
 | 
			
		||||
                  <div>{t("domain.deployment.form.access.label")}</div>
 | 
			
		||||
 | 
			
		||||
                  <AccessEditDialog
 | 
			
		||||
                    trigger={
 | 
			
		||||
                      <div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
 | 
			
		||||
                        <Plus size={14} />
 | 
			
		||||
                        {t("common.add")}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                    op="add"
 | 
			
		||||
                    outConfigType="volcengine"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <AccessSelect
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("access", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                    providerType="volcengine-live"
 | 
			
		||||
                  />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="certificate"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("workflow.common.certificate.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    {...field}
 | 
			
		||||
                    value={field.value}
 | 
			
		||||
                    onValueChange={(value) => {
 | 
			
		||||
                      form.setValue("certificate", value);
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <SelectTrigger>
 | 
			
		||||
                      <SelectValue placeholder={t("workflow.common.certificate.placeholder")} />
 | 
			
		||||
                    </SelectTrigger>
 | 
			
		||||
                    <SelectContent>
 | 
			
		||||
                      {beforeOutput.map((item) => (
 | 
			
		||||
                        <>
 | 
			
		||||
                          <SelectGroup key={item.id}>
 | 
			
		||||
                            <SelectLabel>{item.name}</SelectLabel>
 | 
			
		||||
                            {item.output?.map((output) => (
 | 
			
		||||
                              <SelectItem key={output.name} value={`${item.id}#${output.name}`}>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                  {item.name}-{output.label}
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </SelectItem>
 | 
			
		||||
                            ))}
 | 
			
		||||
                          </SelectGroup>
 | 
			
		||||
                        </>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </SelectContent>
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="domain"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>{t("domain.deployment.form.domain.label")}</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder={t("domain.deployment.form.domain.label")} {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div className="flex justify-end">
 | 
			
		||||
            <Button type="submit">{t("common.save")}</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployToVolcengineLive;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
import { Access } from "./access";
 | 
			
		||||
 | 
			
		||||
export type AccessGroup = {
 | 
			
		||||
  id?: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  access?: string[];
 | 
			
		||||
  expand?: {
 | 
			
		||||
    access: Access[];
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +15,7 @@ import {
 | 
			
		|||
} from "@/components/ui/alert-dialog.tsx";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Separator } from "@/components/ui/separator";
 | 
			
		||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 | 
			
		||||
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
 | 
			
		||||
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
 | 
			
		||||
import AccessGroupList from "@/components/certimate/AccessGroupList";
 | 
			
		||||
import XPagination from "@/components/certimate/XPagination";
 | 
			
		||||
import { convertZulu2Beijing } from "@/lib/time";
 | 
			
		||||
import { Access as AccessType, accessProvidersMap } from "@/domain/access";
 | 
			
		||||
| 
						 | 
				
			
			@ -40,10 +37,6 @@ const Access = () => {
 | 
			
		|||
  const page = query.get("page");
 | 
			
		||||
  const pageNumber = page ? Number(page) : 1;
 | 
			
		||||
 | 
			
		||||
  const tab = query.get("tab");
 | 
			
		||||
 | 
			
		||||
  const accessGroupId = query.get("accessGroupId");
 | 
			
		||||
 | 
			
		||||
  const startIndex = (pageNumber - 1) * perPage;
 | 
			
		||||
  const endIndex = startIndex + perPage;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,143 +45,104 @@ const Access = () => {
 | 
			
		|||
    deleteAccess(rs.id);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleTabItemClick = (tab: string) => {
 | 
			
		||||
    query.set("tab", tab);
 | 
			
		||||
    navigate({ search: query.toString() });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="">
 | 
			
		||||
      <div className="flex justify-between items-center">
 | 
			
		||||
        <div className="text-muted-foreground">{t("access.page.title")}</div>
 | 
			
		||||
        {tab != "access_group" ? (
 | 
			
		||||
          <AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} />
 | 
			
		||||
        )}
 | 
			
		||||
        <AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <Tabs defaultValue={tab ? tab : "access"} value={tab ? tab : "access"} className="w-full mt-5">
 | 
			
		||||
        <TabsList className="space-x-5 px-3">
 | 
			
		||||
          <TabsTrigger
 | 
			
		||||
            value="access"
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              handleTabItemClick("access");
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            {t("access.authorization.tab")}
 | 
			
		||||
          </TabsTrigger>
 | 
			
		||||
          <TabsTrigger
 | 
			
		||||
            value="access_group"
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              handleTabItemClick("access_group");
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            {t("access.group.tab")}
 | 
			
		||||
          </TabsTrigger>
 | 
			
		||||
        </TabsList>
 | 
			
		||||
        <TabsContent value="access">
 | 
			
		||||
          {accesses.length === 0 ? (
 | 
			
		||||
            <div className="flex flex-col items-center mt-10">
 | 
			
		||||
              <span className="bg-orange-100 p-5 rounded-full">
 | 
			
		||||
                <Key size={40} className="text-primary" />
 | 
			
		||||
              </span>
 | 
			
		||||
      {accesses.length === 0 ? (
 | 
			
		||||
        <div className="flex flex-col items-center mt-10">
 | 
			
		||||
          <span className="bg-orange-100 p-5 rounded-full">
 | 
			
		||||
            <Key size={40} className="text-primary" />
 | 
			
		||||
          </span>
 | 
			
		||||
 | 
			
		||||
              <div className="text-center text-sm text-muted-foreground mt-3">{t("access.authorization.nodata")}</div>
 | 
			
		||||
              <AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" className="mt-3" />
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <>
 | 
			
		||||
              <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
 | 
			
		||||
                <div className="w-48">{t("common.text.name")}</div>
 | 
			
		||||
                <div className="w-48">{t("common.text.provider")}</div>
 | 
			
		||||
          <div className="text-center text-sm text-muted-foreground mt-3">{t("access.authorization.nodata")}</div>
 | 
			
		||||
          <AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" className="mt-3" />
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <>
 | 
			
		||||
          <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
 | 
			
		||||
            <div className="w-48">{t("common.text.name")}</div>
 | 
			
		||||
            <div className="w-48">{t("common.text.provider")}</div>
 | 
			
		||||
 | 
			
		||||
                <div className="w-60">{t("common.text.created_at")}</div>
 | 
			
		||||
                <div className="w-60">{t("common.text.updated_at")}</div>
 | 
			
		||||
                <div className="grow">{t("common.text.operations")}</div>
 | 
			
		||||
            <div className="w-60">{t("common.text.created_at")}</div>
 | 
			
		||||
            <div className="w-60">{t("common.text.updated_at")}</div>
 | 
			
		||||
            <div className="grow">{t("common.text.operations")}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
          {accesses.slice(startIndex, endIndex).map((access) => (
 | 
			
		||||
            <div
 | 
			
		||||
              className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
 | 
			
		||||
              key={access.id}
 | 
			
		||||
            >
 | 
			
		||||
              <div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-start">
 | 
			
		||||
                <div className="pr-3  truncate">{access.name}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="sm:w-48 w-full pt-1 sm:pt-0 flex  items-center space-x-2">
 | 
			
		||||
                <img src={accessProvidersMap.get(access.configType)?.icon} className="w-6" />
 | 
			
		||||
                <div>{t(accessProvidersMap.get(access.configType)?.name || "")}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
              {accesses
 | 
			
		||||
                .filter((item) => {
 | 
			
		||||
                  return accessGroupId ? item.group == accessGroupId : true;
 | 
			
		||||
                })
 | 
			
		||||
                .slice(startIndex, endIndex)
 | 
			
		||||
                .map((access) => (
 | 
			
		||||
                  <div
 | 
			
		||||
                    className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
 | 
			
		||||
                    key={access.id}
 | 
			
		||||
                  >
 | 
			
		||||
                    <div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-start">
 | 
			
		||||
                      <div className="pr-3  truncate">{access.name}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="sm:w-48 w-full pt-1 sm:pt-0 flex  items-center space-x-2">
 | 
			
		||||
                      <img src={accessProvidersMap.get(access.configType)?.icon} className="w-6" />
 | 
			
		||||
                      <div>{t(accessProvidersMap.get(access.configType)?.name || "")}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">{access.created && convertZulu2Beijing(access.created)}</div>
 | 
			
		||||
                    <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">{access.updated && convertZulu2Beijing(access.updated)}</div>
 | 
			
		||||
                    <div className="flex items-center grow justify-start pt-1 sm:pt-0">
 | 
			
		||||
                      <AccessEditDialog
 | 
			
		||||
                        trigger={
 | 
			
		||||
                          <Button variant={"link"} className="p-0">
 | 
			
		||||
                            {t("common.edit")}
 | 
			
		||||
                          </Button>
 | 
			
		||||
                        }
 | 
			
		||||
                        op="edit"
 | 
			
		||||
                        data={access}
 | 
			
		||||
                      />
 | 
			
		||||
                      <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                      <AccessEditDialog
 | 
			
		||||
                        trigger={
 | 
			
		||||
                          <Button variant={"link"} className="p-0">
 | 
			
		||||
                            {t("common.copy")}
 | 
			
		||||
                          </Button>
 | 
			
		||||
                        }
 | 
			
		||||
                        op="copy"
 | 
			
		||||
                        data={access}
 | 
			
		||||
                      />
 | 
			
		||||
                      <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                      <AlertDialog>
 | 
			
		||||
                        <AlertDialogTrigger asChild>
 | 
			
		||||
                          <Button variant={"link"} className="p-0">
 | 
			
		||||
                            {t("common.delete")}
 | 
			
		||||
                          </Button>
 | 
			
		||||
                        </AlertDialogTrigger>
 | 
			
		||||
                        <AlertDialogContent>
 | 
			
		||||
                          <AlertDialogHeader>
 | 
			
		||||
                            <AlertDialogTitle className="dark:text-gray-200">{t("access.authorization.delete")}</AlertDialogTitle>
 | 
			
		||||
                            <AlertDialogDescription>{t("access.authorization.delete.confirm")}</AlertDialogDescription>
 | 
			
		||||
                          </AlertDialogHeader>
 | 
			
		||||
                          <AlertDialogFooter>
 | 
			
		||||
                            <AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
 | 
			
		||||
                            <AlertDialogAction
 | 
			
		||||
                              onClick={() => {
 | 
			
		||||
                                handleDelete(access);
 | 
			
		||||
                              }}
 | 
			
		||||
                            >
 | 
			
		||||
                              {t("common.confirm")}
 | 
			
		||||
                            </AlertDialogAction>
 | 
			
		||||
                          </AlertDialogFooter>
 | 
			
		||||
                        </AlertDialogContent>
 | 
			
		||||
                      </AlertDialog>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                ))}
 | 
			
		||||
              <XPagination
 | 
			
		||||
                totalPages={totalPages}
 | 
			
		||||
                currentPage={pageNumber}
 | 
			
		||||
                onPageChange={(page) => {
 | 
			
		||||
                  query.set("page", page.toString());
 | 
			
		||||
                  navigate({ search: query.toString() });
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            </>
 | 
			
		||||
          )}
 | 
			
		||||
        </TabsContent>
 | 
			
		||||
        <TabsContent value="access_group">
 | 
			
		||||
          <AccessGroupList />
 | 
			
		||||
        </TabsContent>
 | 
			
		||||
      </Tabs>
 | 
			
		||||
              <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">{access.created && convertZulu2Beijing(access.created)}</div>
 | 
			
		||||
              <div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">{access.updated && convertZulu2Beijing(access.updated)}</div>
 | 
			
		||||
              <div className="flex items-center grow justify-start pt-1 sm:pt-0">
 | 
			
		||||
                <AccessEditDialog
 | 
			
		||||
                  trigger={
 | 
			
		||||
                    <Button variant={"link"} className="p-0">
 | 
			
		||||
                      {t("common.edit")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  }
 | 
			
		||||
                  op="edit"
 | 
			
		||||
                  data={access}
 | 
			
		||||
                />
 | 
			
		||||
                <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                <AccessEditDialog
 | 
			
		||||
                  trigger={
 | 
			
		||||
                    <Button variant={"link"} className="p-0">
 | 
			
		||||
                      {t("common.copy")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  }
 | 
			
		||||
                  op="copy"
 | 
			
		||||
                  data={access}
 | 
			
		||||
                />
 | 
			
		||||
                <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                <AlertDialog>
 | 
			
		||||
                  <AlertDialogTrigger asChild>
 | 
			
		||||
                    <Button variant={"link"} className="p-0">
 | 
			
		||||
                      {t("common.delete")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </AlertDialogTrigger>
 | 
			
		||||
                  <AlertDialogContent>
 | 
			
		||||
                    <AlertDialogHeader>
 | 
			
		||||
                      <AlertDialogTitle className="dark:text-gray-200">{t("access.authorization.delete")}</AlertDialogTitle>
 | 
			
		||||
                      <AlertDialogDescription>{t("access.authorization.delete.confirm")}</AlertDialogDescription>
 | 
			
		||||
                    </AlertDialogHeader>
 | 
			
		||||
                    <AlertDialogFooter>
 | 
			
		||||
                      <AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
 | 
			
		||||
                      <AlertDialogAction
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          handleDelete(access);
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
                        {t("common.confirm")}
 | 
			
		||||
                      </AlertDialogAction>
 | 
			
		||||
                    </AlertDialogFooter>
 | 
			
		||||
                  </AlertDialogContent>
 | 
			
		||||
                </AlertDialog>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          ))}
 | 
			
		||||
          <XPagination
 | 
			
		||||
            totalPages={totalPages}
 | 
			
		||||
            currentPage={pageNumber}
 | 
			
		||||
            onPageChange={(page) => {
 | 
			
		||||
              query.set("page", page.toString());
 | 
			
		||||
              navigate({ search: query.toString() });
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,503 +0,0 @@
 | 
			
		|||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useLocation } from "react-router-dom";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import z from "zod";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { ChevronsUpDown, Plus, CircleHelp } from "lucide-react";
 | 
			
		||||
import { ClientResponseError } from "pocketbase";
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
 | 
			
		||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
 | 
			
		||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
 | 
			
		||||
import { Toaster } from "@/components/ui/toaster";
 | 
			
		||||
import { useToast } from "@/components/ui/use-toast";
 | 
			
		||||
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
 | 
			
		||||
import DeployList from "@/components/certimate/DeployList";
 | 
			
		||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
 | 
			
		||||
import StringList from "@/components/certimate/StringList";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
import { PbErrorData } from "@/domain/base";
 | 
			
		||||
import { accessProvidersMap } from "@/domain/access";
 | 
			
		||||
import { EmailsSetting } from "@/domain/settings";
 | 
			
		||||
import { DeployConfig, Domain } from "@/domain/domain";
 | 
			
		||||
import { save, get } from "@/repository/domains";
 | 
			
		||||
import { useConfigContext } from "@/providers/config";
 | 
			
		||||
import { Switch } from "@/components/ui/switch";
 | 
			
		||||
import { TooltipFast } from "@/components/ui/tooltip";
 | 
			
		||||
 | 
			
		||||
const Edit = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    config: { accesses, emails },
 | 
			
		||||
  } = useConfigContext();
 | 
			
		||||
 | 
			
		||||
  const [domain, setDomain] = useState<Domain>({} as Domain);
 | 
			
		||||
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const [tab, setTab] = useState<"apply" | "deploy">("apply");
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // Parsing query parameters
 | 
			
		||||
    const queryParams = new URLSearchParams(location.search);
 | 
			
		||||
    const id = queryParams.get("id");
 | 
			
		||||
    if (id) {
 | 
			
		||||
      const fetchData = async () => {
 | 
			
		||||
        const data = await get(id);
 | 
			
		||||
        setDomain(data);
 | 
			
		||||
      };
 | 
			
		||||
      fetchData();
 | 
			
		||||
    }
 | 
			
		||||
  }, [location.search]);
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    id: z.string().optional(),
 | 
			
		||||
    domain: z.string().min(1, {
 | 
			
		||||
      message: "common.errmsg.domain_invalid",
 | 
			
		||||
    }),
 | 
			
		||||
    email: z.string().email("common.errmsg.email_invalid").optional(),
 | 
			
		||||
    access: z.string().regex(/^[a-zA-Z0-9]+$/, {
 | 
			
		||||
      message: "domain.application.form.access.placeholder",
 | 
			
		||||
    }),
 | 
			
		||||
    keyAlgorithm: z.string().optional(),
 | 
			
		||||
    nameservers: z.string().optional(),
 | 
			
		||||
    timeout: z.number().optional(),
 | 
			
		||||
    disableFollowCNAME: z.boolean().optional(),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
			
		||||
    resolver: zodResolver(formSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      id: "",
 | 
			
		||||
      domain: "",
 | 
			
		||||
      email: "",
 | 
			
		||||
      access: "",
 | 
			
		||||
      keyAlgorithm: "RSA2048",
 | 
			
		||||
      nameservers: "",
 | 
			
		||||
      timeout: 60,
 | 
			
		||||
      disableFollowCNAME: true,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (domain) {
 | 
			
		||||
      form.reset({
 | 
			
		||||
        id: domain.id,
 | 
			
		||||
        domain: domain.domain,
 | 
			
		||||
        email: domain.applyConfig?.email,
 | 
			
		||||
        access: domain.applyConfig?.access,
 | 
			
		||||
        keyAlgorithm: domain.applyConfig?.keyAlgorithm,
 | 
			
		||||
        nameservers: domain.applyConfig?.nameservers,
 | 
			
		||||
        timeout: domain.applyConfig?.timeout,
 | 
			
		||||
        disableFollowCNAME: domain.applyConfig?.disableFollowCNAME,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [domain, form]);
 | 
			
		||||
 | 
			
		||||
  const { toast } = useToast();
 | 
			
		||||
 | 
			
		||||
  const onSubmit = async (data: z.infer<typeof formSchema>) => {
 | 
			
		||||
    const req: Domain = {
 | 
			
		||||
      id: data.id as string,
 | 
			
		||||
      crontab: "0 0 * * *",
 | 
			
		||||
      domain: data.domain,
 | 
			
		||||
      email: data.email,
 | 
			
		||||
      access: data.access,
 | 
			
		||||
      applyConfig: {
 | 
			
		||||
        email: data.email ?? "",
 | 
			
		||||
        access: data.access,
 | 
			
		||||
        keyAlgorithm: data.keyAlgorithm,
 | 
			
		||||
        nameservers: data.nameservers,
 | 
			
		||||
        timeout: data.timeout,
 | 
			
		||||
        disableFollowCNAME: data.disableFollowCNAME,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const resp = await save(req);
 | 
			
		||||
      let description = t("domain.application.form.domain.changed.message");
 | 
			
		||||
      if (req.id == "") {
 | 
			
		||||
        description = t("domain.application.form.domain.added.message");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      toast({
 | 
			
		||||
        title: t("common.save.succeeded.message"),
 | 
			
		||||
        description,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!domain?.id) setTab("deploy");
 | 
			
		||||
      setDomain({ ...resp });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      const err = e as ClientResponseError;
 | 
			
		||||
 | 
			
		||||
      Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
 | 
			
		||||
        form.setError(key as keyof z.infer<typeof formSchema>, {
 | 
			
		||||
          type: "manual",
 | 
			
		||||
          message: value.message,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handelOnDeployListChange = async (list: DeployConfig[]) => {
 | 
			
		||||
    const req = {
 | 
			
		||||
      ...domain,
 | 
			
		||||
      deployConfig: list,
 | 
			
		||||
    };
 | 
			
		||||
    try {
 | 
			
		||||
      const resp = await save(req);
 | 
			
		||||
      let description = t("domain.application.form.domain.changed.message");
 | 
			
		||||
      if (req.id == "") {
 | 
			
		||||
        description = t("domain.application.form.domain.added.message");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      toast({
 | 
			
		||||
        title: t("common.save.succeeded.message"),
 | 
			
		||||
        description,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (!domain?.id) setTab("deploy");
 | 
			
		||||
      setDomain({ ...resp });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      const err = e as ClientResponseError;
 | 
			
		||||
 | 
			
		||||
      Object.entries(err.response.data as PbErrorData).forEach(([key, value]) => {
 | 
			
		||||
        form.setError(key as keyof z.infer<typeof formSchema>, {
 | 
			
		||||
          type: "manual",
 | 
			
		||||
          message: value.message,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="">
 | 
			
		||||
        <Toaster />
 | 
			
		||||
        <div className="h-5 text-muted-foreground">
 | 
			
		||||
          <Breadcrumb>
 | 
			
		||||
            <BreadcrumbList>
 | 
			
		||||
              <BreadcrumbItem>
 | 
			
		||||
                <BreadcrumbLink href="#/domains">{t("domain.page.title")}</BreadcrumbLink>
 | 
			
		||||
              </BreadcrumbItem>
 | 
			
		||||
              <BreadcrumbSeparator />
 | 
			
		||||
 | 
			
		||||
              <BreadcrumbItem>
 | 
			
		||||
                <BreadcrumbPage>{domain?.id ? t("domain.edit") : t("domain.add")}</BreadcrumbPage>
 | 
			
		||||
              </BreadcrumbItem>
 | 
			
		||||
            </BreadcrumbList>
 | 
			
		||||
          </Breadcrumb>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="flex flex-col justify-center w-full mt-5 md:space-x-10 md:flex-row">
 | 
			
		||||
          <div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex md:mt-5">
 | 
			
		||||
            <div
 | 
			
		||||
              className={cn("cursor-pointer text-right", tab === "apply" ? "text-primary" : "")}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                setTab("apply");
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {t("domain.application.tab")}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
              className={cn("cursor-pointer text-right", tab === "deploy" ? "text-primary" : "")}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                if (!domain?.id) {
 | 
			
		||||
                  toast({
 | 
			
		||||
                    title: t("domain.application.unsaved.message"),
 | 
			
		||||
                    description: t("domain.application.unsaved.message"),
 | 
			
		||||
                    variant: "destructive",
 | 
			
		||||
                  });
 | 
			
		||||
                  return;
 | 
			
		||||
                }
 | 
			
		||||
                setTab("deploy");
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {t("domain.deployment.tab")}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className="flex flex-col">
 | 
			
		||||
            <div className={cn("w-full md:w-[35em]  p-5 rounded mt-3 md:mt-0", tab == "deploy" && "hidden")}>
 | 
			
		||||
              <Form {...form}>
 | 
			
		||||
                <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 dark:text-stone-200">
 | 
			
		||||
                  {/* 域名 */}
 | 
			
		||||
                  <FormField
 | 
			
		||||
                    control={form.control}
 | 
			
		||||
                    name="domain"
 | 
			
		||||
                    render={({ field }) => (
 | 
			
		||||
                      <FormItem>
 | 
			
		||||
                        <>
 | 
			
		||||
                          <StringList
 | 
			
		||||
                            value={field.value}
 | 
			
		||||
                            valueType="domain"
 | 
			
		||||
                            onValueChange={(domain: string) => {
 | 
			
		||||
                              form.setValue("domain", domain);
 | 
			
		||||
                            }}
 | 
			
		||||
                          />
 | 
			
		||||
                        </>
 | 
			
		||||
                        <FormMessage />
 | 
			
		||||
                      </FormItem>
 | 
			
		||||
                    )}
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
                  {/* 邮箱 */}
 | 
			
		||||
                  <FormField
 | 
			
		||||
                    control={form.control}
 | 
			
		||||
                    name="email"
 | 
			
		||||
                    render={({ field }) => (
 | 
			
		||||
                      <FormItem>
 | 
			
		||||
                        <FormLabel className="flex justify-between w-full">
 | 
			
		||||
                          <div>{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}</div>
 | 
			
		||||
                          <EmailsEdit
 | 
			
		||||
                            trigger={
 | 
			
		||||
                              <div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
 | 
			
		||||
                                <Plus size={14} />
 | 
			
		||||
                                {t("common.add")}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            }
 | 
			
		||||
                          />
 | 
			
		||||
                        </FormLabel>
 | 
			
		||||
                        <FormControl>
 | 
			
		||||
                          <Select
 | 
			
		||||
                            {...field}
 | 
			
		||||
                            value={field.value}
 | 
			
		||||
                            onValueChange={(value) => {
 | 
			
		||||
                              form.setValue("email", value);
 | 
			
		||||
                            }}
 | 
			
		||||
                          >
 | 
			
		||||
                            <SelectTrigger>
 | 
			
		||||
                              <SelectValue placeholder={t("domain.application.form.email.placeholder")} />
 | 
			
		||||
                            </SelectTrigger>
 | 
			
		||||
                            <SelectContent>
 | 
			
		||||
                              <SelectGroup>
 | 
			
		||||
                                <SelectLabel>{t("domain.application.form.email.list")}</SelectLabel>
 | 
			
		||||
                                {(emails.content as EmailsSetting).emails.map((item) => (
 | 
			
		||||
                                  <SelectItem key={item} value={item}>
 | 
			
		||||
                                    <div>{item}</div>
 | 
			
		||||
                                  </SelectItem>
 | 
			
		||||
                                ))}
 | 
			
		||||
                              </SelectGroup>
 | 
			
		||||
                            </SelectContent>
 | 
			
		||||
                          </Select>
 | 
			
		||||
                        </FormControl>
 | 
			
		||||
 | 
			
		||||
                        <FormMessage />
 | 
			
		||||
                      </FormItem>
 | 
			
		||||
                    )}
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
                  {/* DNS 服务商授权 */}
 | 
			
		||||
                  <FormField
 | 
			
		||||
                    control={form.control}
 | 
			
		||||
                    name="access"
 | 
			
		||||
                    render={({ field }) => (
 | 
			
		||||
                      <FormItem>
 | 
			
		||||
                        <FormLabel className="flex justify-between w-full">
 | 
			
		||||
                          <div>{t("domain.application.form.access.label")}</div>
 | 
			
		||||
                          <AccessEditDialog
 | 
			
		||||
                            trigger={
 | 
			
		||||
                              <div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
 | 
			
		||||
                                <Plus size={14} />
 | 
			
		||||
                                {t("common.add")}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            }
 | 
			
		||||
                            op="add"
 | 
			
		||||
                          />
 | 
			
		||||
                        </FormLabel>
 | 
			
		||||
                        <FormControl>
 | 
			
		||||
                          <Select
 | 
			
		||||
                            {...field}
 | 
			
		||||
                            value={field.value}
 | 
			
		||||
                            onValueChange={(value) => {
 | 
			
		||||
                              form.setValue("access", value);
 | 
			
		||||
                            }}
 | 
			
		||||
                          >
 | 
			
		||||
                            <SelectTrigger>
 | 
			
		||||
                              <SelectValue placeholder={t("domain.application.form.access.placeholder")} />
 | 
			
		||||
                            </SelectTrigger>
 | 
			
		||||
                            <SelectContent>
 | 
			
		||||
                              <SelectGroup>
 | 
			
		||||
                                <SelectLabel>{t("domain.application.form.access.list")}</SelectLabel>
 | 
			
		||||
                                {accesses
 | 
			
		||||
                                  .filter((item) => item.usage != "deploy")
 | 
			
		||||
                                  .map((item) => (
 | 
			
		||||
                                    <SelectItem key={item.id} value={item.id}>
 | 
			
		||||
                                      <div className="flex items-center space-x-2">
 | 
			
		||||
                                        <img className="w-6" src={accessProvidersMap.get(item.configType)?.icon} />
 | 
			
		||||
                                        <div>{item.name}</div>
 | 
			
		||||
                                      </div>
 | 
			
		||||
                                    </SelectItem>
 | 
			
		||||
                                  ))}
 | 
			
		||||
                              </SelectGroup>
 | 
			
		||||
                            </SelectContent>
 | 
			
		||||
                          </Select>
 | 
			
		||||
                        </FormControl>
 | 
			
		||||
 | 
			
		||||
                        <FormMessage />
 | 
			
		||||
                      </FormItem>
 | 
			
		||||
                    )}
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <hr />
 | 
			
		||||
                    <Collapsible>
 | 
			
		||||
                      <CollapsibleTrigger className="w-full my-4">
 | 
			
		||||
                        <div className="flex items-center justify-between space-x-4">
 | 
			
		||||
                          <span className="flex-1 text-sm text-left text-gray-600">{t("domain.application.form.advanced_settings.label")}</span>
 | 
			
		||||
                          <ChevronsUpDown className="w-4 h-4" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </CollapsibleTrigger>
 | 
			
		||||
                      <CollapsibleContent>
 | 
			
		||||
                        <div className="flex flex-col space-y-8">
 | 
			
		||||
                          {/* 证书算法 */}
 | 
			
		||||
                          <FormField
 | 
			
		||||
                            control={form.control}
 | 
			
		||||
                            name="keyAlgorithm"
 | 
			
		||||
                            render={({ field }) => (
 | 
			
		||||
                              <FormItem>
 | 
			
		||||
                                <FormLabel>{t("domain.application.form.key_algorithm.label")}</FormLabel>
 | 
			
		||||
                                <Select
 | 
			
		||||
                                  {...field}
 | 
			
		||||
                                  value={field.value}
 | 
			
		||||
                                  onValueChange={(value) => {
 | 
			
		||||
                                    form.setValue("keyAlgorithm", value);
 | 
			
		||||
                                  }}
 | 
			
		||||
                                >
 | 
			
		||||
                                  <SelectTrigger>
 | 
			
		||||
                                    <SelectValue placeholder={t("domain.application.form.key_algorithm.placeholder")} />
 | 
			
		||||
                                  </SelectTrigger>
 | 
			
		||||
                                  <SelectContent>
 | 
			
		||||
                                    <SelectGroup>
 | 
			
		||||
                                      <SelectItem value="RSA2048">RSA2048</SelectItem>
 | 
			
		||||
                                      <SelectItem value="RSA3072">RSA3072</SelectItem>
 | 
			
		||||
                                      <SelectItem value="RSA4096">RSA4096</SelectItem>
 | 
			
		||||
                                      <SelectItem value="RSA8192">RSA8192</SelectItem>
 | 
			
		||||
                                      <SelectItem value="EC256">EC256</SelectItem>
 | 
			
		||||
                                      <SelectItem value="EC384">EC384</SelectItem>
 | 
			
		||||
                                    </SelectGroup>
 | 
			
		||||
                                  </SelectContent>
 | 
			
		||||
                                </Select>
 | 
			
		||||
                              </FormItem>
 | 
			
		||||
                            )}
 | 
			
		||||
                          />
 | 
			
		||||
 | 
			
		||||
                          {/* DNS */}
 | 
			
		||||
                          <FormField
 | 
			
		||||
                            control={form.control}
 | 
			
		||||
                            name="nameservers"
 | 
			
		||||
                            render={({ field }) => (
 | 
			
		||||
                              <FormItem>
 | 
			
		||||
                                <StringList
 | 
			
		||||
                                  value={field.value ?? ""}
 | 
			
		||||
                                  onValueChange={(val: string) => {
 | 
			
		||||
                                    form.setValue("nameservers", val);
 | 
			
		||||
                                  }}
 | 
			
		||||
                                  valueType="dns"
 | 
			
		||||
                                ></StringList>
 | 
			
		||||
 | 
			
		||||
                                <FormMessage />
 | 
			
		||||
                              </FormItem>
 | 
			
		||||
                            )}
 | 
			
		||||
                          />
 | 
			
		||||
 | 
			
		||||
                          {/* DNS 超时时间 */}
 | 
			
		||||
                          <FormField
 | 
			
		||||
                            control={form.control}
 | 
			
		||||
                            name="timeout"
 | 
			
		||||
                            render={({ field }) => (
 | 
			
		||||
                              <FormItem>
 | 
			
		||||
                                <FormLabel>{t("domain.application.form.timeout.label")}</FormLabel>
 | 
			
		||||
                                <FormControl>
 | 
			
		||||
                                  <Input
 | 
			
		||||
                                    type="number"
 | 
			
		||||
                                    placeholder={t("domain.application.form.timeout.placeholder")}
 | 
			
		||||
                                    {...field}
 | 
			
		||||
                                    value={field.value}
 | 
			
		||||
                                    onChange={(e) => {
 | 
			
		||||
                                      form.setValue("timeout", parseInt(e.target.value));
 | 
			
		||||
                                    }}
 | 
			
		||||
                                  />
 | 
			
		||||
                                </FormControl>
 | 
			
		||||
 | 
			
		||||
                                <FormMessage />
 | 
			
		||||
                              </FormItem>
 | 
			
		||||
                            )}
 | 
			
		||||
                          />
 | 
			
		||||
 | 
			
		||||
                          {/* 禁用 CNAME 跟随 */}
 | 
			
		||||
                          <FormField
 | 
			
		||||
                            control={form.control}
 | 
			
		||||
                            name="disableFollowCNAME"
 | 
			
		||||
                            render={({ field }) => (
 | 
			
		||||
                              <FormItem>
 | 
			
		||||
                                <FormLabel>
 | 
			
		||||
                                  <div className="flex">
 | 
			
		||||
                                    <span className="mr-1">{t("domain.application.form.disable_follow_cname.label")} </span>
 | 
			
		||||
                                    <TooltipFast
 | 
			
		||||
                                      className="max-w-[20rem]"
 | 
			
		||||
                                      contentView={
 | 
			
		||||
                                        <p>
 | 
			
		||||
                                          {t("domain.application.form.disable_follow_cname.tips")}
 | 
			
		||||
                                          <a
 | 
			
		||||
                                            className="text-primary"
 | 
			
		||||
                                            target="_blank"
 | 
			
		||||
                                            href="https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname"
 | 
			
		||||
                                          >
 | 
			
		||||
                                            {t("domain.application.form.disable_follow_cname.tips_link")}
 | 
			
		||||
                                          </a>
 | 
			
		||||
                                        </p>
 | 
			
		||||
                                      }
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <CircleHelp size={14} />
 | 
			
		||||
                                    </TooltipFast>
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                </FormLabel>
 | 
			
		||||
                                <FormControl>
 | 
			
		||||
                                  <div>
 | 
			
		||||
                                    <Switch
 | 
			
		||||
                                      defaultChecked={field.value}
 | 
			
		||||
                                      onCheckedChange={(value) => {
 | 
			
		||||
                                        form.setValue(field.name, value);
 | 
			
		||||
                                      }}
 | 
			
		||||
                                    />
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                </FormControl>
 | 
			
		||||
                                <FormMessage />
 | 
			
		||||
                              </FormItem>
 | 
			
		||||
                            )}
 | 
			
		||||
                          />
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </CollapsibleContent>
 | 
			
		||||
                    </Collapsible>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div className="flex justify-end">
 | 
			
		||||
                    <Button type="submit">{domain?.id ? t("common.save") : t("common.next")}</Button>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </form>
 | 
			
		||||
              </Form>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className={cn("flex flex-col space-y-5 w-full md:w-[35em]", tab == "apply" && "hidden")}>
 | 
			
		||||
              <DeployList
 | 
			
		||||
                deploys={domain?.deployConfig ?? []}
 | 
			
		||||
                onChange={(list: DeployConfig[]) => {
 | 
			
		||||
                  handelOnDeployListChange(list);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Edit;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,339 +0,0 @@
 | 
			
		|||
import { useEffect, useState } from "react";
 | 
			
		||||
import { Link, useLocation, useNavigate } from "react-router-dom";
 | 
			
		||||
import { useTranslation, Trans } from "react-i18next";
 | 
			
		||||
import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
 | 
			
		||||
import { Earth } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
import DeployProgress from "@/components/certimate/DeployProgress";
 | 
			
		||||
import DeployState from "@/components/certimate/DeployState";
 | 
			
		||||
import XPagination from "@/components/certimate/XPagination";
 | 
			
		||||
import {
 | 
			
		||||
  AlertDialogAction,
 | 
			
		||||
  AlertDialogCancel,
 | 
			
		||||
  AlertDialogDescription,
 | 
			
		||||
  AlertDialogFooter,
 | 
			
		||||
  AlertDialogHeader,
 | 
			
		||||
  AlertDialog,
 | 
			
		||||
  AlertDialogContent,
 | 
			
		||||
  AlertDialogTitle,
 | 
			
		||||
  AlertDialogTrigger,
 | 
			
		||||
} from "@/components/ui/alert-dialog";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Separator } from "@/components/ui/separator";
 | 
			
		||||
import { Switch } from "@/components/ui/switch";
 | 
			
		||||
import { Toaster } from "@/components/ui/toaster";
 | 
			
		||||
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
 | 
			
		||||
import { useToast } from "@/components/ui/use-toast";
 | 
			
		||||
import { CustomFile, saveFiles2ZIP } from "@/lib/file";
 | 
			
		||||
import { convertZulu2Beijing, getDate, getLeftDays } from "@/lib/time";
 | 
			
		||||
import { Domain } from "@/domain/domain";
 | 
			
		||||
import { list, remove, save, subscribeId, unsubscribeId } from "@/repository/domains";
 | 
			
		||||
 | 
			
		||||
const Home = () => {
 | 
			
		||||
  const toast = useToast();
 | 
			
		||||
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const query = new URLSearchParams(location.search);
 | 
			
		||||
  const page = query.get("page");
 | 
			
		||||
 | 
			
		||||
  const state = query.get("state");
 | 
			
		||||
 | 
			
		||||
  const [totalPage, setTotalPage] = useState(0);
 | 
			
		||||
 | 
			
		||||
  const handleCreateClick = () => {
 | 
			
		||||
    navigate("/edit");
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const setPage = (newPage: number) => {
 | 
			
		||||
    query.set("page", newPage.toString());
 | 
			
		||||
    navigate(`?${query.toString()}`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleEditClick = (id: string) => {
 | 
			
		||||
    navigate(`/edit?id=${id}`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleHistoryClick = (id: string) => {
 | 
			
		||||
    navigate(`/history?domain=${id}`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleDeleteClick = async (id: string) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await remove(id);
 | 
			
		||||
      setDomains(domains.filter((domain) => domain.id !== id));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error("Error deleting domain:", error);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const [domains, setDomains] = useState<Domain[]>([]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchData = async () => {
 | 
			
		||||
      const data = await list({
 | 
			
		||||
        page: page ? Number(page) : 1,
 | 
			
		||||
        perPage: 10,
 | 
			
		||||
        state: state ? state : "",
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setDomains(data.items);
 | 
			
		||||
      setTotalPage(data.totalPages);
 | 
			
		||||
    };
 | 
			
		||||
    fetchData();
 | 
			
		||||
  }, [page, state]);
 | 
			
		||||
 | 
			
		||||
  const handelCheckedChange = async (id: string) => {
 | 
			
		||||
    const checkedDomains = domains.filter((domain) => domain.id === id);
 | 
			
		||||
    const isChecked = checkedDomains[0].enabled;
 | 
			
		||||
 | 
			
		||||
    const data = checkedDomains[0];
 | 
			
		||||
    data.enabled = !isChecked;
 | 
			
		||||
 | 
			
		||||
    await save(data);
 | 
			
		||||
 | 
			
		||||
    const updatedDomains = domains.map((domain) => {
 | 
			
		||||
      if (domain.id === id) {
 | 
			
		||||
        return { ...domain, checked: !isChecked };
 | 
			
		||||
      }
 | 
			
		||||
      return domain;
 | 
			
		||||
    });
 | 
			
		||||
    setDomains(updatedDomains);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleRightNowClick = async (domain: Domain) => {
 | 
			
		||||
    try {
 | 
			
		||||
      unsubscribeId(domain.id ?? "");
 | 
			
		||||
      subscribeId(domain.id ?? "", (resp) => {
 | 
			
		||||
        const updatedDomains = domains.map((domain) => {
 | 
			
		||||
          if (domain.id === resp.id) {
 | 
			
		||||
            return { ...resp };
 | 
			
		||||
          }
 | 
			
		||||
          return domain;
 | 
			
		||||
        });
 | 
			
		||||
        setDomains(updatedDomains);
 | 
			
		||||
      });
 | 
			
		||||
      domain.rightnow = true;
 | 
			
		||||
 | 
			
		||||
      await save(domain);
 | 
			
		||||
 | 
			
		||||
      toast.toast({
 | 
			
		||||
        title: t("domain.deploy.started.message"),
 | 
			
		||||
        description: t("domain.deploy.started.tips"),
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      toast.toast({
 | 
			
		||||
        title: t("domain.deploy.failed.message"),
 | 
			
		||||
        description: (
 | 
			
		||||
          // 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
 | 
			
		||||
          <Trans i18nKey="domain.deploy.failed.tips">
 | 
			
		||||
            text1
 | 
			
		||||
            <Link to={`/history?domain=${domain.id}`} className="underline text-blue-500">
 | 
			
		||||
              text2
 | 
			
		||||
            </Link>
 | 
			
		||||
            text3
 | 
			
		||||
          </Trans>
 | 
			
		||||
        ),
 | 
			
		||||
        variant: "destructive",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleForceClick = async (domain: Domain) => {
 | 
			
		||||
    await handleRightNowClick({ ...domain, deployed: false });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleDownloadClick = async (domain: Domain) => {
 | 
			
		||||
    const zipName = `${domain.id}-${domain.domain}.zip`;
 | 
			
		||||
    const files: CustomFile[] = [
 | 
			
		||||
      {
 | 
			
		||||
        name: `${domain.domain}.pem`,
 | 
			
		||||
        content: domain.certificate ? domain.certificate : "",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: `${domain.domain}.key`,
 | 
			
		||||
        content: domain.privateKey ? domain.privateKey : "",
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    await saveFiles2ZIP(zipName, files);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="">
 | 
			
		||||
        <Toaster />
 | 
			
		||||
        <div className="flex justify-between items-center">
 | 
			
		||||
          <div className="text-muted-foreground">{t("domain.page.title")}</div>
 | 
			
		||||
          <Button onClick={handleCreateClick}>{t("domain.add")}</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {!domains.length ? (
 | 
			
		||||
          <>
 | 
			
		||||
            <div className="flex flex-col items-center mt-10">
 | 
			
		||||
              <span className="bg-orange-100 p-5 rounded-full">
 | 
			
		||||
                <Earth size={40} className="text-primary" />
 | 
			
		||||
              </span>
 | 
			
		||||
 | 
			
		||||
              <div className="text-center text-sm text-muted-foreground mt-3">{t("domain.nodata")}</div>
 | 
			
		||||
              <Button onClick={handleCreateClick} className="mt-3">
 | 
			
		||||
                {t("domain.add")}
 | 
			
		||||
              </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <>
 | 
			
		||||
            <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
 | 
			
		||||
              <div className="w-36">{t("common.text.domain")}</div>
 | 
			
		||||
              <div className="w-40">{t("domain.props.expiry")}</div>
 | 
			
		||||
              <div className="w-32">{t("domain.props.last_execution_status")}</div>
 | 
			
		||||
              <div className="w-64">{t("domain.props.last_execution_stage")}</div>
 | 
			
		||||
              <div className="w-40 sm:ml-2">{t("domain.props.last_execution_time")}</div>
 | 
			
		||||
              <div className="w-24">{t("domain.props.enable")}</div>
 | 
			
		||||
              <div className="grow">{t("common.text.operations")}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            {domains.map((domain) => (
 | 
			
		||||
              <div
 | 
			
		||||
                className="flex flex-col sm:flex-row text-secondary-foreground border-b  dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
 | 
			
		||||
                key={domain.id}
 | 
			
		||||
              >
 | 
			
		||||
                <div className="sm:w-36 w-full pt-1 sm:pt-0 flex flex-col items-start">
 | 
			
		||||
                  {domain.domain.split(";").map((domain: string) => (
 | 
			
		||||
                    <div className="pr-3 truncate w-full">{domain}</div>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="sm:w-40 w-full pt-1 sm:pt-0 flex  items-center">
 | 
			
		||||
                  <div>
 | 
			
		||||
                    {domain.expiredAt ? (
 | 
			
		||||
                      <>
 | 
			
		||||
                        <div>{t("domain.props.expiry.date1", { date: `${getLeftDays(domain.expiredAt)}/90` })}</div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                          {t("domain.props.expiry.date2", {
 | 
			
		||||
                            date: getDate(domain.expiredAt),
 | 
			
		||||
                          })}
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                      "---"
 | 
			
		||||
                    )}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="sm:w-32 w-full pt-1 sm:pt-0 flex items-center">
 | 
			
		||||
                  {domain.lastDeployedAt && domain.expand?.lastDeployment ? (
 | 
			
		||||
                    <>
 | 
			
		||||
                      <DeployState deployment={domain.expand.lastDeployment} />
 | 
			
		||||
                    </>
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    "---"
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="sm:w-64 w-full pt-1 sm:pt-0 flex items-center">
 | 
			
		||||
                  {domain.lastDeployedAt && domain.expand?.lastDeployment ? (
 | 
			
		||||
                    <DeployProgress phase={domain.expand.lastDeployment?.phase} phaseSuccess={domain.expand.lastDeployment?.phaseSuccess} />
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    "---"
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="sm:w-40 pt-1 sm:pt-0 sm:ml-2 flex items-center">
 | 
			
		||||
                  {domain.lastDeployedAt ? convertZulu2Beijing(domain.lastDeployedAt) : "---"}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="sm:w-24 flex items-center">
 | 
			
		||||
                  <TooltipProvider>
 | 
			
		||||
                    <Tooltip>
 | 
			
		||||
                      <TooltipTrigger>
 | 
			
		||||
                        <Switch
 | 
			
		||||
                          checked={domain.enabled}
 | 
			
		||||
                          onCheckedChange={() => {
 | 
			
		||||
                            handelCheckedChange(domain.id ?? "");
 | 
			
		||||
                          }}
 | 
			
		||||
                        ></Switch>
 | 
			
		||||
                      </TooltipTrigger>
 | 
			
		||||
                      <TooltipContent>
 | 
			
		||||
                        <div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
 | 
			
		||||
                          {domain.enabled ? t("domain.props.enable.disabled") : t("domain.props.enable.enabled")}
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </TooltipContent>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                  </TooltipProvider>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="flex items-center grow justify-start pt-1 sm:pt-0">
 | 
			
		||||
                  <Button variant={"link"} className="p-0" onClick={() => handleHistoryClick(domain.id ?? "")}>
 | 
			
		||||
                    {t("domain.history")}
 | 
			
		||||
                  </Button>
 | 
			
		||||
                  <Show when={domain.enabled ? true : false}>
 | 
			
		||||
                    <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                    <Button variant={"link"} className="p-0" onClick={() => handleRightNowClick(domain)}>
 | 
			
		||||
                      {t("domain.deploy")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </Show>
 | 
			
		||||
 | 
			
		||||
                  <Show when={(domain.enabled ? true : false) && domain.deployed ? true : false}>
 | 
			
		||||
                    <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                    <Button variant={"link"} className="p-0" onClick={() => handleForceClick(domain)}>
 | 
			
		||||
                      {t("domain.deploy_forced")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </Show>
 | 
			
		||||
 | 
			
		||||
                  <Show when={domain.expiredAt ? true : false}>
 | 
			
		||||
                    <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                    <Button variant={"link"} className="p-0" onClick={() => handleDownloadClick(domain)}>
 | 
			
		||||
                      {t("common.download")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </Show>
 | 
			
		||||
 | 
			
		||||
                  {!domain.enabled && (
 | 
			
		||||
                    <>
 | 
			
		||||
                      <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                      <AlertDialog>
 | 
			
		||||
                        <AlertDialogTrigger asChild>
 | 
			
		||||
                          <Button variant={"link"} className="p-0">
 | 
			
		||||
                            {t("common.delete")}
 | 
			
		||||
                          </Button>
 | 
			
		||||
                        </AlertDialogTrigger>
 | 
			
		||||
                        <AlertDialogContent>
 | 
			
		||||
                          <AlertDialogHeader>
 | 
			
		||||
                            <AlertDialogTitle>{t("domain.delete")}</AlertDialogTitle>
 | 
			
		||||
                            <AlertDialogDescription>{t("domain.delete.confirm")}</AlertDialogDescription>
 | 
			
		||||
                          </AlertDialogHeader>
 | 
			
		||||
                          <AlertDialogFooter>
 | 
			
		||||
                            <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
 | 
			
		||||
                            <AlertDialogAction
 | 
			
		||||
                              onClick={() => {
 | 
			
		||||
                                handleDeleteClick(domain.id ?? "");
 | 
			
		||||
                              }}
 | 
			
		||||
                            >
 | 
			
		||||
                              {t("common.confirm")}
 | 
			
		||||
                            </AlertDialogAction>
 | 
			
		||||
                          </AlertDialogFooter>
 | 
			
		||||
                        </AlertDialogContent>
 | 
			
		||||
                      </AlertDialog>
 | 
			
		||||
 | 
			
		||||
                      <Separator orientation="vertical" className="h-4 mx-2" />
 | 
			
		||||
                      <Button variant={"link"} className="p-0" onClick={() => handleEditClick(domain.id ?? "")}>
 | 
			
		||||
                        {t("common.edit")}
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </>
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            ))}
 | 
			
		||||
 | 
			
		||||
            <XPagination
 | 
			
		||||
              totalPages={totalPage}
 | 
			
		||||
              currentPage={page ? Number(page) : 1}
 | 
			
		||||
              onPageChange={(page) => {
 | 
			
		||||
                setPage(page);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Home;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,170 +0,0 @@
 | 
			
		|||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useNavigate, useSearchParams } from "react-router-dom";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Smile } from "lucide-react";
 | 
			
		||||
 | 
			
		||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { ScrollArea } from "@/components/ui/scroll-area";
 | 
			
		||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
 | 
			
		||||
import DeployProgress from "@/components/certimate/DeployProgress";
 | 
			
		||||
import DeployState from "@/components/certimate/DeployState";
 | 
			
		||||
import { convertZulu2Beijing } from "@/lib/time";
 | 
			
		||||
import { Deployment, DeploymentListReq, Log } from "@/domain/deployment";
 | 
			
		||||
import { list } from "@/repository/deployment";
 | 
			
		||||
 | 
			
		||||
const History = () => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [deployments, setDeployments] = useState<Deployment[]>();
 | 
			
		||||
  const [searchParams] = useSearchParams();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const domain = searchParams.get("domain");
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchData = async () => {
 | 
			
		||||
      const param: DeploymentListReq = {};
 | 
			
		||||
      if (domain) {
 | 
			
		||||
        param.domain = domain;
 | 
			
		||||
      }
 | 
			
		||||
      const data = await list(param);
 | 
			
		||||
      setDeployments(data.items);
 | 
			
		||||
    };
 | 
			
		||||
    fetchData();
 | 
			
		||||
  }, [domain]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ScrollArea className="h-[80vh] overflow-hidden">
 | 
			
		||||
      <div className="text-muted-foreground">{t("history.page.title")}</div>
 | 
			
		||||
      {!deployments?.length ? (
 | 
			
		||||
        <>
 | 
			
		||||
          <Alert className="max-w-[40em] mx-auto mt-20">
 | 
			
		||||
            <AlertTitle>{t("common.text.nodata")}</AlertTitle>
 | 
			
		||||
            <AlertDescription>
 | 
			
		||||
              <div className="flex items-center mt-5">
 | 
			
		||||
                <div>
 | 
			
		||||
                  <Smile className="text-yellow-400" size={36} />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="ml-2"> {t("history.nodata")}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="mt-2 flex justify-end">
 | 
			
		||||
                <Button
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    navigate("/");
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {t("domain.add")}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </AlertDescription>
 | 
			
		||||
          </Alert>
 | 
			
		||||
        </>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <>
 | 
			
		||||
          <div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
 | 
			
		||||
            <div className="w-48">{t("history.props.domain")}</div>
 | 
			
		||||
 | 
			
		||||
            <div className="w-24">{t("history.props.status")}</div>
 | 
			
		||||
            <div className="w-56">{t("history.props.stage")}</div>
 | 
			
		||||
            <div className="w-56 sm:ml-2 text-center">{t("history.props.last_execution_time")}</div>
 | 
			
		||||
 | 
			
		||||
            <div className="grow">{t("common.text.operations")}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          {deployments?.map((deployment) => (
 | 
			
		||||
            <div
 | 
			
		||||
              key={deployment.id}
 | 
			
		||||
              className="flex flex-col sm:flex-row text-secondary-foreground border-b  dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
 | 
			
		||||
            >
 | 
			
		||||
              <div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-start flex-col">
 | 
			
		||||
                {deployment.expand.domain?.domain.split(";").map((domain: string) => <div className="pr-3 truncate w-full">{domain}</div>)}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
 | 
			
		||||
                <DeployState deployment={deployment} />
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
 | 
			
		||||
                <DeployProgress phase={deployment.phase} phaseSuccess={deployment.phaseSuccess} />
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center sm:justify-center">{convertZulu2Beijing(deployment.deployedAt)}</div>
 | 
			
		||||
              <div className="flex items-center grow justify-start pt-1 sm:pt-0 sm:ml-2">
 | 
			
		||||
                <Sheet>
 | 
			
		||||
                  <SheetTrigger asChild>
 | 
			
		||||
                    <Button variant={"link"} className="p-0">
 | 
			
		||||
                      {t("history.log")}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </SheetTrigger>
 | 
			
		||||
                  <SheetContent className="sm:max-w-5xl">
 | 
			
		||||
                    <SheetHeader>
 | 
			
		||||
                      <SheetTitle>
 | 
			
		||||
                        {deployment.expand.domain?.domain}-{deployment.id}
 | 
			
		||||
                        {t("history.log")}
 | 
			
		||||
                      </SheetTitle>
 | 
			
		||||
                    </SheetHeader>
 | 
			
		||||
                    <div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh] overflow-y-auto">
 | 
			
		||||
                      {deployment.log.check && (
 | 
			
		||||
                        <>
 | 
			
		||||
                          {deployment.log.check.map((item: Log) => {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <div className="flex flex-col mt-2">
 | 
			
		||||
                                <div className="flex">
 | 
			
		||||
                                  <div>[{item.time}]</div>
 | 
			
		||||
                                  <div className="ml-2">{item.message}</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                {item.error && <div className="mt-1 text-red-600">{item.error}</div>}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            );
 | 
			
		||||
                          })}
 | 
			
		||||
                        </>
 | 
			
		||||
                      )}
 | 
			
		||||
 | 
			
		||||
                      {deployment.log.apply && (
 | 
			
		||||
                        <>
 | 
			
		||||
                          {deployment.log.apply.map((item: Log) => {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <div className="flex flex-col mt-2">
 | 
			
		||||
                                <div className="flex">
 | 
			
		||||
                                  <div>[{item.time}]</div>
 | 
			
		||||
                                  <div className="ml-2">{item.message}</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                {item.info &&
 | 
			
		||||
                                  item.info.map((info: string) => {
 | 
			
		||||
                                    return <div className="mt-1 text-green-600">{info}</div>;
 | 
			
		||||
                                  })}
 | 
			
		||||
                                {item.error && <div className="mt-1 text-red-600">{item.error}</div>}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            );
 | 
			
		||||
                          })}
 | 
			
		||||
                        </>
 | 
			
		||||
                      )}
 | 
			
		||||
 | 
			
		||||
                      {deployment.log.deploy && (
 | 
			
		||||
                        <>
 | 
			
		||||
                          {deployment.log.deploy.map((item: Log) => {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <div className="flex flex-col mt-2">
 | 
			
		||||
                                <div className="flex">
 | 
			
		||||
                                  <div>[{item.time}]</div>
 | 
			
		||||
                                  <div className="ml-2">{item.message}</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                {item.info &&
 | 
			
		||||
                                  item.info.map((info: string) => {
 | 
			
		||||
                                    return <div className="mt-1 text-green-600 break-words">{info}</div>;
 | 
			
		||||
                                  })}
 | 
			
		||||
                                {item.error && <div className="mt-1 text-red-600">{item.error}</div>}
 | 
			
		||||
                              </div>
 | 
			
		||||
                            );
 | 
			
		||||
                          })}
 | 
			
		||||
                        </>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </SheetContent>
 | 
			
		||||
                </Sheet>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          ))}
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
    </ScrollArea>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default History;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,6 @@ const WorkflowDetail = () => {
 | 
			
		|||
  const [running, setRunning] = useState(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    console.log(id);
 | 
			
		||||
    init(id ?? "");
 | 
			
		||||
    if (id) {
 | 
			
		||||
      setLocId(id);
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +86,9 @@ const WorkflowDetail = () => {
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    switchEnable();
 | 
			
		||||
    if (!locId) {
 | 
			
		||||
      navigate(`/workflow/detail?id=${workflow.id}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleWorkflowSaveClick = () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +101,9 @@ const WorkflowDetail = () => {
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    save();
 | 
			
		||||
    if (!locId) {
 | 
			
		||||
      navigate(`/workflow/detail?id=${workflow.id}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getTabCls = (tabName: string) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,7 @@
 | 
			
		|||
import { createHashRouter } from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
import DashboardLayout from "./pages/DashboardLayout";
 | 
			
		||||
import Home from "./pages/domains/Home";
 | 
			
		||||
import Edit from "./pages/domains/Edit";
 | 
			
		||||
import Access from "./pages/access/Access";
 | 
			
		||||
import History from "./pages/history/History";
 | 
			
		||||
import Login from "./pages/login/Login";
 | 
			
		||||
import LoginLayout from "./pages/LoginLayout";
 | 
			
		||||
import Password from "./pages/setting/Password";
 | 
			
		||||
| 
						 | 
				
			
			@ -26,22 +23,10 @@ export const router = createHashRouter([
 | 
			
		|||
        path: "/",
 | 
			
		||||
        element: <Dashboard />,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: "/domains",
 | 
			
		||||
        element: <Home />,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: "/edit",
 | 
			
		||||
        element: <Edit />,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: "/access",
 | 
			
		||||
        element: <Access />,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: "/history",
 | 
			
		||||
        element: <History />,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: "/workflow",
 | 
			
		||||
        element: <Workflow />,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue