mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
details improvement and unnecessary files deletion
This commit is contained in:
parent
37df882ed3
commit
9ff3e22c80
102
internal/certificate/service.go
Normal file
102
internal/certificate/service.go
Normal file
@ -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 {
|
type Certificate struct {
|
||||||
Meta
|
Meta
|
||||||
SAN string `json:"san"`
|
SAN string `json:"san" db:"san"`
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate" db:"certificate"`
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey" db:"privateKey"`
|
||||||
IssuerCertificate string `json:"issuerCertificate"`
|
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
||||||
CertUrl string `json:"certUrl"`
|
CertUrl string `json:"certUrl" db:"certUrl"`
|
||||||
CertStableUrl string `json:"certStableUrl"`
|
CertStableUrl string `json:"certStableUrl" db:"certStableUrl"`
|
||||||
Output string `json:"output"`
|
Output string `json:"output" db:"output"`
|
||||||
Workflow string `json:"workflow"`
|
Workflow string `json:"workflow" db:"workflow"`
|
||||||
ExpireAt time.Time `json:"ExpireAt"`
|
ExpireAt time.Time `json:"ExpireAt" db:"expireAt"`
|
||||||
NodeId string `json:"nodeId"`
|
NodeId string `json:"nodeId" db:"nodeId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetaData struct {
|
type MetaData struct {
|
||||||
|
@ -3,7 +3,7 @@ package domain
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id" db:"id"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created" db:"created"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated" db:"updated"`
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,17 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
|||||||
|
|
||||||
return v, nil
|
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"
|
WorkflowNodeTypeCondition = "condition"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WorkflowTypeAuto = "auto"
|
||||||
|
WorkflowTypeManual = "manual"
|
||||||
|
)
|
||||||
|
|
||||||
type Workflow struct {
|
type Workflow struct {
|
||||||
Meta
|
Meta
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
Crontab string `json:"crontab"`
|
||||||
Content *WorkflowNode `json:"content"`
|
Content *WorkflowNode `json:"content"`
|
||||||
Draft *WorkflowNode `json:"draft"`
|
Draft *WorkflowNode `json:"draft"`
|
||||||
Enabled bool `json:"enabled"`
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
24
internal/repository/certificate.go
Normal file
24
internal/repository/certificate.go
Normal file
@ -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"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
@ -16,6 +17,26 @@ func NewWorkflowRepository() *WorkflowRepository {
|
|||||||
return &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 {
|
func (w *WorkflowRepository) SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error {
|
||||||
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_run_log")
|
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_run_log")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -40,6 +61,10 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return record2Workflow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func record2Workflow(record *models.Record) (*domain.Workflow, error) {
|
||||||
content := &domain.WorkflowNode{}
|
content := &domain.WorkflowNode{}
|
||||||
if err := record.UnmarshalJSONField("content", content); err != nil {
|
if err := record.UnmarshalJSONField("content", content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -59,6 +84,7 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
|
|||||||
Name: record.GetString("name"),
|
Name: record.GetString("name"),
|
||||||
Description: record.GetString("description"),
|
Description: record.GetString("description"),
|
||||||
Type: record.GetString("type"),
|
Type: record.GetString("type"),
|
||||||
|
Crontab: record.GetString("crontab"),
|
||||||
Enabled: record.GetBool("enabled"),
|
Enabled: record.GetBool("enabled"),
|
||||||
HasDraft: record.GetBool("hasDraft"),
|
HasDraft: record.GetBool("hasDraft"),
|
||||||
|
|
||||||
|
11
internal/scheduler/certificate.go
Normal file
11
internal/scheduler/certificate.go
Normal file
@ -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())
|
||||||
|
}
|
19
internal/scheduler/scheduler.go
Normal file
19
internal/scheduler/scheduler.go
Normal file
@ -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)
|
||||||
|
}
|
11
internal/scheduler/workflow.go
Normal file
11
internal/scheduler/workflow.go
Normal file
@ -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 (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/tools/cron"
|
"github.com/pocketbase/pocketbase/tools/cron"
|
||||||
)
|
)
|
||||||
@ -13,6 +14,10 @@ var scheduler *cron.Cron
|
|||||||
func GetScheduler() *cron.Cron {
|
func GetScheduler() *cron.Cron {
|
||||||
schedulerOnce.Do(func() {
|
schedulerOnce.Do(func() {
|
||||||
scheduler = cron.New()
|
scheduler = cron.New()
|
||||||
|
location, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
if err == nil {
|
||||||
|
scheduler.SetTimezone(location)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return scheduler
|
return scheduler
|
||||||
|
71
internal/workflow/event.go
Normal file
71
internal/workflow/event.go
Normal file
@ -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 {
|
type WorkflowRepository interface {
|
||||||
Get(ctx context.Context, id string) (*domain.Workflow, error)
|
Get(ctx context.Context, id string) (*domain.Workflow, error)
|
||||||
SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error
|
SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error
|
||||||
|
ListEnabledAuto(ctx context.Context) ([]domain.Workflow, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkflowService struct {
|
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 {
|
func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) error {
|
||||||
// 查询
|
// 查询
|
||||||
if req.Id == "" {
|
if req.Id == "" {
|
||||||
|
9
main.go
9
main.go
@ -13,9 +13,10 @@ import (
|
|||||||
|
|
||||||
_ "github.com/usual2970/certimate/migrations"
|
_ "github.com/usual2970/certimate/migrations"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domains"
|
|
||||||
"github.com/usual2970/certimate/internal/routes"
|
"github.com/usual2970/certimate/internal/routes"
|
||||||
|
"github.com/usual2970/certimate/internal/scheduler"
|
||||||
"github.com/usual2970/certimate/internal/utils/app"
|
"github.com/usual2970/certimate/internal/utils/app"
|
||||||
|
"github.com/usual2970/certimate/internal/workflow"
|
||||||
"github.com/usual2970/certimate/ui"
|
"github.com/usual2970/certimate/ui"
|
||||||
|
|
||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
@ -38,13 +39,13 @@ func main() {
|
|||||||
Automigrate: isGoRun,
|
Automigrate: isGoRun,
|
||||||
})
|
})
|
||||||
|
|
||||||
domains.AddEvent()
|
workflow.AddEvent()
|
||||||
|
|
||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
domains.InitSchedule()
|
|
||||||
|
|
||||||
routes.Register(e.Router)
|
routes.Register(e.Router)
|
||||||
|
|
||||||
|
scheduler.Register()
|
||||||
|
|
||||||
e.Router.GET(
|
e.Router.GET(
|
||||||
"/*",
|
"/*",
|
||||||
echo.StaticDirectoryHandler(ui.DistDirFS, false),
|
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 { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Plus } from "lucide-react";
|
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { readFileContent } from "@/lib/file";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { accessProvidersMap, accessTypeFormSchema, type Access, type SSHConfig } from "@/domain/access";
|
import { accessProvidersMap, accessTypeFormSchema, type Access, type SSHConfig } from "@/domain/access";
|
||||||
import { save } from "@/repository/access";
|
import { save } from "@/repository/access";
|
||||||
@ -26,12 +22,7 @@ type AccessSSHFormProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
|
const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
|
||||||
const {
|
const { addAccess, updateAccess, reloadAccessGroups } = useConfigContext();
|
||||||
addAccess,
|
|
||||||
updateAccess,
|
|
||||||
reloadAccessGroups,
|
|
||||||
config: { accessGroups },
|
|
||||||
} = useConfigContext();
|
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
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
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="id"
|
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 DeployToTencentTEO from "./DeployToTencentTEO";
|
||||||
import DeployToSSH from "./DeployToSSH";
|
import DeployToSSH from "./DeployToSSH";
|
||||||
import DeployToLocal from "./DeployToLocal";
|
import DeployToLocal from "./DeployToLocal";
|
||||||
|
import DeployToByteplusCDN from "./DeployToByteplusCDN";
|
||||||
|
import DeployToVolcengineCDN from "./DeployToVolcengineCDN";
|
||||||
|
import DeployToVolcengineLive from "./DeployToVolcengineLive";
|
||||||
|
|
||||||
export type DeployFormProps = {
|
export type DeployFormProps = {
|
||||||
data: WorkflowNode;
|
data: WorkflowNode;
|
||||||
@ -70,6 +73,12 @@ const getForm = (data: WorkflowNode, defaultProivder?: string) => {
|
|||||||
return <DeployToSSH data={data} />;
|
return <DeployToSSH data={data} />;
|
||||||
case "local":
|
case "local":
|
||||||
return <DeployToLocal data={data} />;
|
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:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ const DeployToBaiduCloudCDN = ({ data }: DeployFormProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
|
const rs = getWorkflowOuptutBeforeId(data.id, "certificate");
|
||||||
console.log(rs);
|
|
||||||
setBeforeOutput(rs);
|
setBeforeOutput(rs);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
181
ui/src/components/workflow/DeployToByteplusCDN.tsx
Normal file
181
ui/src/components/workflow/DeployToByteplusCDN.tsx
Normal file
@ -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;
|
181
ui/src/components/workflow/DeployToVolcengineCDN.tsx
Normal file
181
ui/src/components/workflow/DeployToVolcengineCDN.tsx
Normal file
@ -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;
|
181
ui/src/components/workflow/DeployToVolcengineLive.tsx
Normal file
181
ui/src/components/workflow/DeployToVolcengineLive.tsx
Normal file
@ -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";
|
} from "@/components/ui/alert-dialog.tsx";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
|
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 XPagination from "@/components/certimate/XPagination";
|
||||||
import { convertZulu2Beijing } from "@/lib/time";
|
import { convertZulu2Beijing } from "@/lib/time";
|
||||||
import { Access as AccessType, accessProvidersMap } from "@/domain/access";
|
import { Access as AccessType, accessProvidersMap } from "@/domain/access";
|
||||||
@ -40,10 +37,6 @@ const Access = () => {
|
|||||||
const page = query.get("page");
|
const page = query.get("page");
|
||||||
const pageNumber = page ? Number(page) : 1;
|
const pageNumber = page ? Number(page) : 1;
|
||||||
|
|
||||||
const tab = query.get("tab");
|
|
||||||
|
|
||||||
const accessGroupId = query.get("accessGroupId");
|
|
||||||
|
|
||||||
const startIndex = (pageNumber - 1) * perPage;
|
const startIndex = (pageNumber - 1) * perPage;
|
||||||
const endIndex = startIndex + perPage;
|
const endIndex = startIndex + perPage;
|
||||||
|
|
||||||
@ -52,143 +45,104 @@ const Access = () => {
|
|||||||
deleteAccess(rs.id);
|
deleteAccess(rs.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabItemClick = (tab: string) => {
|
|
||||||
query.set("tab", tab);
|
|
||||||
navigate({ search: query.toString() });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-muted-foreground">{t("access.page.title")}</div>
|
<div className="text-muted-foreground">{t("access.page.title")}</div>
|
||||||
{tab != "access_group" ? (
|
<AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
|
||||||
<AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
|
|
||||||
) : (
|
|
||||||
<AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue={tab ? tab : "access"} value={tab ? tab : "access"} className="w-full mt-5">
|
{accesses.length === 0 ? (
|
||||||
<TabsList className="space-x-5 px-3">
|
<div className="flex flex-col items-center mt-10">
|
||||||
<TabsTrigger
|
<span className="bg-orange-100 p-5 rounded-full">
|
||||||
value="access"
|
<Key size={40} className="text-primary" />
|
||||||
onClick={() => {
|
</span>
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="text-center text-sm text-muted-foreground mt-3">{t("access.authorization.nodata")}</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" />
|
<AccessEditDialog trigger={<Button>{t("access.authorization.add")}</Button>} op="add" className="mt-3" />
|
||||||
</div>
|
</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="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.name")}</div>
|
||||||
<div className="w-48">{t("common.text.provider")}</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.created_at")}</div>
|
||||||
<div className="w-60">{t("common.text.updated_at")}</div>
|
<div className="w-60">{t("common.text.updated_at")}</div>
|
||||||
<div className="grow">{t("common.text.operations")}</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>
|
</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.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="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">
|
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
||||||
<AccessEditDialog
|
<AccessEditDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
{t("common.edit")}
|
{t("common.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
op="edit"
|
op="edit"
|
||||||
data={access}
|
data={access}
|
||||||
/>
|
/>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||||
<AccessEditDialog
|
<AccessEditDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
{t("common.copy")}
|
{t("common.copy")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
op="copy"
|
op="copy"
|
||||||
data={access}
|
data={access}
|
||||||
/>
|
/>
|
||||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant={"link"} className="p-0">
|
<Button variant={"link"} className="p-0">
|
||||||
{t("common.delete")}
|
{t("common.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle className="dark:text-gray-200">{t("access.authorization.delete")}</AlertDialogTitle>
|
<AlertDialogTitle className="dark:text-gray-200">{t("access.authorization.delete")}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>{t("access.authorization.delete.confirm")}</AlertDialogDescription>
|
<AlertDialogDescription>{t("access.authorization.delete.confirm")}</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
|
<AlertDialogCancel className="dark:text-gray-200">{t("common.cancel")}</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDelete(access);
|
handleDelete(access);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("common.confirm")}
|
{t("common.confirm")}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<XPagination
|
<XPagination
|
||||||
totalPages={totalPages}
|
totalPages={totalPages}
|
||||||
currentPage={pageNumber}
|
currentPage={pageNumber}
|
||||||
onPageChange={(page) => {
|
onPageChange={(page) => {
|
||||||
query.set("page", page.toString());
|
query.set("page", page.toString());
|
||||||
navigate({ search: query.toString() });
|
navigate({ search: query.toString() });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="access_group">
|
|
||||||
<AccessGroupList />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</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);
|
const [running, setRunning] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(id);
|
|
||||||
init(id ?? "");
|
init(id ?? "");
|
||||||
if (id) {
|
if (id) {
|
||||||
setLocId(id);
|
setLocId(id);
|
||||||
@ -87,6 +86,9 @@ const WorkflowDetail = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switchEnable();
|
switchEnable();
|
||||||
|
if (!locId) {
|
||||||
|
navigate(`/workflow/detail?id=${workflow.id}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWorkflowSaveClick = () => {
|
const handleWorkflowSaveClick = () => {
|
||||||
@ -99,6 +101,9 @@ const WorkflowDetail = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
save();
|
save();
|
||||||
|
if (!locId) {
|
||||||
|
navigate(`/workflow/detail?id=${workflow.id}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTabCls = (tabName: string) => {
|
const getTabCls = (tabName: string) => {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { createHashRouter } from "react-router-dom";
|
import { createHashRouter } from "react-router-dom";
|
||||||
|
|
||||||
import DashboardLayout from "./pages/DashboardLayout";
|
import DashboardLayout from "./pages/DashboardLayout";
|
||||||
import Home from "./pages/domains/Home";
|
|
||||||
import Edit from "./pages/domains/Edit";
|
|
||||||
import Access from "./pages/access/Access";
|
import Access from "./pages/access/Access";
|
||||||
import History from "./pages/history/History";
|
|
||||||
import Login from "./pages/login/Login";
|
import Login from "./pages/login/Login";
|
||||||
import LoginLayout from "./pages/LoginLayout";
|
import LoginLayout from "./pages/LoginLayout";
|
||||||
import Password from "./pages/setting/Password";
|
import Password from "./pages/setting/Password";
|
||||||
@ -26,22 +23,10 @@ export const router = createHashRouter([
|
|||||||
path: "/",
|
path: "/",
|
||||||
element: <Dashboard />,
|
element: <Dashboard />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/domains",
|
|
||||||
element: <Home />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/edit",
|
|
||||||
element: <Edit />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/access",
|
path: "/access",
|
||||||
element: <Access />,
|
element: <Access />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/history",
|
|
||||||
element: <History />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/workflow",
|
path: "/workflow",
|
||||||
element: <Workflow />,
|
element: <Workflow />,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user