Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704d2eed32 | ||
|
|
3348301493 | ||
|
|
afeae4269c | ||
|
|
be15f2b6a6 | ||
|
|
0c1d3341f4 | ||
|
|
1a9cad355c | ||
|
|
2c97fa929a | ||
|
|
e397793153 | ||
|
|
9bd279a8a0 | ||
|
|
dd7897feff | ||
|
|
d851a86a9b | ||
|
|
62133a91a6 | ||
|
|
5b30fc8aba | ||
|
|
2ed94bf509 | ||
|
|
1928a47961 | ||
|
|
19f5348802 | ||
|
|
7d74e1d41e | ||
|
|
3b06c7b0a6 | ||
|
|
9f1b00f04c |
@@ -15,6 +15,7 @@ Certimate 就是为了解决上述问题而产生的,它具有以下特点:
|
||||
|
||||
相关文章:
|
||||
|
||||
* [⚠️⚠️⚠️V0.2.0-第一个不向后兼容的版本](https://docs.certimate.me/blog/v0.2.0)
|
||||
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
|
||||
* [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -5,11 +5,11 @@ go 1.22.0
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
|
||||
github.com/alibabacloud-go/tea v1.2.2
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/go-acme/lego/v4 v4.19.2
|
||||
github.com/gojek/heimdall/v7 v7.0.3
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
|
||||
5
go.sum
5
go.sum
@@ -31,12 +31,9 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0 h1:nOrp0n2nFZiYN0wIG7S26YVVaMMzOBkX9GJqUvYnGeE=
|
||||
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0/go.mod h1:yzkgdLANANu/v56k0ptslGl++JJL4Op1V09HTavfoCo=
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 h1:yTKngw4rBR3hdpoo/uCyBffYXfPfjNjlaDL8nTYUIds=
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0/go.mod h1:HxQrwVKBx3/6bIwmdDcpqBpSQt2tpi/j4LfEhl+QFPk=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.6/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9 h1:fxMCrZatZfXq5nLcgkmWBXmU3FLC1OR+m/SqVtMqflk=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
|
||||
@@ -82,6 +79,8 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 h1:r2uwBUQhLhcPzaWz9tRJqc8MjYwHb+oF2+Q6467BF14=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
@@ -25,6 +26,7 @@ func (a *aliyun) Apply() (*Certificate, error) {
|
||||
|
||||
os.Setenv("ALICLOUD_ACCESS_KEY", access.AccessKeyId)
|
||||
os.Setenv("ALICLOUD_SECRET_KEY", access.AccessKeySecret)
|
||||
os.Setenv("ALICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||
dnsProvider, err := alidns.NewDNSProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package applicant
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/app"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
@@ -46,6 +47,8 @@ var sslProviderUrls = map[string]string{
|
||||
|
||||
const defaultEmail = "536464346@qq.com"
|
||||
|
||||
const defaultTimeout = 60
|
||||
|
||||
type Certificate struct {
|
||||
CertUrl string `json:"certUrl"`
|
||||
CertStableUrl string `json:"certStableUrl"`
|
||||
@@ -60,6 +63,7 @@ type ApplyOption struct {
|
||||
Domain string `json:"domain"`
|
||||
Access string `json:"access"`
|
||||
Nameservers string `json:"nameservers"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
|
||||
type MyUser struct {
|
||||
@@ -83,8 +87,22 @@ type Applicant interface {
|
||||
}
|
||||
|
||||
func Get(record *models.Record) (Applicant, error) {
|
||||
access := record.ExpandedOne("access")
|
||||
email := record.GetString("email")
|
||||
|
||||
if record.GetString("applyConfig") == "" {
|
||||
return nil, errors.New("apply config is empty")
|
||||
}
|
||||
|
||||
applyConfig := &domain.ApplyConfig{}
|
||||
|
||||
record.UnmarshalJSONField("applyConfig", applyConfig)
|
||||
|
||||
access, err := app.GetApp().Dao().FindRecordById("access", applyConfig.Access)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("access record not found: %w", err)
|
||||
}
|
||||
|
||||
email := applyConfig.Email
|
||||
if email == "" {
|
||||
email = defaultEmail
|
||||
}
|
||||
@@ -92,7 +110,8 @@ func Get(record *models.Record) (Applicant, error) {
|
||||
Email: email,
|
||||
Domain: record.GetString("domain"),
|
||||
Access: access.GetString("config"),
|
||||
Nameservers: record.GetString("nameservers"),
|
||||
Nameservers: applyConfig.Nameservers,
|
||||
Timeout: applyConfig.Timeout,
|
||||
}
|
||||
switch access.GetString("configType") {
|
||||
case configTypeAliyun:
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
@@ -23,6 +24,7 @@ func (c *cloudflare) Apply() (*Certificate, error) {
|
||||
json.Unmarshal([]byte(c.option.Access), access)
|
||||
|
||||
os.Setenv("CLOUDFLARE_DNS_API_TOKEN", access.DnsApiToken)
|
||||
os.Setenv("CLOUDFLARE_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", c.option.Timeout))
|
||||
|
||||
provider, err := cf.NewDNSProvider()
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
@@ -25,6 +26,7 @@ func (a *godaddy) Apply() (*Certificate, error) {
|
||||
|
||||
os.Setenv("GODADDY_API_KEY", access.ApiKey)
|
||||
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
|
||||
os.Setenv("GODADDY_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||
|
||||
dnsProvider, err := godaddyProvider.NewDNSProvider()
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||
@@ -26,6 +27,8 @@ func (t *huaweicloud) Apply() (*Certificate, error) {
|
||||
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
||||
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
||||
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
||||
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
||||
|
||||
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||
@@ -24,6 +25,7 @@ func (a *namesilo) Apply() (*Certificate, error) {
|
||||
json.Unmarshal([]byte(a.option.Access), access)
|
||||
|
||||
os.Setenv("NAMESILO_API_KEY", access.ApiKey)
|
||||
os.Setenv("NAMESILO_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
||||
|
||||
dnsProvider, err := namesiloProvider.NewDNSProvider()
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package applicant
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||
@@ -25,6 +26,8 @@ func (t *tencent) Apply() (*Certificate, error) {
|
||||
|
||||
os.Setenv("TENCENTCLOUD_SECRET_ID", access.SecretId)
|
||||
os.Setenv("TENCENTCLOUD_SECRET_KEY", access.SecretKey)
|
||||
os.Setenv("TENCENTCLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
||||
|
||||
dnsProvider, err := tencentcloud.NewDNSProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/applicant"
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/rand"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cas20200407 "github.com/alibabacloud-go/cas-20200407/v2/client"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
)
|
||||
|
||||
type aliyun struct {
|
||||
client *cas20200407.Client
|
||||
client *oss.Client
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
}
|
||||
@@ -47,160 +40,31 @@ func (a *aliyun) GetInfo() []string {
|
||||
}
|
||||
|
||||
func (a *aliyun) Deploy(ctx context.Context) error {
|
||||
|
||||
// 查询有没有对应的资源
|
||||
resource, err := a.resource()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.infos = append(a.infos, toStr("查询对应的资源", resource))
|
||||
|
||||
// 查询有没有对应的联系人
|
||||
contacts, err := a.contacts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.infos = append(a.infos, toStr("查询联系人", contacts))
|
||||
|
||||
// 上传证书
|
||||
certId, err := a.uploadCert(&a.option.Certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.infos = append(a.infos, toStr("上传证书", certId))
|
||||
|
||||
// 部署证书
|
||||
jobId, err := a.deploy(resource, certId, contacts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.infos = append(a.infos, toStr("创建部署证书任务", jobId))
|
||||
|
||||
// 等待部署成功
|
||||
err = a.updateDeployStatus(*jobId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 部署成功后删除旧的证书
|
||||
a.deleteCert(resource)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *aliyun) updateDeployStatus(jobId int64) error {
|
||||
// 查询部署状态
|
||||
req := &cas20200407.UpdateDeploymentJobStatusRequest{
|
||||
JobId: tea.Int64(jobId),
|
||||
}
|
||||
|
||||
resp, err := a.client.UpdateDeploymentJobStatus(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.infos = append(a.infos, toStr("查询对应的资源", resp))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *aliyun) deleteCert(resource *cas20200407.ListCloudResourcesResponseBodyData) error {
|
||||
// 查询有没有对应的资源
|
||||
if resource.CertId == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除证书
|
||||
_, err := a.client.DeleteUserCertificate(&cas20200407.DeleteUserCertificateRequest{
|
||||
CertId: resource.CertId,
|
||||
err := a.client.PutBucketCnameWithCertificate(getDeployString(a.option.DeployConfig, "bucket"), oss.PutBucketCname{
|
||||
Cname: getDeployString(a.option.DeployConfig, "domain"),
|
||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||
Certificate: a.option.Certificate.Certificate,
|
||||
PrivateKey: a.option.Certificate.PrivateKey,
|
||||
Force: true,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("deploy aliyun oss error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (a *aliyun) contacts() ([]*cas20200407.ListContactResponseBodyContactList, error) {
|
||||
listContactRequest := &cas20200407.ListContactRequest{}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
resp, err := a.client.ListContactWithOptions(listContactRequest, runtime)
|
||||
func (a *aliyun) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
|
||||
client, err := oss.New(
|
||||
getDeployString(a.option.DeployConfig, "endpoint"),
|
||||
accessKeyId,
|
||||
accessKeySecret,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("create aliyun client error: %w", err)
|
||||
}
|
||||
if resp.Body.TotalCount == nil {
|
||||
return nil, errors.New("no contact found")
|
||||
}
|
||||
|
||||
return resp.Body.ContactList, nil
|
||||
}
|
||||
|
||||
func (a *aliyun) deploy(resource *cas20200407.ListCloudResourcesResponseBodyData, certId int64, contacts []*cas20200407.ListContactResponseBodyContactList) (*int64, error) {
|
||||
contactIds := make([]string, 0, len(contacts))
|
||||
for _, contact := range contacts {
|
||||
contactIds = append(contactIds, fmt.Sprintf("%d", *contact.ContactId))
|
||||
}
|
||||
// 部署证书
|
||||
createCloudResourceRequest := &cas20200407.CreateDeploymentJobRequest{
|
||||
CertIds: tea.String(fmt.Sprintf("%d", certId)),
|
||||
Name: tea.String(a.option.Domain + rand.RandStr(6)),
|
||||
JobType: tea.String("user"),
|
||||
ResourceIds: tea.String(fmt.Sprintf("%d", *resource.Id)),
|
||||
ContactIds: tea.String(strings.Join(contactIds, ",")),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
resp, err := a.client.CreateDeploymentJobWithOptions(createCloudResourceRequest, runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body.JobId, nil
|
||||
}
|
||||
|
||||
func (a *aliyun) uploadCert(cert *applicant.Certificate) (int64, error) {
|
||||
uploadUserCertificateRequest := &cas20200407.UploadUserCertificateRequest{
|
||||
Cert: &cert.Certificate,
|
||||
Key: &cert.PrivateKey,
|
||||
Name: tea.String(a.option.Domain + rand.RandStr(6)),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
resp, err := a.client.UploadUserCertificateWithOptions(uploadUserCertificateRequest, runtime)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return *resp.Body.CertId, nil
|
||||
}
|
||||
|
||||
func (a *aliyun) createClient(accessKeyId, accessKeySecret string) (_result *cas20200407.Client, _err error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKeyId),
|
||||
AccessKeySecret: tea.String(accessKeySecret),
|
||||
}
|
||||
config.Endpoint = tea.String("cas.aliyuncs.com")
|
||||
_result = &cas20200407.Client{}
|
||||
_result, _err = cas20200407.NewClient(config)
|
||||
return _result, _err
|
||||
}
|
||||
|
||||
func (a *aliyun) resource() (*cas20200407.ListCloudResourcesResponseBodyData, error) {
|
||||
|
||||
listCloudResourcesRequest := &cas20200407.ListCloudResourcesRequest{
|
||||
CloudProduct: tea.String(a.option.Product),
|
||||
Keyword: tea.String(a.option.Domain),
|
||||
}
|
||||
|
||||
resp, err := a.client.ListCloudResources(listCloudResourcesRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if *resp.Body.Total == 0 {
|
||||
return nil, errors.New("no resource found")
|
||||
}
|
||||
|
||||
return resp.Body.Data[0], nil
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/rand"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -46,9 +47,9 @@ func (a *AliyunCdn) GetInfo() []string {
|
||||
|
||||
func (a *AliyunCdn) Deploy(ctx context.Context) error {
|
||||
|
||||
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
|
||||
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
|
||||
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(a.option.Domain),
|
||||
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
|
||||
CertName: tea.String(certName),
|
||||
CertType: tea.String("upload"),
|
||||
SSLProtocol: tea.String("on"),
|
||||
|
||||
@@ -7,6 +7,7 @@ package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/rand"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -51,9 +52,9 @@ func (a *AliyunEsa) GetInfo() []string {
|
||||
|
||||
func (a *AliyunEsa) Deploy(ctx context.Context) error {
|
||||
|
||||
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
|
||||
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
|
||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(a.option.Domain),
|
||||
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
|
||||
CertName: tea.String(certName),
|
||||
CertType: tea.String("upload"),
|
||||
SSLProtocol: tea.String("on"),
|
||||
|
||||
@@ -2,8 +2,8 @@ package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/applicant"
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/app"
|
||||
"certimate/internal/utils/variables"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -30,6 +30,7 @@ type DeployerOption struct {
|
||||
Product string `json:"product"`
|
||||
Access string `json:"access"`
|
||||
AceessRecord *models.Record `json:"-"`
|
||||
DeployConfig domain.DeployConfig `json:"deployConfig"`
|
||||
Certificate applicant.Certificate `json:"certificate"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
@@ -42,52 +43,29 @@ type Deployer interface {
|
||||
|
||||
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
|
||||
rs := make([]Deployer, 0)
|
||||
|
||||
if record.GetString("targetAccess") != "" {
|
||||
singleDeployer, err := Get(record, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs = append(rs, singleDeployer)
|
||||
if record.GetString("deployConfig") == "" {
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
if record.GetString("group") != "" {
|
||||
group := record.ExpandedOne("group")
|
||||
|
||||
if errs := app.GetApp().Dao().ExpandRecord(group, []string{"access"}, nil); len(errs) > 0 {
|
||||
|
||||
errList := make([]error, 0)
|
||||
for name, err := range errs {
|
||||
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
|
||||
}
|
||||
err := errors.Join(errList...)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := group.ExpandedAll("access")
|
||||
|
||||
deployers, err := getByGroup(record, cert, records...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs = append(rs, deployers...)
|
||||
deployConfigs := make([]domain.DeployConfig, 0)
|
||||
|
||||
err := record.UnmarshalJSONField("deployConfig", &deployConfigs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析部署配置失败: %w", err)
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
if len(deployConfigs) == 0 {
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
}
|
||||
for _, deployConfig := range deployConfigs {
|
||||
|
||||
func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...*models.Record) ([]Deployer, error) {
|
||||
deployer, err := getWithDeployConfig(record, cert, deployConfig)
|
||||
|
||||
rs := make([]Deployer, 0)
|
||||
|
||||
for _, access := range accesses {
|
||||
deployer, err := getWithAccess(record, cert, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs = append(rs, deployer)
|
||||
}
|
||||
|
||||
@@ -95,15 +73,21 @@ func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...
|
||||
|
||||
}
|
||||
|
||||
func getWithAccess(record *models.Record, cert *applicant.Certificate, access *models.Record) (Deployer, error) {
|
||||
func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, deployConfig domain.DeployConfig) (Deployer, error) {
|
||||
|
||||
access, err := app.GetApp().Dao().FindRecordById("access", deployConfig.Access)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("access record not found: %w", err)
|
||||
}
|
||||
|
||||
option := &DeployerOption{
|
||||
DomainId: record.Id,
|
||||
Domain: record.GetString("domain"),
|
||||
Product: getProduct(record),
|
||||
Product: getProduct(deployConfig.Type),
|
||||
Access: access.GetString("config"),
|
||||
AceessRecord: access,
|
||||
Variables: variables.Parse2Map(record.GetString("variables")),
|
||||
DeployConfig: deployConfig,
|
||||
}
|
||||
if cert != nil {
|
||||
option.Certificate = *cert
|
||||
@@ -114,7 +98,7 @@ func getWithAccess(record *models.Record, cert *applicant.Certificate, access *m
|
||||
}
|
||||
}
|
||||
|
||||
switch record.GetString("targetType") {
|
||||
switch deployConfig.Type {
|
||||
case targetAliyunOss:
|
||||
return NewAliyun(option)
|
||||
case targetAliyunCdn:
|
||||
@@ -136,16 +120,8 @@ func getWithAccess(record *models.Record, cert *applicant.Certificate, access *m
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
|
||||
access := record.ExpandedOne("targetAccess")
|
||||
|
||||
return getWithAccess(record, cert, access)
|
||||
}
|
||||
|
||||
func getProduct(record *models.Record) string {
|
||||
targetType := record.GetString("targetType")
|
||||
rs := strings.Split(targetType, "-")
|
||||
func getProduct(t string) string {
|
||||
rs := strings.Split(t, "-")
|
||||
if len(rs) < 2 {
|
||||
return ""
|
||||
}
|
||||
@@ -159,3 +135,39 @@ func toStr(tag string, data any) string {
|
||||
byts, _ := json.Marshal(data)
|
||||
return tag + ":" + string(byts)
|
||||
}
|
||||
|
||||
func getDeployString(conf domain.DeployConfig, key string) string {
|
||||
if _, ok := conf.Config[key]; !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
val, ok := conf.Config[key].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func getDeployVariables(conf domain.DeployConfig) map[string]string {
|
||||
rs := make(map[string]string)
|
||||
data, ok := conf.Config["variables"]
|
||||
if !ok {
|
||||
return rs
|
||||
}
|
||||
|
||||
bts, _ := json.Marshal(data)
|
||||
|
||||
kvData := make([]domain.KV, 0)
|
||||
|
||||
if err := json.Unmarshal(bts, &kvData); err != nil {
|
||||
return rs
|
||||
}
|
||||
|
||||
for _, kv := range kvData {
|
||||
rs[kv.Key] = kv.Value
|
||||
}
|
||||
|
||||
return rs
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@ import (
|
||||
)
|
||||
|
||||
type localAccess struct {
|
||||
Command string `json:"command"`
|
||||
CertPath string `json:"certPath"`
|
||||
KeyPath string `json:"keyPath"`
|
||||
}
|
||||
|
||||
type local struct {
|
||||
@@ -41,18 +38,27 @@ func (l *local) Deploy(ctx context.Context) error {
|
||||
if err := json.Unmarshal([]byte(l.option.Access), access); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preCommand := getDeployString(l.option.DeployConfig, "preCommand")
|
||||
|
||||
if preCommand != "" {
|
||||
if err := execCmd(preCommand); err != nil {
|
||||
return fmt.Errorf("执行前置命令失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
if err := copyFile(l.option.Certificate.Certificate, access.CertPath); err != nil {
|
||||
if err := copyFile(l.option.Certificate.Certificate, getDeployString(l.option.DeployConfig, "certPath")); err != nil {
|
||||
return fmt.Errorf("复制证书失败: %w", err)
|
||||
}
|
||||
|
||||
if err := copyFile(l.option.Certificate.PrivateKey, access.KeyPath); err != nil {
|
||||
if err := copyFile(l.option.Certificate.PrivateKey, getDeployString(l.option.DeployConfig, "keyPath")); err != nil {
|
||||
return fmt.Errorf("复制私钥失败: %w", err)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
|
||||
if err := execCmd(access.Command); err != nil {
|
||||
if err := execCmd(getDeployString(l.option.DeployConfig, "command")); err != nil {
|
||||
return fmt.Errorf("执行命令失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func (q *qiuniu) Deploy(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (q *qiuniu) enableHttps(certId string) error {
|
||||
path := fmt.Sprintf("/domain/%s/sslize", q.option.Domain)
|
||||
path := fmt.Sprintf("/domain/%s/sslize", getDeployString(q.option.DeployConfig, "domain"))
|
||||
|
||||
body := &modifyDomainCertReq{
|
||||
CertID: certId,
|
||||
@@ -104,7 +104,7 @@ type domainInfo struct {
|
||||
}
|
||||
|
||||
func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
|
||||
path := fmt.Sprintf("/domain/%s", q.option.Domain)
|
||||
path := fmt.Sprintf("/domain/%s", getDeployString(q.option.DeployConfig, "domain"))
|
||||
|
||||
res, err := q.req(qiniuGateway+path, http.MethodGet, nil)
|
||||
if err != nil {
|
||||
@@ -135,8 +135,8 @@ func (q *qiuniu) uploadCert() (string, error) {
|
||||
path := "/sslcert"
|
||||
|
||||
body := &uploadCertReq{
|
||||
Name: q.option.Domain,
|
||||
CommonName: q.option.Domain,
|
||||
Name: getDeployString(q.option.DeployConfig, "domain"),
|
||||
CommonName: getDeployString(q.option.DeployConfig, "domain"),
|
||||
Pri: q.option.Certificate.PrivateKey,
|
||||
Ca: q.option.Certificate.Certificate,
|
||||
}
|
||||
@@ -166,7 +166,7 @@ type modifyDomainCertReq struct {
|
||||
}
|
||||
|
||||
func (q *qiuniu) modifyDomainCert(certId string) error {
|
||||
path := fmt.Sprintf("/domain/%s/httpsconf", q.option.Domain)
|
||||
path := fmt.Sprintf("/domain/%s/httpsconf", getDeployString(q.option.DeployConfig, "domain"))
|
||||
|
||||
body := &modifyDomainCertReq{
|
||||
CertID: certId,
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
xpath "path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
sshPkg "golang.org/x/crypto/ssh"
|
||||
@@ -19,15 +18,11 @@ type ssh struct {
|
||||
}
|
||||
|
||||
type sshAccess struct {
|
||||
Host string `json:"host"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Key string `json:"key"`
|
||||
Port string `json:"port"`
|
||||
PreCommand string `json:"preCommand"`
|
||||
Command string `json:"command"`
|
||||
CertPath string `json:"certPath"`
|
||||
KeyPath string `json:"keyPath"`
|
||||
Host string `json:"host"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Key string `json:"key"`
|
||||
Port string `json:"port"`
|
||||
}
|
||||
|
||||
func NewSSH(option *DeployerOption) (Deployer, error) {
|
||||
@@ -50,16 +45,6 @@ func (s *ssh) Deploy(ctx context.Context) error {
|
||||
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将证书路径和命令中的变量替换为实际值
|
||||
for k, v := range s.option.Variables {
|
||||
key := fmt.Sprintf("${%s}", k)
|
||||
access.CertPath = strings.ReplaceAll(access.CertPath, key, v)
|
||||
access.KeyPath = strings.ReplaceAll(access.KeyPath, key, v)
|
||||
access.Command = strings.ReplaceAll(access.Command, key, v)
|
||||
access.PreCommand = strings.ReplaceAll(access.PreCommand, key, v)
|
||||
}
|
||||
|
||||
// 连接
|
||||
client, err := s.getClient(access)
|
||||
if err != nil {
|
||||
@@ -70,29 +55,30 @@ func (s *ssh) Deploy(ctx context.Context) error {
|
||||
s.infos = append(s.infos, toStr("ssh连接成功", nil))
|
||||
|
||||
// 执行前置命令
|
||||
if access.PreCommand != "" {
|
||||
err, stdout, stderr := s.sshExecCommand(client, access.PreCommand)
|
||||
preCommand := getDeployString(s.option.DeployConfig, "preCommand")
|
||||
if preCommand != "" {
|
||||
err, stdout, stderr := s.sshExecCommand(client, preCommand)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传证书
|
||||
if err := s.upload(client, s.option.Certificate.Certificate, access.CertPath); err != nil {
|
||||
if err := s.upload(client, s.option.Certificate.Certificate, getDeployString(s.option.DeployConfig, "certPath")); err != nil {
|
||||
return fmt.Errorf("failed to upload certificate: %w", err)
|
||||
}
|
||||
|
||||
s.infos = append(s.infos, toStr("ssh上传证书成功", nil))
|
||||
|
||||
// 上传私钥
|
||||
if err := s.upload(client, s.option.Certificate.PrivateKey, access.KeyPath); err != nil {
|
||||
if err := s.upload(client, s.option.Certificate.PrivateKey, getDeployString(s.option.DeployConfig, "keyPath")); err != nil {
|
||||
return fmt.Errorf("failed to upload private key: %w", err)
|
||||
}
|
||||
|
||||
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
|
||||
|
||||
// 执行命令
|
||||
err, stdout, stderr := s.sshExecCommand(client, access.Command)
|
||||
err, stdout, stderr := s.sshExecCommand(client, getDeployString(s.option.DeployConfig, "command"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import (
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/rand"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
|
||||
)
|
||||
|
||||
type tencentCdn struct {
|
||||
@@ -76,7 +76,7 @@ func (t *tencentCdn) uploadCert() (string, error) {
|
||||
request.CertificatePublicKey = common.StringPtr(t.option.Certificate.Certificate)
|
||||
request.CertificatePrivateKey = common.StringPtr(t.option.Certificate.PrivateKey)
|
||||
request.Alias = common.StringPtr(t.option.Domain + "_" + rand.RandStr(6))
|
||||
request.Repeatable = common.BoolPtr(true)
|
||||
request.Repeatable = common.BoolPtr(false)
|
||||
|
||||
response, err := client.UploadCertificate(request)
|
||||
if err != nil {
|
||||
@@ -92,8 +92,6 @@ func (t *tencentCdn) deploy(certId string) error {
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
client, _ := ssl.NewClient(t.credential, "", cpf)
|
||||
|
||||
|
||||
|
||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
||||
request := ssl.NewDeployCertificateInstanceRequest()
|
||||
|
||||
@@ -102,7 +100,8 @@ func (t *tencentCdn) deploy(certId string) error {
|
||||
request.Status = common.Int64Ptr(1)
|
||||
|
||||
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
|
||||
if(strings.Contains(t.option.Domain, "*")){
|
||||
domain := getDeployString(t.option.DeployConfig, "domain")
|
||||
if strings.Contains(domain, "*") {
|
||||
list, errGetList := t.getDomainList()
|
||||
if errGetList != nil {
|
||||
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
|
||||
@@ -111,8 +110,8 @@ func (t *tencentCdn) deploy(certId string) error {
|
||||
return fmt.Errorf("failed to get certificate domain list: empty list.")
|
||||
}
|
||||
request.InstanceIdList = common.StringPtrs(list)
|
||||
}else{ // 否则直接使用传入的域名
|
||||
request.InstanceIdList = common.StringPtrs([]string{t.option.Domain})
|
||||
} else { // 否则直接使用传入的域名
|
||||
request.InstanceIdList = common.StringPtrs([]string{domain})
|
||||
}
|
||||
|
||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
||||
@@ -134,7 +133,6 @@ func (t *tencentCdn) getDomainList() ([]string, error) {
|
||||
|
||||
cert := base64.StdEncoding.EncodeToString([]byte(t.option.Certificate.Certificate))
|
||||
request.Cert = &cert
|
||||
|
||||
|
||||
response, err := client.DescribeCertDomains(request)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,9 +14,10 @@ type webhookAccess struct {
|
||||
}
|
||||
|
||||
type hookData struct {
|
||||
Domain string `json:"domain"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Domain string `json:"domain"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
|
||||
type webhook struct {
|
||||
@@ -50,6 +51,7 @@ func (w *webhook) Deploy(ctx context.Context) error {
|
||||
Domain: w.option.Domain,
|
||||
Certificate: w.option.Certificate.Certificate,
|
||||
PrivateKey: w.option.Certificate.PrivateKey,
|
||||
Variables: getDeployVariables(w.option.DeployConfig),
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(data)
|
||||
|
||||
20
internal/domain/domains.go
Normal file
20
internal/domain/domains.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package domain
|
||||
|
||||
type ApplyConfig struct {
|
||||
Email string `json:"email"`
|
||||
Access string `json:"access"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
Nameservers string `json:"nameservers"`
|
||||
}
|
||||
|
||||
type DeployConfig struct {
|
||||
Id string `json:"id"`
|
||||
Access string `json:"access"`
|
||||
Type string `json:"type"`
|
||||
Config map[string]any `json:"config"`
|
||||
}
|
||||
|
||||
type KV struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"certimate/internal/deployer"
|
||||
"certimate/internal/utils/app"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -41,18 +40,6 @@ func deploy(ctx context.Context, record *models.Record) error {
|
||||
return err
|
||||
}
|
||||
history.record(checkPhase, "获取记录成功", nil)
|
||||
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess", "group"}, nil); len(errs) > 0 {
|
||||
|
||||
errList := make([]error, 0)
|
||||
for name, err := range errs {
|
||||
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
|
||||
}
|
||||
err = errors.Join(errList...)
|
||||
app.GetApp().Logger().Error("展开记录失败", "err", err)
|
||||
history.record(checkPhase, "获取授权信息失败", &RecordInfo{Err: err})
|
||||
return err
|
||||
}
|
||||
history.record(checkPhase, "获取授权信息成功", nil)
|
||||
|
||||
cert := currRecord.GetString("certificate")
|
||||
expiredAt := currRecord.GetDateTime("expiredAt").Time()
|
||||
@@ -106,6 +93,13 @@ func deploy(ctx context.Context, record *models.Record) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 没有部署配置,也算成功
|
||||
if len(deployers) == 0 {
|
||||
history.record(deployPhase, "没有部署配置", &RecordInfo{Info: []string{"没有部署配置"}})
|
||||
history.setWholeSuccess(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, deployer := range deployers {
|
||||
if err = deployer.Deploy(ctx); err != nil {
|
||||
|
||||
|
||||
731
migrations/1728778480_collections_snapshot.go
Normal file
731
migrations/1728778480_collections_snapshot.go
Normal file
@@ -0,0 +1,731 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-10-08 06:50:56.637Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "iuaerpl2",
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ukkhuw85",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "v98eebqq",
|
||||
"name": "crontab",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "alc8e9ow",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "topsc9bj",
|
||||
"name": "certUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "vixgq072",
|
||||
"name": "certStableUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g3a3sza5",
|
||||
"name": "privateKey",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "gr6iouny",
|
||||
"name": "certificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "tk6vnrmn",
|
||||
"name": "issuerCertificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "sjo6ibse",
|
||||
"name": "csr",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "x03n1bkj",
|
||||
"name": "expiredAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"aliyun-dcdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn",
|
||||
"local"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xy7yk0mb",
|
||||
"name": "targetAccess",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "6jqeyggw",
|
||||
"name": "enabled",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hdsjcchf",
|
||||
"name": "deployed",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "aiya3rev",
|
||||
"name": "rightnow",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ixznmhzc",
|
||||
"name": "lastDeployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ghtlkn5j",
|
||||
"name": "lastDeployment",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "0a1o4e6sstp694f",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "zfnyj9he",
|
||||
"name": "variables",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "1bspzuku",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g65gfh7a",
|
||||
"name": "nameservers",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "wwrzc3jo",
|
||||
"name": "applyConfig",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "474iwy8r",
|
||||
"name": "deployConfig",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-10-11 13:55:13.777Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "geeur58v",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "iql7jpwx",
|
||||
"name": "config",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hwy7m03o",
|
||||
"name": "configType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun",
|
||||
"tencent",
|
||||
"huaweicloud",
|
||||
"qiniu",
|
||||
"cloudflare",
|
||||
"namesilo",
|
||||
"godaddy",
|
||||
"local",
|
||||
"ssh",
|
||||
"webhook"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lr33hiwg",
|
||||
"name": "deleted",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "c8egzzwj",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-09-26 12:29:38.334Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "farvlzk7",
|
||||
"name": "domain",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "z3p974ainxjqlvs",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "jx5f69i3",
|
||||
"name": "log",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "qbxdtg9q",
|
||||
"name": "phase",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"check",
|
||||
"apply",
|
||||
"deploy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "rglrp1hz",
|
||||
"name": "phaseSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lt1g1blu",
|
||||
"name": "deployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "wledpzgb",
|
||||
"name": "wholeSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-09-26 12:29:38.334Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dy6ccjb60spfy6p",
|
||||
"created": "2024-09-12 23:12:21.677Z",
|
||||
"updated": "2024-09-26 12:29:38.334Z",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "1tcmdsdf",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "f9wyhypi",
|
||||
"name": "content",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "teolp9pl72dxlxq",
|
||||
"created": "2024-09-13 12:51:05.611Z",
|
||||
"updated": "2024-09-26 12:29:38.334Z",
|
||||
"name": "access_groups",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "7sajiv6i",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xp8admif",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]`
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
1
ui/dist/assets/index-CV_7sKTK.css
vendored
Normal file
1
ui/dist/assets/index-CV_7sKTK.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-DOft-CKV.css
vendored
1
ui/dist/assets/index-DOft-CKV.css
vendored
File diff suppressed because one or more lines are too long
324
ui/dist/assets/index-DipHpsma.js
vendored
Normal file
324
ui/dist/assets/index-DipHpsma.js
vendored
Normal file
File diff suppressed because one or more lines are too long
332
ui/dist/assets/index-DpHAV802.js
vendored
332
ui/dist/assets/index-DpHAV802.js
vendored
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||
<script type="module" crossorigin src="/assets/index-DpHAV802.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DOft-CKV.css">
|
||||
<script type="module" crossorigin src="/assets/index-DipHpsma.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CV_7sKTK.css">
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<div id="root"></div>
|
||||
|
||||
38
ui/package-lock.json
generated
38
ui/package-lock.json
generated
@@ -30,9 +30,11 @@
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"immer": "^10.1.1",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.417.0",
|
||||
"moment": "^2.30.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"pocketbase": "^0.21.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -3780,6 +3782,15 @@
|
||||
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
|
||||
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@@ -4159,9 +4170,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.0.7.tgz",
|
||||
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -4169,10 +4180,10 @@
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
@@ -4561,6 +4572,23 @@
|
||||
"resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/postcss/node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
||||
@@ -29,22 +29,24 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"immer": "^10.1.1",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.417.0",
|
||||
"moment": "^2.30.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"pocketbase": "^0.21.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-i18next": "^15.0.2",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^0.9.1",
|
||||
"zod": "^3.23.8",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"react-i18next": "^15.0.2"
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
export default function LocaleToggle() {
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -22,7 +22,7 @@ export default function LocaleToggle() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{Object.keys(i18n.store.data).map(key => (
|
||||
{Object.keys(i18n.store.data).map((key) => (
|
||||
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
|
||||
{i18n.store.data[key].name as string}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -14,7 +14,6 @@ export function ThemeToggle() {
|
||||
const { setTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -26,13 +25,13 @@ export function ThemeToggle() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
{t('theme.light')}
|
||||
{t("common.theme.light")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
{t('theme.dark')}
|
||||
{t("common.theme.dark")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
{t('theme.system')}
|
||||
{t("common.theme.system")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, AliyunConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
AliyunConfig,
|
||||
getUsageByConfigType,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
|
||||
@@ -35,10 +40,19 @@ const AccessAliyunForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
accessKeyId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
accessSecretId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_secret.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: AliyunConfig = {
|
||||
@@ -51,7 +65,7 @@ const AccessAliyunForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "aliyun",
|
||||
accessKeyId: config.accessKeyId,
|
||||
accessSecretId: config.accessKeySecret,
|
||||
@@ -71,7 +85,7 @@ const AccessAliyunForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -117,9 +131,12 @@ const AccessAliyunForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -132,7 +149,7 @@ const AccessAliyunForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -147,7 +164,7 @@ const AccessAliyunForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -162,9 +179,12 @@ const AccessAliyunForm = ({
|
||||
name="accessKeyId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.access_key_id.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -177,9 +197,12 @@ const AccessAliyunForm = ({
|
||||
name="accessSecretId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.access_key_secret.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -190,7 +213,7 @@ const AccessAliyunForm = ({
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
CloudflareConfig,
|
||||
getUsageByConfigType,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -34,9 +39,15 @@ const AccessCloudflareForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
dnsApiToken: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: CloudflareConfig = {
|
||||
@@ -48,7 +59,7 @@ const AccessCloudflareForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "cloudflare",
|
||||
dnsApiToken: config.dnsApiToken,
|
||||
},
|
||||
@@ -67,7 +78,7 @@ const AccessCloudflareForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -111,9 +122,12 @@ const AccessCloudflareForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -126,7 +140,7 @@ const AccessCloudflareForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -141,7 +155,7 @@ const AccessCloudflareForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -156,9 +170,14 @@ const AccessCloudflareForm = ({
|
||||
name="dnsApiToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.cloud_dns_api_token.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -167,7 +186,7 @@ const AccessCloudflareForm = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -180,15 +180,15 @@ export function AccessEdit({
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{op == "add"
|
||||
? t("access.add")
|
||||
? t("access.authorization.add")
|
||||
: op == "edit"
|
||||
? t("access.edit")
|
||||
: t("access.copy")}
|
||||
? t("access.authorization.edit")
|
||||
: t("access.authorization.copy")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[80vh]">
|
||||
<div className="container py-3">
|
||||
<Label>{t("access.type")}</Label>
|
||||
<Label>{t("access.authorization.form.type.label")}</Label>
|
||||
|
||||
<Select
|
||||
onValueChange={(val) => {
|
||||
@@ -197,11 +197,11 @@ export function AccessEdit({
|
||||
defaultValue={configType}
|
||||
>
|
||||
<SelectTrigger className="mt-3">
|
||||
<SelectValue placeholder={t("access.type.not.empty")} />
|
||||
<SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t("access.type")}</SelectLabel>
|
||||
<SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
|
||||
{typeKeys.map((key) => (
|
||||
<SelectItem value={key} key={key}>
|
||||
<div
|
||||
|
||||
@@ -39,10 +39,19 @@ const AccessGodaddyFrom = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
apiKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
apiSecret: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.godaddy_api_secret.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: GodaddyConfig = {
|
||||
@@ -55,7 +64,7 @@ const AccessGodaddyFrom = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "godaddy",
|
||||
apiKey: config.apiKey,
|
||||
apiSecret: config.apiSecret,
|
||||
@@ -76,7 +85,7 @@ const AccessGodaddyFrom = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -120,9 +129,12 @@ const AccessGodaddyFrom = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -135,7 +147,7 @@ const AccessGodaddyFrom = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -150,7 +162,7 @@ const AccessGodaddyFrom = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -165,9 +177,12 @@ const AccessGodaddyFrom = ({
|
||||
name="apiKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.godaddy_api_key.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -180,9 +195,14 @@ const AccessGodaddyFrom = ({
|
||||
name="apiSecret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.godaddy_api_secret.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -191,7 +211,7 @@ const AccessGodaddyFrom = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -39,7 +39,10 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
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>>({
|
||||
@@ -80,7 +83,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('access.group.add')}</DialogTitle>
|
||||
<DialogTitle>{t("access.group.add")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="container py-3">
|
||||
@@ -97,9 +100,13 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.group.name')}</FormLabel>
|
||||
<FormLabel>{t("access.group.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" />
|
||||
<Input
|
||||
placeholder={t("access.group.form.name.errmsg.empty")}
|
||||
{...field}
|
||||
type="text"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -108,7 +115,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -48,7 +48,7 @@ const AccessGroupList = () => {
|
||||
reloadAccessGroups();
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: t('delete.failed'),
|
||||
title: t("common.delete.failed.message"),
|
||||
description: getErrMessage(e),
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -69,10 +69,10 @@ const AccessGroupList = () => {
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
{t('access.group.domain.empty')}
|
||||
{t("access.group.domains.nodata")}
|
||||
</div>
|
||||
<AccessGroupEdit
|
||||
trigger={<Button>{t('access.group.add')}</Button>}
|
||||
trigger={<Button>{t("access.group.add")}</Button>}
|
||||
className="mt-3"
|
||||
/>
|
||||
</div>
|
||||
@@ -86,7 +86,11 @@ const AccessGroupList = () => {
|
||||
<CardHeader>
|
||||
<CardTitle>{accessGroup.name}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })}
|
||||
{t("access.group.total", {
|
||||
total: accessGroup.expand
|
||||
? accessGroup.expand.access.length
|
||||
: 0,
|
||||
})}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="min-h-[180px]">
|
||||
@@ -120,9 +124,7 @@ const AccessGroupList = () => {
|
||||
<div>
|
||||
<Group size={40} />
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
{t('access.group.empty')}
|
||||
</div>
|
||||
<div className="ml-2">{t("access.group.nodata")}</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -149,7 +151,7 @@ const AccessGroupList = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('access.all')}
|
||||
{t("access.group.domains")}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -157,14 +159,14 @@ const AccessGroupList = () => {
|
||||
<Show
|
||||
when={
|
||||
!accessGroup.expand ||
|
||||
accessGroup.expand.access.length == 0
|
||||
accessGroup.expand.access.length == 0
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Button size="sm" onClick={handleAddAccess}>
|
||||
{t('access.add')}
|
||||
{t("access.authorization.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -173,21 +175,21 @@ const AccessGroupList = () => {
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant={"destructive"} size={"sm"}>
|
||||
{t('delete')}
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="dark:text-gray-200">
|
||||
{t('access.group.delete')}
|
||||
{t("access.group.delete")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('access.group.delete.confirm')}
|
||||
{t("access.group.delete.confirm")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="dark:text-gray-200">
|
||||
{t('cancel')}
|
||||
{t("common.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
@@ -196,7 +198,7 @@ const AccessGroupList = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t('confirm')}
|
||||
{t("common.confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
HuaweicloudConfig,
|
||||
getUsageByConfigType,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
|
||||
@@ -35,11 +40,23 @@ const AccessHuaweicloudForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
region: z.string().min(1, 'access.form.region.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
secretAccessKey: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
region: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.region.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
accessKeyId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_id.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
secretAccessKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.access_key_secret.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: HuaweicloudConfig = {
|
||||
@@ -53,7 +70,7 @@ const AccessHuaweicloudForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "huaweicloud",
|
||||
region: config.region,
|
||||
accessKeyId: config.accessKeyId,
|
||||
@@ -75,7 +92,7 @@ const AccessHuaweicloudForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -121,9 +138,12 @@ const AccessHuaweicloudForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -136,7 +156,7 @@ const AccessHuaweicloudForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -151,7 +171,7 @@ const AccessHuaweicloudForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -166,9 +186,12 @@ const AccessHuaweicloudForm = ({
|
||||
name="region"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.region')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.region.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.region.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -181,9 +204,12 @@ const AccessHuaweicloudForm = ({
|
||||
name="accessKeyId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.access_key_id.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -196,9 +222,12 @@ const AccessHuaweicloudForm = ({
|
||||
name="secretAccessKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.access_key_secret.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -209,7 +238,7 @@ const AccessHuaweicloudForm = ({
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
LocalConfig,
|
||||
SSHConfig,
|
||||
} from "@/domain/access";
|
||||
import { Access, accessFormType, getUsageByConfigType } from "@/domain/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -20,7 +14,7 @@ import {
|
||||
} from "../ui/form";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
|
||||
import { save } from "@/repository/access";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
@@ -39,30 +33,19 @@ const AccessLocalForm = ({
|
||||
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
|
||||
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
});
|
||||
|
||||
let config: LocalConfig = {
|
||||
command: "sudo service nginx restart",
|
||||
certPath: "/etc/nginx/ssl/certificate.crt",
|
||||
keyPath: "/etc/nginx/ssl/private.key",
|
||||
};
|
||||
if (data) config = data.config as SSHConfig;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "local",
|
||||
certPath: config.certPath,
|
||||
keyPath: config.keyPath,
|
||||
command: config.command,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -73,15 +56,11 @@ const AccessLocalForm = ({
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
|
||||
config: {
|
||||
command: data.command,
|
||||
certPath: data.certPath,
|
||||
keyPath: data.keyPath,
|
||||
},
|
||||
config: {},
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -128,9 +107,12 @@ const AccessLocalForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -143,7 +125,7 @@ const AccessLocalForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -158,7 +140,7 @@ const AccessLocalForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -168,55 +150,10 @@ const AccessLocalForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="certPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="keyPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="command"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, getUsageByConfigType, NamesiloConfig } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
NamesiloConfig,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -34,9 +39,15 @@ const AccessNamesiloForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
apiKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.namesilo_api_key.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: NamesiloConfig = {
|
||||
@@ -48,7 +59,7 @@ const AccessNamesiloForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "namesilo",
|
||||
apiKey: config.apiKey,
|
||||
},
|
||||
@@ -66,7 +77,7 @@ const AccessNamesiloForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -110,9 +121,12 @@ const AccessNamesiloForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -125,7 +139,7 @@ const AccessNamesiloForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -140,7 +154,7 @@ const AccessNamesiloForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -155,9 +169,12 @@ const AccessNamesiloForm = ({
|
||||
name="apiKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.namesilo_api_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.namesilo_api_key.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -166,7 +183,7 @@ const AccessNamesiloForm = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, getUsageByConfigType, QiniuConfig } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
QiniuConfig,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
|
||||
@@ -35,10 +40,13 @@ const AccessQiniuForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64),
|
||||
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64),
|
||||
accessKey: z.string().min(1, "access.authorization.form.access_key.placeholder").max(64),
|
||||
secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64),
|
||||
});
|
||||
|
||||
let config: QiniuConfig = {
|
||||
@@ -51,7 +59,7 @@ const AccessQiniuForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "qiniu",
|
||||
accessKey: config.accessKey,
|
||||
secretKey: config.secretKey,
|
||||
@@ -71,7 +79,7 @@ const AccessQiniuForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -116,9 +124,12 @@ const AccessQiniuForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -131,7 +142,7 @@ const AccessQiniuForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -146,7 +157,7 @@ const AccessQiniuForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -161,9 +172,12 @@ const AccessQiniuForm = ({
|
||||
name="accessKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.access.key')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.access_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.access.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.access_key.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -176,9 +190,12 @@ const AccessQiniuForm = ({
|
||||
name="secretKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.secret.key')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.secret_key.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -189,7 +206,7 @@ const AccessQiniuForm = ({
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from "../ui/form";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { save } from "@/repository/access";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
@@ -66,27 +65,37 @@ const AccessSSHForm = ({
|
||||
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
host: z.string().refine(
|
||||
(str) => {
|
||||
return ipReg.test(str) || domainReg.test(str);
|
||||
},
|
||||
{
|
||||
message: "zod.rule.ssh.host",
|
||||
message: "common.errmsg.host_invalid",
|
||||
}
|
||||
),
|
||||
group: z.string().optional(),
|
||||
port: z.string().min(1, 'access.form.ssh.port.not.empty').max(5, t('zod.rule.string.max', { max: 5 })),
|
||||
username: z.string().min(1, 'username.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
password: z.string().min(0, 'password.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
key: z.string().min(0, 'access.form.ssh.key.not.empty').max(20480, t('zod.rule.string.max', { max: 20480 })),
|
||||
port: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.ssh_port.placeholder")
|
||||
.max(5, t("common.errmsg.string_max", { max: 5 })),
|
||||
username: z
|
||||
.string()
|
||||
.min(1, "username.not.empty")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
password: z
|
||||
.string()
|
||||
.min(0, "password.not.empty")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
key: z
|
||||
.string()
|
||||
.min(0, "access.authorization.form.ssh_key.placeholder")
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 })),
|
||||
keyFile: z.any().optional(),
|
||||
|
||||
preCommand: z.string().min(0).max(2048, t('zod.rule.string.max', { max: 2048 })).optional(),
|
||||
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
|
||||
});
|
||||
|
||||
let config: SSHConfig = {
|
||||
@@ -96,10 +105,6 @@ const AccessSSHForm = ({
|
||||
password: "",
|
||||
key: "",
|
||||
keyFile: "",
|
||||
preCommand: "",
|
||||
command: "sudo service nginx restart",
|
||||
certPath: "/etc/nginx/ssl/certificate.crt",
|
||||
keyPath: "/etc/nginx/ssl/private.key",
|
||||
};
|
||||
if (data) config = data.config as SSHConfig;
|
||||
|
||||
@@ -107,7 +112,7 @@ const AccessSSHForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "ssh",
|
||||
group: data?.group,
|
||||
host: config.host,
|
||||
@@ -116,10 +121,6 @@ const AccessSSHForm = ({
|
||||
password: config.password,
|
||||
key: config.key,
|
||||
keyFile: config.keyFile,
|
||||
certPath: config.certPath,
|
||||
keyPath: config.keyPath,
|
||||
command: config.command,
|
||||
preCommand: config.preCommand,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -139,15 +140,11 @@ const AccessSSHForm = ({
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
key: data.key,
|
||||
command: data.command,
|
||||
preCommand: data.preCommand,
|
||||
certPath: data.certPath,
|
||||
keyPath: data.keyPath,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -228,9 +225,16 @@ const AccessSSHForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.name.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.name.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -244,12 +248,12 @@ const AccessSSHForm = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>{t('access.form.ssh.group.label')}</div>
|
||||
<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('add')}
|
||||
{t("common.add")}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -264,7 +268,11 @@ const AccessSSHForm = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('access.group.not.empty')} />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"access.authorization.form.access_group.placeholder"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="emptyId">
|
||||
@@ -304,7 +312,9 @@ const AccessSSHForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.config.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -319,7 +329,9 @@ const AccessSSHForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.config.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -334,9 +346,16 @@ const AccessSSHForm = ({
|
||||
name="host"
|
||||
render={({ field }) => (
|
||||
<FormItem className="grow">
|
||||
<FormLabel>{t('access.form.ssh.host')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.ssh_host.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.host.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_host.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -349,10 +368,14 @@ const AccessSSHForm = ({
|
||||
name="port"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.port')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.ssh_port.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('access.form.ssh.port.not.empty')}
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_port.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
type="number"
|
||||
/>
|
||||
@@ -369,9 +392,16 @@ const AccessSSHForm = ({
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('username')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.username.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('username.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.username.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -384,10 +414,14 @@ const AccessSSHForm = ({
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('password')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.password.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('password.not.empty')}
|
||||
placeholder={t(
|
||||
"access.authorization.form.password.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
@@ -403,9 +437,16 @@ const AccessSSHForm = ({
|
||||
name="key"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden>
|
||||
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.ssh_key.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_key.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -418,7 +459,9 @@ const AccessSSHForm = ({
|
||||
name="keyFile"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("access.authorization.form.ssh_key.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<div>
|
||||
<Button
|
||||
@@ -428,10 +471,16 @@ const AccessSSHForm = ({
|
||||
className="w-48"
|
||||
onClick={handleSelectFileClick}
|
||||
>
|
||||
{fileName ? fileName : t('access.form.ssh.key.file.not.empty')}
|
||||
{fileName
|
||||
? fileName
|
||||
: t(
|
||||
"access.authorization.form.ssh_key_file.placeholder"
|
||||
)}
|
||||
</Button>
|
||||
<Input
|
||||
placeholder={t('access.form.ssh.key.not.empty')}
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_key.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
@@ -447,70 +496,10 @@ const AccessSSHForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="certPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="keyPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="preCommand"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.pre.command')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder={t('access.form.ssh.pre.command.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="command"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, getUsageByConfigType, TencentConfig } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
TencentConfig,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -34,10 +39,19 @@ const AccessTencentForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
secretId: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.secret_id.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
secretKey: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.secret_key.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
});
|
||||
|
||||
let config: TencentConfig = {
|
||||
@@ -50,7 +64,7 @@ const AccessTencentForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "tencent",
|
||||
secretId: config.secretId,
|
||||
secretKey: config.secretKey,
|
||||
@@ -70,7 +84,7 @@ const AccessTencentForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -113,9 +127,12 @@ const AccessTencentForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -128,7 +145,7 @@ const AccessTencentForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -143,7 +160,7 @@ const AccessTencentForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -158,9 +175,12 @@ const AccessTencentForm = ({
|
||||
name="secretId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.secret.id')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.secret_id.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.secret_id.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -173,9 +193,12 @@ const AccessTencentForm = ({
|
||||
name="secretKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.secret.key')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.secret_key.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -184,7 +207,7 @@ const AccessTencentForm = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, getUsageByConfigType, WebhookConfig } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
WebhookConfig,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -34,9 +39,12 @@ const WebhookForm = ({
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
|
||||
name: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.name.placeholder")
|
||||
.max(64, t("common.errmsg.string_max", { max: 64 })),
|
||||
configType: accessFormType,
|
||||
url: z.string().url('zod.rule.url'),
|
||||
url: z.string().url("common.errmsg.url_invalid"),
|
||||
});
|
||||
|
||||
let config: WebhookConfig = {
|
||||
@@ -48,7 +56,7 @@ const WebhookForm = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || '',
|
||||
name: data?.name || "",
|
||||
configType: "webhook",
|
||||
url: config.url,
|
||||
},
|
||||
@@ -66,7 +74,7 @@ const WebhookForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
req.id = op == "copy" ? "" : req.id;
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
@@ -110,9 +118,12 @@ const WebhookForm = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.name.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.name.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -125,7 +136,7 @@ const WebhookForm = ({
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -140,7 +151,7 @@ const WebhookForm = ({
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('access.form.config.field')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
@@ -155,9 +166,12 @@ const WebhookForm = ({
|
||||
name="url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('access.form.webhook.url')}</FormLabel>
|
||||
<FormLabel>{t("access.authorization.form.webhook_url.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} />
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.webhook_url.placeholder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -166,7 +180,7 @@ const WebhookForm = ({
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
850
ui/src/components/certimate/DeployList.tsx
Normal file
850
ui/src/components/certimate/DeployList.tsx
Normal file
@@ -0,0 +1,850 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { EditIcon, Plus, Trash2 } from "lucide-react";
|
||||
import {
|
||||
DeployConfig,
|
||||
KVType,
|
||||
targetTypeKeys,
|
||||
targetTypeMap,
|
||||
} from "@/domain/domain";
|
||||
import Show from "../Show";
|
||||
import { Alert, AlertDescription } from "../ui/alert";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
|
||||
import { Label } from "../ui/label";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import { accessTypeMap } from "@/domain/access";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AccessEdit } from "./AccessEdit";
|
||||
import { Input } from "../ui/input";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import KVList from "./KVList";
|
||||
import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
type DeployEditContextProps = {
|
||||
deploy: DeployConfig;
|
||||
error: Record<string, string>;
|
||||
setDeploy: (deploy: DeployConfig) => void;
|
||||
setError: (error: Record<string, string>) => void;
|
||||
};
|
||||
|
||||
const DeployEditContext = createContext<DeployEditContextProps>(
|
||||
{} as DeployEditContextProps
|
||||
);
|
||||
|
||||
export const useDeployEditContext = () => {
|
||||
return useContext(DeployEditContext);
|
||||
};
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type DeployItemProps = {
|
||||
item: DeployConfig;
|
||||
onDelete: () => void;
|
||||
onSave: (deploy: DeployConfig) => void;
|
||||
};
|
||||
const DeployItem = ({ item, onDelete, onSave }: DeployItemProps) => {
|
||||
const {
|
||||
config: { accesses },
|
||||
} = useConfig();
|
||||
const { t } = useTranslation();
|
||||
const access = accesses.find((access) => access.id === item.access);
|
||||
const getImg = () => {
|
||||
if (!access) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const accessType = accessTypeMap.get(access.configType);
|
||||
|
||||
if (accessType) {
|
||||
return accessType[1];
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const getTypeName = () => {
|
||||
if (!access) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const accessType = targetTypeMap.get(item.type);
|
||||
|
||||
if (accessType) {
|
||||
return t(accessType[0]);
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
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={getImg()} 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 DeployEditDialogProps = {
|
||||
trigger: React.ReactNode;
|
||||
deployConfig?: DeployConfig;
|
||||
onSave: (deploy: DeployConfig) => void;
|
||||
};
|
||||
const DeployEditDialog = ({
|
||||
trigger,
|
||||
deployConfig,
|
||||
onSave,
|
||||
}: DeployEditDialogProps) => {
|
||||
const {
|
||||
config: { accesses },
|
||||
} = useConfig();
|
||||
|
||||
const [deployType, setDeployType] = useState<TargetType>();
|
||||
|
||||
const [locDeployConfig, setLocDeployConfig] = useState<DeployConfig>({
|
||||
access: "",
|
||||
type: "",
|
||||
});
|
||||
|
||||
const [error, setError] = useState<Record<string, string>>({});
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (deployConfig) {
|
||||
setLocDeployConfig({ ...deployConfig });
|
||||
} else {
|
||||
setLocDeployConfig({
|
||||
access: "",
|
||||
type: "",
|
||||
});
|
||||
}
|
||||
}, [deployConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
const temp = locDeployConfig.type.split("-");
|
||||
console.log(temp);
|
||||
let t;
|
||||
if (temp && temp.length > 1) {
|
||||
t = temp[1];
|
||||
} else {
|
||||
t = locDeployConfig.type;
|
||||
}
|
||||
setDeployType(t as TargetType);
|
||||
setError({});
|
||||
}, [locDeployConfig.type]);
|
||||
|
||||
const setDeploy = useCallback(
|
||||
(deploy: DeployConfig) => {
|
||||
if (deploy.type !== locDeployConfig.type) {
|
||||
setLocDeployConfig({ ...deploy, access: "", config: {} });
|
||||
} else {
|
||||
setLocDeployConfig({ ...deploy });
|
||||
}
|
||||
},
|
||||
[locDeployConfig.type]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const targetAccesses = accesses.filter((item) => {
|
||||
if (item.usage == "apply") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (locDeployConfig.type == "") {
|
||||
return true;
|
||||
}
|
||||
const types = locDeployConfig.type.split("-");
|
||||
return item.configType === types[0];
|
||||
});
|
||||
|
||||
const handleSaveClick = () => {
|
||||
// 验证数据
|
||||
// 保存数据
|
||||
// 清理数据
|
||||
// 关闭弹框
|
||||
const newError = { ...error };
|
||||
if (locDeployConfig.type === "") {
|
||||
newError.type = t("domain.deployment.form.access.placeholder");
|
||||
} else {
|
||||
newError.type = "";
|
||||
}
|
||||
|
||||
if (locDeployConfig.access === "") {
|
||||
newError.access = t("domain.deployment.form.access.placeholder");
|
||||
} else {
|
||||
newError.access = "";
|
||||
}
|
||||
setError(newError);
|
||||
|
||||
for (const key in newError) {
|
||||
if (newError[key] !== "") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onSave(locDeployConfig);
|
||||
|
||||
setLocDeployConfig({
|
||||
access: "",
|
||||
type: "",
|
||||
});
|
||||
|
||||
setError({});
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DeployEditContext.Provider
|
||||
value={{
|
||||
deploy: locDeployConfig,
|
||||
setDeploy: setDeploy,
|
||||
error: error,
|
||||
setError: setError,
|
||||
}}
|
||||
>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger>{trigger}</DialogTrigger>
|
||||
<DialogContent className="dark:text-stone-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("history.page.title")}</DialogTitle>
|
||||
<DialogDescription></DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 部署方式 */}
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.type.label")}</Label>
|
||||
|
||||
<Select
|
||||
value={locDeployConfig.type}
|
||||
onValueChange={(val: string) => {
|
||||
setDeploy({ ...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>
|
||||
{targetTypeKeys.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={targetTypeMap.get(item)?.[1]}
|
||||
/>
|
||||
<div>{t(targetTypeMap.get(item)?.[0] ?? "")}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="text-red-500 text-sm mt-1">{error.type}</div>
|
||||
</div>
|
||||
|
||||
{/* 授权配置 */}
|
||||
<div>
|
||||
<Label className="flex justify-between">
|
||||
<div>{t("domain.deployment.form.access.label")}</div>
|
||||
<AccessEdit
|
||||
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) => {
|
||||
setDeploy({ ...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={accessTypeMap.get(item.configType)?.[1]}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="text-red-500 text-sm mt-1">{error.access}</div>
|
||||
</div>
|
||||
|
||||
{/* 其他参数 */}
|
||||
<DeployEdit type={deployType!} />
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DeployEditContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
type TargetType = "ssh" | "cdn" | "webhook" | "local" | "oss" | "dcdn";
|
||||
|
||||
type DeployEditProps = {
|
||||
type: TargetType;
|
||||
};
|
||||
const DeployEdit = ({ type }: DeployEditProps) => {
|
||||
const getDeploy = () => {
|
||||
switch (type) {
|
||||
case "ssh":
|
||||
return <DeploySSH />;
|
||||
case "local":
|
||||
return <DeploySSH />;
|
||||
case "cdn":
|
||||
return <DeployCDN />;
|
||||
case "dcdn":
|
||||
return <DeployCDN />;
|
||||
case "oss":
|
||||
return <DeployOSS />;
|
||||
case "webhook":
|
||||
return <DeployWebhook />;
|
||||
default:
|
||||
return <DeployCDN />;
|
||||
}
|
||||
};
|
||||
return getDeploy();
|
||||
};
|
||||
|
||||
const DeploySSH = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setError } = useDeployEditContext();
|
||||
|
||||
useEffect(() => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
const { deploy: data, setDeploy } = useDeployEditContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (!data.id) {
|
||||
setDeploy({
|
||||
...data,
|
||||
config: {
|
||||
certPath: "/etc/nginx/ssl/nginx.crt",
|
||||
keyPath: "/etc/nginx/ssl/nginx.key",
|
||||
preCommand: "",
|
||||
command: "sudo service nginx reload",
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<Label>{t("access.authorization.form.ssh_cert_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.ssh_cert_path.label")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.certPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.certPath = e.target.value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>{t("access.authorization.form.ssh_key_path.label")}</Label>
|
||||
<Input
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_key_path.placeholder"
|
||||
)}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.keyPath}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.keyPath = e.target.value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("access.authorization.form.ssh_pre_command.label")}</Label>
|
||||
<Textarea
|
||||
className="mt-1"
|
||||
value={data?.config?.preCommand}
|
||||
placeholder={t(
|
||||
"access.authorization.form.ssh_pre_command.placeholder"
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.preCommand = e.target.value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
></Textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>{t("access.authorization.form.ssh_command.label")}</Label>
|
||||
<Textarea
|
||||
className="mt-1"
|
||||
value={data?.config?.command}
|
||||
placeholder={t("access.authorization.form.ssh_command.placeholder")}
|
||||
onChange={(e) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.command = e.target.value;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
></Textarea>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DeployCDN = () => {
|
||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const resp = domainSchema.safeParse(data.config?.domain);
|
||||
if (!resp.success) {
|
||||
setError({
|
||||
...error,
|
||||
domain: JSON.parse(resp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
domain: "",
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const domainSchema = z
|
||||
.string()
|
||||
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: t("common.errmsg.domain_invalid"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
|
||||
<Input
|
||||
placeholder={t(
|
||||
"domain.deployment.form.cdn_domain.placeholder"
|
||||
)}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.domain}
|
||||
onChange={(e) => {
|
||||
const temp = e.target.value;
|
||||
|
||||
const resp = domainSchema.safeParse(temp);
|
||||
if (!resp.success) {
|
||||
setError({
|
||||
...error,
|
||||
domain: JSON.parse(resp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
domain: "",
|
||||
});
|
||||
}
|
||||
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.domain = temp;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeployOSS = () => {
|
||||
const { deploy: data, setDeploy, error, setError } = useDeployEditContext();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const resp = domainSchema.safeParse(data.config?.domain);
|
||||
if (!resp.success) {
|
||||
setError({
|
||||
...error,
|
||||
domain: JSON.parse(resp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
domain: "",
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const bucketResp = bucketSchema.safeParse(data.config?.domain);
|
||||
if (!bucketResp.success) {
|
||||
setError({
|
||||
...error,
|
||||
bucket: JSON.parse(bucketResp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
bucket: "",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data.id) {
|
||||
setDeploy({
|
||||
...data,
|
||||
config: {
|
||||
endpoint: "oss-cn-hangzhou.aliyuncs.com",
|
||||
bucket: "",
|
||||
domain: "",
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const domainSchema = z
|
||||
.string()
|
||||
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: t("common.errmsg.domain_invalid"),
|
||||
});
|
||||
|
||||
const bucketSchema = z.string().min(1, {
|
||||
message: t("domain.deployment.form.oss_bucket.placeholder"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>
|
||||
<Label>{t("domain.deployment.form.oss_endpoint.label")}</Label>
|
||||
|
||||
<Input
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.endpoint}
|
||||
onChange={(e) => {
|
||||
const temp = e.target.value;
|
||||
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.endpoint = temp;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.endpoint}</div>
|
||||
|
||||
<Label>{t("domain.deployment.form.oss_bucket")}</Label>
|
||||
<Input
|
||||
placeholder={t(
|
||||
"domain.deployment.form.oss_bucket.placeholder"
|
||||
)}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.bucket}
|
||||
onChange={(e) => {
|
||||
const temp = e.target.value;
|
||||
|
||||
const resp = bucketSchema.safeParse(temp);
|
||||
if (!resp.success) {
|
||||
setError({
|
||||
...error,
|
||||
bucket: JSON.parse(resp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
bucket: "",
|
||||
});
|
||||
}
|
||||
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.bucket = temp;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.bucket}</div>
|
||||
|
||||
<Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.cdn_domain.label")}
|
||||
className="w-full mt-1"
|
||||
value={data?.config?.domain}
|
||||
onChange={(e) => {
|
||||
const temp = e.target.value;
|
||||
|
||||
const resp = domainSchema.safeParse(temp);
|
||||
if (!resp.success) {
|
||||
setError({
|
||||
...error,
|
||||
domain: JSON.parse(resp.error.message)[0].message,
|
||||
});
|
||||
} else {
|
||||
setError({
|
||||
...error,
|
||||
domain: "",
|
||||
});
|
||||
}
|
||||
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.domain = temp;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-600 text-sm mt-1">{error?.domain}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeployWebhook = () => {
|
||||
const { deploy: data, setDeploy } = useDeployEditContext();
|
||||
|
||||
const { setError } = useDeployEditContext();
|
||||
|
||||
useEffect(() => {
|
||||
setError({});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<KVList
|
||||
variables={data?.config?.variables}
|
||||
onValueChange={(variables: KVType[]) => {
|
||||
const newData = produce(data, (draft) => {
|
||||
if (!draft.config) {
|
||||
draft.config = {};
|
||||
}
|
||||
draft.config.variables = variables;
|
||||
});
|
||||
setDeploy(newData);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeployList;
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Separator } from "../ui/separator";
|
||||
|
||||
@@ -16,58 +15,52 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
|
||||
let step = 0;
|
||||
|
||||
if (phase === "check") {
|
||||
step = 1
|
||||
step = 1;
|
||||
} else if (phase === "apply") {
|
||||
step = 2
|
||||
step = 2;
|
||||
} else if (phase === "deploy") {
|
||||
step = 3
|
||||
step = 3;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div className={
|
||||
cn(
|
||||
<div
|
||||
className={cn(
|
||||
"text-xs text-nowrap",
|
||||
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
|
||||
step > 1 ? "text-green-600" : "",
|
||||
)
|
||||
}>
|
||||
{t('deploy.progress.check')}
|
||||
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(
|
||||
<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('deploy.progress.apply')}
|
||||
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(
|
||||
<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('deploy.progress.deploy')}
|
||||
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;
|
||||
|
||||
@@ -43,7 +43,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email("email.valid.message"),
|
||||
email: z.string().email("common.errmsg.email_invalid"),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -56,7 +56,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
|
||||
form.setError("email", {
|
||||
message: "email.already.exist",
|
||||
message: "common.errmsg.email_duplicate",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('email.add')}</DialogTitle>
|
||||
<DialogTitle>{t("domain.application.form.email.add")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="container py-3">
|
||||
@@ -120,9 +120,13 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('email')}</FormLabel>
|
||||
<FormLabel>{t("domain.application.form.email.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('email.not.empty.message')} {...field} type="email" />
|
||||
<Input
|
||||
placeholder={t("common.errmsg.email_empty")}
|
||||
{...field}
|
||||
type="email"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -131,7 +135,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('save')}</Button>
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
252
ui/src/components/certimate/KVList.tsx
Normal file
252
ui/src/components/certimate/KVList.tsx
Normal file
@@ -0,0 +1,252 @@
|
||||
import { KVType } from "@/domain/domain";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Label } from "../ui/label";
|
||||
import { Edit, Plus, Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Show from "../Show";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
import { produce } from "immer";
|
||||
|
||||
type KVListProps = {
|
||||
variables?: KVType[];
|
||||
onValueChange?: (variables: KVType[]) => void;
|
||||
};
|
||||
|
||||
const KVList = ({ variables, onValueChange }: KVListProps) => {
|
||||
const [locVariables, setLocVariables] = useState<KVType[]>([]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (variables) {
|
||||
setLocVariables(variables);
|
||||
}
|
||||
}, [variables]);
|
||||
|
||||
const handleAddClick = (variable: KVType) => {
|
||||
// 查看是否存在key,存在则更新,不存在则添加
|
||||
const index = locVariables.findIndex((item) => {
|
||||
return item.key === variable.key;
|
||||
});
|
||||
|
||||
const newList = produce(locVariables, (draft) => {
|
||||
if (index === -1) {
|
||||
draft.push(variable);
|
||||
} else {
|
||||
draft[index] = variable;
|
||||
}
|
||||
});
|
||||
|
||||
setLocVariables(newList);
|
||||
|
||||
onValueChange?.(newList);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (index: number) => {
|
||||
const newList = [...locVariables];
|
||||
newList.splice(index, 1);
|
||||
setLocVariables(newList);
|
||||
|
||||
onValueChange?.(newList);
|
||||
};
|
||||
|
||||
const handleEditClick = (index: number, variable: KVType) => {
|
||||
const newList = [...locVariables];
|
||||
newList[index] = variable;
|
||||
setLocVariables(newList);
|
||||
|
||||
onValueChange?.(newList);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between dark:text-stone-200">
|
||||
<Label>{t("domain.deployment.form.variables.label")}</Label>
|
||||
<Show when={!!locVariables?.length}>
|
||||
<KVEdit
|
||||
variable={{
|
||||
key: "",
|
||||
value: "",
|
||||
}}
|
||||
trigger={
|
||||
<div className="flex items-center text-primary">
|
||||
<Plus size={16} className="cursor-pointer " />
|
||||
|
||||
<div className="text-sm ">{t("common.add")}</div>
|
||||
</div>
|
||||
}
|
||||
onSave={(variable) => {
|
||||
handleAddClick(variable);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={!!locVariables?.length}
|
||||
fallback={
|
||||
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
|
||||
<div className="text-muted-foreground">
|
||||
{t("domain.deployment.form.variables.empty")}
|
||||
</div>
|
||||
|
||||
<KVEdit
|
||||
trigger={
|
||||
<div className="flex items-center text-primary">
|
||||
<Plus size={16} className="cursor-pointer " />
|
||||
|
||||
<div className="text-sm ">{t("common.add")}</div>
|
||||
</div>
|
||||
}
|
||||
variable={{
|
||||
key: "",
|
||||
value: "",
|
||||
}}
|
||||
onSave={(variable) => {
|
||||
handleAddClick(variable);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="border p-3 rounded-md text-stone-700 text-sm dark:text-stone-200">
|
||||
{locVariables?.map((item, index) => (
|
||||
<div key={index} className="flex justify-between items-center">
|
||||
<div>
|
||||
{item.key}={item.value}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<KVEdit
|
||||
trigger={<Edit size={16} className="cursor-pointer" />}
|
||||
variable={item}
|
||||
onSave={(variable) => {
|
||||
handleEditClick(index, variable);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Trash2
|
||||
size={16}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
handleDeleteClick(index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type KVEditProps = {
|
||||
variable?: KVType;
|
||||
trigger: React.ReactNode;
|
||||
onSave: (variable: KVType) => void;
|
||||
};
|
||||
|
||||
const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
|
||||
const [locVariable, setLocVariable] = useState<KVType>({
|
||||
key: "",
|
||||
value: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (variable) setLocVariable(variable!);
|
||||
}, [variable]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const [err, setErr] = useState<Record<string, string>>({});
|
||||
|
||||
const handleSaveClick = () => {
|
||||
if (!locVariable.key) {
|
||||
setErr({
|
||||
key: t("domain.deployment.form.variables.key.required"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!locVariable.value) {
|
||||
setErr({
|
||||
value: t("domain.deployment.form.variables.value.required"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onSave?.(locVariable);
|
||||
|
||||
setOpen(false);
|
||||
|
||||
setErr({});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
<DialogTrigger>{trigger}</DialogTrigger>
|
||||
<DialogContent className="dark:text-stone-200">
|
||||
<DialogHeader className="flex flex-col">
|
||||
<DialogTitle>{t("domain.deployment.form.variables.label")}</DialogTitle>
|
||||
|
||||
<div className="pt-5 flex flex-col items-start">
|
||||
<Label>{t("domain.deployment.form.variables.key")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.variables.key.placeholder")}
|
||||
value={locVariable?.key}
|
||||
onChange={(e) => {
|
||||
setLocVariable({ ...locVariable, key: e.target.value });
|
||||
}}
|
||||
className="w-full mt-1"
|
||||
/>
|
||||
<div className="text-red-500 text-sm mt-1">{err?.key}</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2 flex flex-col items-start">
|
||||
<Label>{t("domain.deployment.form.variables.value")}</Label>
|
||||
<Input
|
||||
placeholder={t("domain.deployment.form.variables.value.placeholder")}
|
||||
value={locVariable?.value}
|
||||
onChange={(e) => {
|
||||
setLocVariable({ ...locVariable, value: e.target.value });
|
||||
}}
|
||||
className="w-full mt-1"
|
||||
/>
|
||||
|
||||
<div className="text-red-500 text-sm mt-1">{err?.value}</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default KVList;
|
||||
@@ -20,13 +20,14 @@ import { Edit, Plus, Trash2 } from "lucide-react";
|
||||
type StringListProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
valueType?: "domain" | "ip";
|
||||
valueType?: ValueType;
|
||||
onValueChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const titles: Record<string, string> = {
|
||||
domain: "domain",
|
||||
ip: "IP",
|
||||
domain: "common.text.domain",
|
||||
ip: "common.text.ip",
|
||||
dns: "common.text.dns",
|
||||
};
|
||||
|
||||
const StringList = ({
|
||||
@@ -89,7 +90,7 @@ const StringList = ({
|
||||
<div className="flex items-center text-primary">
|
||||
<Plus size={16} className="cursor-pointer " />
|
||||
|
||||
<div className="text-sm ">{t("add")}</div>
|
||||
<div className="text-sm ">{t("common.add")}</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -100,11 +101,13 @@ const StringList = ({
|
||||
when={list.length > 0}
|
||||
fallback={
|
||||
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
|
||||
<div className="text-muted-foreground">暂未添加域名</div>
|
||||
<div className="text-muted-foreground">
|
||||
{t('common.text.' + valueType + '.empty')}
|
||||
</div>
|
||||
|
||||
<StringEdit
|
||||
value={""}
|
||||
trigger={t("add")}
|
||||
trigger={t("common.add")}
|
||||
onValueChange={addVal}
|
||||
valueType={valueType}
|
||||
/>
|
||||
@@ -150,7 +153,7 @@ const StringList = ({
|
||||
|
||||
export default StringList;
|
||||
|
||||
type ValueType = "domain" | "ip";
|
||||
type ValueType = "domain" | "dns" | "host";
|
||||
|
||||
type StringEditProps = {
|
||||
value: string;
|
||||
@@ -179,14 +182,15 @@ const StringEdit = ({
|
||||
const domainSchema = z
|
||||
.string()
|
||||
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: t("domain.not.empty.verify.message"),
|
||||
message: t("common.errmsg.domain_invalid"),
|
||||
});
|
||||
|
||||
const ipSchema = z.string().ip({ message: t("ip.not.empty.verify.message") });
|
||||
const ipSchema = z.string().ip({ message: t("common.errmsg.ip_invalid") });
|
||||
|
||||
const schedules: Record<ValueType, z.ZodString> = {
|
||||
domain: domainSchema,
|
||||
ip: ipSchema,
|
||||
dns: ipSchema,
|
||||
host: ipSchema,
|
||||
};
|
||||
|
||||
const onSaveClick = useCallback(() => {
|
||||
@@ -236,7 +240,7 @@ const StringEdit = ({
|
||||
onSaveClick();
|
||||
}}
|
||||
>
|
||||
{op === "add" ? t("add") : t("confirm")}
|
||||
{op === "add" ? t("common.add") : t("common.confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Separator } from "../ui/separator";
|
||||
import { version } from "@/domain/version";
|
||||
|
||||
const Version = () => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
||||
@@ -17,7 +17,7 @@ const Version = () => {
|
||||
className="flex items-center"
|
||||
>
|
||||
<BookOpen size={16} />
|
||||
<div className="ml-1">{t('document')}</div>
|
||||
<div className="ml-1">{t("common.menu.document")}</div>
|
||||
</a>
|
||||
<Separator orientation="vertical" className="mx-2" />
|
||||
<a
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
|
||||
import { update } from "@/repository/settings";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type DingTalkSetting = {
|
||||
id: string;
|
||||
@@ -72,15 +72,17 @@ const DingTalk = () => {
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: t('save.succeed'),
|
||||
description: t('setting.notify.config.save.succeed'),
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.config.saved.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t('save.failed'),
|
||||
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t(
|
||||
"settings.notification.config.failed.message"
|
||||
)}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -102,7 +104,7 @@ const DingTalk = () => {
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder={t('access.form.ding.access.token.placeholder')}
|
||||
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
|
||||
className="mt-2"
|
||||
value={dingtalk.data.secret}
|
||||
onChange={(e) => {
|
||||
@@ -129,7 +131,9 @@ const DingTalk = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||
<Label htmlFor="airplane-mode">
|
||||
{t("settings.notification.config.enable")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
@@ -138,7 +142,7 @@ const DingTalk = () => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t('save')}
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "@/domain/settings";
|
||||
import { getSetting, update } from "@/repository/settings";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const NotifyTemplate = () => {
|
||||
const [id, setId] = useState("");
|
||||
@@ -68,8 +68,8 @@ const NotifyTemplate = () => {
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t('save.succeed'),
|
||||
description: t('setting.notify.template.save.succeed'),
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.template.saved.message"),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ const NotifyTemplate = () => {
|
||||
/>
|
||||
|
||||
<div className="text-muted-foreground text-sm mt-1">
|
||||
{t('setting.notify.template.variables.tips.title')}
|
||||
{t("settings.notification.template.variables.tips.title")}
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
@@ -94,10 +94,10 @@ const NotifyTemplate = () => {
|
||||
}}
|
||||
></Textarea>
|
||||
<div className="text-muted-foreground text-sm mt-1">
|
||||
{t('setting.notify.template.variables.tips.content')}
|
||||
{t("settings.notification.template.variables.tips.content")}
|
||||
</div>
|
||||
<div className="flex justify-end mt-2">
|
||||
<Button onClick={handleSaveClick}>{t('save')}</Button>
|
||||
<Button onClick={handleSaveClick}>{t("common.save")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ const Telegram = () => {
|
||||
const data = getDetailTelegram();
|
||||
setTelegram({
|
||||
id: config.id ?? "",
|
||||
name: "telegram",
|
||||
name: "common.provider.telegram",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
@@ -72,15 +72,17 @@ const Telegram = () => {
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: t('save.succeed'),
|
||||
description: t('setting.notify.config.save.succeed'),
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.config.saved.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t('save.failed'),
|
||||
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t(
|
||||
"settings.notification.config.failed.message"
|
||||
)}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -130,7 +132,9 @@ const Telegram = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||
<Label htmlFor="airplane-mode">
|
||||
{t("settings.notification.config.enable")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
@@ -139,7 +143,7 @@ const Telegram = () => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t('save')}
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { update } from "@/repository/settings";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
import { isValidURL } from "@/lib/url";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type WebhookSetting = {
|
||||
id: string;
|
||||
@@ -61,8 +61,8 @@ const Webhook = () => {
|
||||
webhook.data.url = webhook.data.url.trim();
|
||||
if (!isValidURL(webhook.data.url)) {
|
||||
toast({
|
||||
title: t('save.failed'),
|
||||
description: t('setting.notify.config.save.failed.url.not.valid'),
|
||||
title: t("common.save.failed.message"),
|
||||
description: t("settings.notification.url.errmsg.invalid"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@@ -81,15 +81,17 @@ const Webhook = () => {
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: t('save.succeed'),
|
||||
description: t('setting.notify.config.save.succeed'),
|
||||
title: t("common.save.succeeded.message"),
|
||||
description: t("settings.notification.config.saved.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: t('save.failed'),
|
||||
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
|
||||
title: t("common.save.failed.message"),
|
||||
description: `${t(
|
||||
"settings.notification.config.failed.message"
|
||||
)}: ${msg}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -125,7 +127,9 @@ const Webhook = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
|
||||
<Label htmlFor="airplane-mode">
|
||||
{t("settings.notification.config.enable")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
@@ -134,7 +138,7 @@ const Webhook = () => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
{t('save')}
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
@@ -15,8 +15,8 @@ const AccordionItem = React.forwardRef<
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
@@ -35,8 +35,8 @@ const AccordionTrigger = React.forwardRef<
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
@@ -49,8 +49,8 @@ const AccordionContent = React.forwardRef<
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
));
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||
@@ -17,7 +17,7 @@ const alertVariants = cva(
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Alert = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Alert.displayName = "Alert"
|
||||
));
|
||||
Alert.displayName = "Alert";
|
||||
|
||||
const AlertTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
|
||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertTitle.displayName = "AlertTitle"
|
||||
));
|
||||
AlertTitle.displayName = "AlertTitle";
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
|
||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDescription.displayName = "AlertDescription"
|
||||
));
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
|
||||
115
ui/src/components/ui/breadcrumb.tsx
Normal file
115
ui/src/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||
Breadcrumb.displayName = "Breadcrumb"
|
||||
|
||||
const BreadcrumbList = React.forwardRef<
|
||||
HTMLOListElement,
|
||||
React.ComponentPropsWithoutRef<"ol">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbList.displayName = "BreadcrumbList"
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentPropsWithoutRef<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li
|
||||
ref={ref}
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem"
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn("transition-colors hover:text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
BreadcrumbLink.displayName = "BreadcrumbLink"
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.ComponentPropsWithoutRef<"span">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbPage.displayName = "BreadcrumbPage"
|
||||
|
||||
const BreadcrumbSeparator = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
)
|
||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
||||
|
||||
const BreadcrumbEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
}
|
||||
@@ -64,7 +64,7 @@ const PaginationPrevious = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<PaginationLink
|
||||
@@ -74,9 +74,9 @@ const PaginationPrevious = ({
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>{t('pagination.prev')}</span>
|
||||
<span>{t("common.pagination.prev")}</span>
|
||||
</PaginationLink>
|
||||
)
|
||||
);
|
||||
};
|
||||
PaginationPrevious.displayName = "PaginationPrevious";
|
||||
|
||||
@@ -84,7 +84,7 @@ const PaginationNext = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<PaginationLink
|
||||
@@ -93,26 +93,30 @@ const PaginationNext = ({
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>{t('pagination.next')}</span>
|
||||
<span>{t("common.pagination.next")}</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
)
|
||||
);
|
||||
};
|
||||
PaginationNext.displayName = "PaginationNext";
|
||||
|
||||
const PaginationEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More pages</span>
|
||||
</span>
|
||||
);
|
||||
}: React.ComponentProps<"span">) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">{t("common.pagination.more")}</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,76 +1,73 @@
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/components/ui/toast"
|
||||
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
const TOAST_LIMIT = 1;
|
||||
const TOAST_REMOVE_DELAY = 1000000;
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
action?: ToastActionElement
|
||||
}
|
||||
id: string;
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
action?: ToastActionElement;
|
||||
};
|
||||
|
||||
const actionTypes = {
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
} as const
|
||||
} as const;
|
||||
|
||||
let count = 0
|
||||
let count = 0;
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
||||
return count.toString()
|
||||
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes
|
||||
type ActionType = typeof actionTypes;
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: ActionType["ADD_TOAST"]
|
||||
toast: ToasterToast
|
||||
type: ActionType["ADD_TOAST"];
|
||||
toast: ToasterToast;
|
||||
}
|
||||
| {
|
||||
type: ActionType["UPDATE_TOAST"]
|
||||
toast: Partial<ToasterToast>
|
||||
type: ActionType["UPDATE_TOAST"];
|
||||
toast: Partial<ToasterToast>;
|
||||
}
|
||||
| {
|
||||
type: ActionType["DISMISS_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
type: ActionType["DISMISS_TOAST"];
|
||||
toastId?: ToasterToast["id"];
|
||||
}
|
||||
| {
|
||||
type: ActionType["REMOVE_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
}
|
||||
type: ActionType["REMOVE_TOAST"];
|
||||
toastId?: ToasterToast["id"];
|
||||
};
|
||||
|
||||
interface State {
|
||||
toasts: ToasterToast[]
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
toastTimeouts.delete(toastId);
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
})
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
});
|
||||
}, TOAST_REMOVE_DELAY);
|
||||
|
||||
toastTimeouts.set(toastId, timeout)
|
||||
}
|
||||
toastTimeouts.set(toastId, timeout);
|
||||
};
|
||||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
@@ -78,7 +75,7 @@ export const reducer = (state: State, action: Action): State => {
|
||||
return {
|
||||
...state,
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||
}
|
||||
};
|
||||
|
||||
case "UPDATE_TOAST":
|
||||
return {
|
||||
@@ -86,19 +83,19 @@ export const reducer = (state: State, action: Action): State => {
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action
|
||||
const { toastId } = action;
|
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
// but I'll keep it here for simplicity
|
||||
if (toastId) {
|
||||
addToRemoveQueue(toastId)
|
||||
addToRemoveQueue(toastId);
|
||||
} else {
|
||||
state.toasts.forEach((toast) => {
|
||||
addToRemoveQueue(toast.id)
|
||||
})
|
||||
addToRemoveQueue(toast.id);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -111,44 +108,44 @@ export const reducer = (state: State, action: Action): State => {
|
||||
}
|
||||
: t
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
case "REMOVE_TOAST":
|
||||
if (action.toastId === undefined) {
|
||||
return {
|
||||
...state,
|
||||
toasts: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const listeners: Array<(state: State) => void> = []
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
|
||||
let memoryState: State = { toasts: [] }
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action)
|
||||
memoryState = reducer(memoryState, action);
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState)
|
||||
})
|
||||
listener(memoryState);
|
||||
});
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
type Toast = Omit<ToasterToast, "id">;
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId()
|
||||
const id = genId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
});
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
@@ -157,36 +154,36 @@ function toast({ ...props }: Toast) {
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss()
|
||||
if (!open) dismiss();
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
const [state, setState] = React.useState<State>(memoryState)
|
||||
const [state, setState] = React.useState<State>(memoryState);
|
||||
|
||||
React.useEffect(() => {
|
||||
listeners.push(setState)
|
||||
listeners.push(setState);
|
||||
return () => {
|
||||
const index = listeners.indexOf(setState)
|
||||
const index = listeners.indexOf(setState);
|
||||
if (index > -1) {
|
||||
listeners.splice(index, 1)
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}, [state])
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast }
|
||||
export { useToast, toast };
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const accessTypeMap: Map<string, [string, string]> = new Map([
|
||||
["aliyun", ["aliyun", "/imgs/providers/aliyun.svg"]],
|
||||
["tencent", ["tencent", "/imgs/providers/tencent.svg"]],
|
||||
["huaweicloud", ["huaweicloud", "/imgs/providers/huaweicloud.svg"]],
|
||||
["qiniu", ["qiniu", "/imgs/providers/qiniu.svg"]],
|
||||
["cloudflare", ["cloudflare", "/imgs/providers/cloudflare.svg"]],
|
||||
["namesilo", ["namesilo", "/imgs/providers/namesilo.svg"]],
|
||||
["godaddy", ["go.daddy", "/imgs/providers/godaddy.svg"]],
|
||||
["local", ["local.deployment", "/imgs/providers/local.svg"]],
|
||||
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
|
||||
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
|
||||
["aliyun", ["common.provider.aliyun", "/imgs/providers/aliyun.svg"]],
|
||||
["tencent", ["common.provider.tencent", "/imgs/providers/tencent.svg"]],
|
||||
["huaweicloud", ["common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg"]],
|
||||
["qiniu", ["common.provider.qiniu", "/imgs/providers/qiniu.svg"]],
|
||||
["cloudflare", ["common.provider.cloudflare", "/imgs/providers/cloudflare.svg"]],
|
||||
["namesilo", ["common.provider.namesilo", "/imgs/providers/namesilo.svg"]],
|
||||
["godaddy", ["common.provider.godaddy", "/imgs/providers/godaddy.svg"]],
|
||||
["local", ["common.provider.local", "/imgs/providers/local.svg"]],
|
||||
["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
|
||||
["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
|
||||
]);
|
||||
|
||||
export const getProviderInfo = (t: string) => {
|
||||
@@ -30,7 +30,7 @@ export const accessFormType = z.union(
|
||||
z.literal("ssh"),
|
||||
z.literal("webhook"),
|
||||
],
|
||||
{ message: "access.not.empty" }
|
||||
{ message: "access.authorization.form.type.placeholder" }
|
||||
);
|
||||
|
||||
type AccessUsage = "apply" | "deploy" | "all";
|
||||
@@ -91,23 +91,15 @@ export type GodaddyConfig = {
|
||||
apiSecret: string;
|
||||
};
|
||||
|
||||
export type LocalConfig = {
|
||||
command: string;
|
||||
certPath: string;
|
||||
keyPath: string;
|
||||
};
|
||||
export type LocalConfig = Record<string, string>;
|
||||
|
||||
export type SSHConfig = {
|
||||
host: string;
|
||||
port: string;
|
||||
preCommand?: string;
|
||||
command: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
key?: string;
|
||||
keyFile?: string;
|
||||
certPath: string;
|
||||
keyPath: string;
|
||||
};
|
||||
|
||||
export type WebhookConfig = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Deployment, Pahse } from "./deployment";
|
||||
|
||||
export type Domain = {
|
||||
id: string;
|
||||
id?: string;
|
||||
domain: string;
|
||||
email?: string;
|
||||
crontab: string;
|
||||
access: string;
|
||||
targetAccess?: string;
|
||||
targetType: string;
|
||||
targetType?: string;
|
||||
expiredAt?: string;
|
||||
phase?: Pahse;
|
||||
phaseSuccess?: boolean;
|
||||
@@ -31,10 +31,20 @@ export type Domain = {
|
||||
deployConfig?: DeployConfig[];
|
||||
};
|
||||
|
||||
export type KVType = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type DeployConfig = {
|
||||
id?: string;
|
||||
access: string;
|
||||
type: string;
|
||||
config?: Record<string, string>;
|
||||
config?: {
|
||||
[key: string]: string;
|
||||
} & {
|
||||
variables?: KVType[];
|
||||
};
|
||||
};
|
||||
|
||||
export type ApplyConfig = {
|
||||
@@ -56,14 +66,14 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
|
||||
};
|
||||
|
||||
export const targetTypeMap: Map<string, [string, string]> = new Map([
|
||||
["aliyun-cdn", ["aliyun.cdn", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-oss", ["aliyun.oss", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-dcdn", ["aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
|
||||
["tencent-cdn", ["tencent.cdn", "/imgs/providers/tencent.svg"]],
|
||||
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
|
||||
["qiniu-cdn", ["qiniu.cdn", "/imgs/providers/qiniu.svg"]],
|
||||
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
|
||||
["local", ["local.deployment", "/imgs/providers/local.svg"]],
|
||||
["aliyun-oss", ["common.provider.aliyun.oss", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-cdn", ["common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-dcdn", ["common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
|
||||
["tencent-cdn", ["common.provider.tencent.cdn", "/imgs/providers/tencent.svg"]],
|
||||
["qiniu-cdn", ["common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"]],
|
||||
["local", ["common.provider.local", "/imgs/providers/local.svg"]],
|
||||
["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
|
||||
["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
|
||||
]);
|
||||
|
||||
export const targetTypeKeys = Array.from(targetTypeMap.keys());
|
||||
|
||||
@@ -50,8 +50,8 @@ export type NotifyChannelWebhook = {
|
||||
};
|
||||
|
||||
export const defaultNotifyTemplate: NotifyTemplate = {
|
||||
title: "您有{COUNT}张证书即将过期",
|
||||
content: "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!",
|
||||
title: "您有 {COUNT} 张证书即将过期",
|
||||
content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!",
|
||||
};
|
||||
|
||||
export type SSLProvider = "letsencrypt" | "zerossl";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const version = "Certimate v0.1.19";
|
||||
export const version = "Certimate v0.2.1";
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import resources from './locales'
|
||||
import resources from "./locales";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'zh',
|
||||
debug: true,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
backend: {
|
||||
loadPath: '/locales/{{lng}}.json',
|
||||
}
|
||||
});
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "zh",
|
||||
debug: true,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
backend: {
|
||||
loadPath: "/locales/{{lng}}.json",
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
{
|
||||
"ca": "Certificate Authority",
|
||||
"username": "Username",
|
||||
"username.not.empty": "Please enter username",
|
||||
"password": "Password",
|
||||
"password.not.empty": "Please enter password",
|
||||
"email": "Email",
|
||||
"logout": "Logout",
|
||||
"setting": "Settings",
|
||||
"account": "Account",
|
||||
"template": "Template",
|
||||
"save": "Save",
|
||||
"no.data": "No data available",
|
||||
"status": "Status",
|
||||
"operation": "Operation",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"deploy": "Deploy",
|
||||
"download": "Download",
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"edit": "Edit",
|
||||
"copy": "Copy",
|
||||
"succeed": "Successful",
|
||||
"add": "Add",
|
||||
"document": "Document",
|
||||
"variables": "Variables",
|
||||
"dns": "Domain Name Server",
|
||||
"name": "Name",
|
||||
"create.time": "CreateTime",
|
||||
"update.time": "UpdateTime",
|
||||
"created.in": "Created in",
|
||||
"updated.in": "Updated in",
|
||||
"basic.setting": "Basic Settings",
|
||||
"advanced.setting": "Advanced Settings",
|
||||
"operation.succeed": "Operation Successful",
|
||||
"save.succeed": "Save Successful",
|
||||
"save.failed": "Save Failed",
|
||||
"update.succeed": "Update Successful",
|
||||
"update.failed": "Update Failed",
|
||||
"delete.failed": "Delete Failed",
|
||||
"ding.talk": "Ding Talk",
|
||||
"telegram": "Telegram",
|
||||
"webhook": "Webhook",
|
||||
"local.deployment": "Local Deployment",
|
||||
"tencent": "Tencent",
|
||||
"tencent.cdn": "Tencent-CDN",
|
||||
"aliyun": "Alibaba Cloud",
|
||||
"aliyun.cdn": "Alibaba Cloud-CDN",
|
||||
"aliyun.oss": "Alibaba Cloud-OSS",
|
||||
"aliyun.dcdn": "Alibaba Cloud-DCDN",
|
||||
"huaweicloud": "Huawei Cloud",
|
||||
"qiniu": "Qiniu",
|
||||
"qiniu.cdn": "Qiniu-CDN",
|
||||
"cloudflare": "Cloudflare",
|
||||
"namesilo": "Namesilo",
|
||||
"go.daddy": "GoDaddy",
|
||||
"ssh": "SSH Deployment",
|
||||
"zod.rule.string.max": "Please enter no more than {{max}} characters",
|
||||
"zod.rule.url": "Please enter a valid URL",
|
||||
"zod.rule.ssh.host": "Please enter the correct domain name or IP",
|
||||
"login.submit": "Log In",
|
||||
"login.username.no.empty.message": "Please enter a valid email address",
|
||||
"login.password.length.message": "Password should be at least 10 characters",
|
||||
"menu.auth.management": "Authorization Management",
|
||||
"theme.light": "Light",
|
||||
"theme.dark": "Dark",
|
||||
"theme.system": "System",
|
||||
"dashboard": "Dashboard",
|
||||
"dashboard.all": "All",
|
||||
"dashboard.near.expired": "About to Expire",
|
||||
"dashboard.enabled": "Enabled",
|
||||
"dashboard.not.enabled": "Not Enabled",
|
||||
"dashboard.unit": "Unit",
|
||||
"deployment.log.name": "Deployment History",
|
||||
"deployment.log.empty": "You have not created any deployments yet, please add a domain to start deployment!",
|
||||
"deployment.log.status": "Status",
|
||||
"deployment.log.stage": "Stage",
|
||||
"deployment.log.last.execution.time": "Last Execution Time",
|
||||
"deployment.log.detail.button.text": "Log",
|
||||
"deployment.log.detail": "Deployment Details",
|
||||
"pagination.next": "Next",
|
||||
"pagination.prev": "Previous",
|
||||
"domain": "Domain",
|
||||
"domain.add": "Add Domain",
|
||||
"domain.edit":"Edit Domain",
|
||||
"domain.delete": "Delete Domain",
|
||||
"domain.not.empty.verify.message": "Please enter domain",
|
||||
"domain.management.name": "Domain List",
|
||||
"domain.management.start.deploy.succeed.tips": "Deployment initiated, please check the deployment log later.",
|
||||
"domain.management.execution.failed": "Execution Failed",
|
||||
"domain.management.execution.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
|
||||
"domain.management.empty": "Please add a domain to start deploying the certificate.",
|
||||
"domain.management.expiry.date": "Validity Period",
|
||||
"domain.management.expiry.date1": "Valid for {{date}} days",
|
||||
"domain.management.expiry.date2": "Expiry on {{date}}",
|
||||
"domain.management.last.execution.time": "Last Execution Time",
|
||||
"domain.management.last.execution.status": "Last Execution Status",
|
||||
"domain.management.last.execution.stage": "Last Execution Stage",
|
||||
"domain.management.enable": "Enable",
|
||||
"domain.management.start.deploying": "Deploy Now",
|
||||
"domain.management.forced.deployment": "Force Deployment",
|
||||
"domain.management.delete.confirm": "Are you sure you want to delete this domain?",
|
||||
"domain.management.edit.title": "Edit Domain",
|
||||
"domain.management.edit.dns.access.label": "DNS Provider Authorization Configuration",
|
||||
"domain.management.edit.dns.access.not.empty.message": "Please select DNS provider authorization configuration",
|
||||
"domain.management.edit.access.label": "Provider Authorization Configuration",
|
||||
"domain.management.edit.access.not.empty.message": "Please select authorization configuration",
|
||||
"domain.management.edit.target.type": "Deployment Service Type",
|
||||
"domain.management.edit.target.type.not.empty.message": "Please select deployment service type",
|
||||
"domain.management.edit.succeed.tips": "Successful domain editing",
|
||||
"domain.management.edit.target.access": "Deployment Service Provider Authorization Configuration",
|
||||
"domain.management.edit.target.access.content.label": "Provider Authorization Configuration",
|
||||
"domain.management.edit.target.access.not.empty.message": "Please select authorization configuration",
|
||||
"domain.management.edit.target.access.verify.msg": "At least one of the deployment authorization and deployment authorization group must be selected",
|
||||
"domain.management.edit.group.label": "Deployment Configuration Group (used to deploy a domain certificate to multiple ssh hosts)",
|
||||
"domain.management.edit.group.not.empty.message": "Please select group",
|
||||
"domain.management.edit.email.not.empty.message": "Please select email",
|
||||
"domain.management.edit.email.description": "(A email is required to apply for a certificate)",
|
||||
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
|
||||
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
|
||||
"domain.management.add.succeed.tips": "Domain added successfully",
|
||||
"email.add": "Add Email",
|
||||
"email.list": "Email List",
|
||||
"email.valid.message": "Please enter a valid email address",
|
||||
"email.already.exist": "Email already exists",
|
||||
"email.not.empty.message": "Please enter email",
|
||||
"setting.notify.menu": "Notification Push",
|
||||
"setting.submit": "Confirm Changes",
|
||||
"setting.account.email.valid.message": "Please enter a valid email address",
|
||||
"setting.account.email.placeholder": "Please enter email",
|
||||
"setting.account.email.change.succeed": "Account email altered successfully",
|
||||
"setting.account.email.change.failed": "Account email alteration failed",
|
||||
"setting.account.log.back.in": "Please login again",
|
||||
"setting.password.length.message": "Password should be at least 10 characters",
|
||||
"setting.password.not.match": "Passwords do not match",
|
||||
"setting.password.change.succeed": "Password changed successfully",
|
||||
"setting.password.change.failed": "Password change failed",
|
||||
"setting.password.current.password": "Current Password",
|
||||
"setting.password.new.password": "New Password",
|
||||
"setting.password.confirm.password": "Confirm Password",
|
||||
"setting.notify.template.save.succeed": "Notification template saved successfully",
|
||||
"setting.notify.template.variables.tips.title": "Optional variables, COUNT: number of expiring soon",
|
||||
"setting.notify.template.variables.tips.content": "Optional variables, COUNT: number of expiring soon, DOMAINS: Domain list",
|
||||
"setting.notify.config.enable": "Enable",
|
||||
"setting.notify.config.save.succeed": "Configuration saved successfully",
|
||||
"setting.notify.config.save.failed": "Configuration save failed",
|
||||
"setting.notify.config.save.failed.url.not.valid": "Invalid Url format",
|
||||
"setting.ca.not.empty": "Please select a Certificate Authority",
|
||||
"setting.ca.eab_kid.not.empty": "Please enter EAB_KID",
|
||||
"setting.ca.eab_hmac_key.not.empty": "Please enter EAB_HMAC_KEY.",
|
||||
"setting.ca.eab_kid_hmac_key.not.empty": "Please enter EAB_KID and EAB_HMAC_KEY",
|
||||
"deploy.progress.check": "Check",
|
||||
"deploy.progress.apply": "Apply",
|
||||
"deploy.progress.deploy": "Deploy",
|
||||
"access.management": "Authorization Management",
|
||||
"access.add": "Add Authorization",
|
||||
"access.edit": "Edit Authorization",
|
||||
"access.copy": "Copy Authorization",
|
||||
"access.delete.confirm": "Are you sure you want to delete the deployment authorization?",
|
||||
"access.all": "All Authorizations",
|
||||
"access.list": "Authorization List",
|
||||
"access.type": "Provider",
|
||||
"access.type.not.empty": "Please select a provider",
|
||||
"access.not.empty": "Please select a cloud provider",
|
||||
"access.empty": "Please add authorization to start deploying certificate.",
|
||||
"access.group.management": "Authorization Group Management",
|
||||
"access.group.add": "Add Authorization Group",
|
||||
"access.group.not.empty": "Please select a group",
|
||||
"access.group.name": "Group Name",
|
||||
"access.group.name.not.empty": "Please enter group name",
|
||||
"access.group.delete": "Delete Group",
|
||||
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
|
||||
"access.group.domain.empty": "Please add a domain to start deploying the certificate.",
|
||||
"access.group.empty": "No deployment authorization configuration yet, please add after starting use.",
|
||||
"access.group.total": "Totally {{total}} deployment authorization configuration",
|
||||
"access.form.name.not.empty": "Please enter authorization name",
|
||||
"access.form.config.field": "Configuration Type",
|
||||
"access.form.access.key.id": "AccessKeyId",
|
||||
"access.form.access.key.id.not.empty": "Please enter AccessKeyId",
|
||||
"access.form.access.key.secret": "AccessKeySecret",
|
||||
"access.form.access.key.secret.not.empty": "Please enter AccessKeySecret",
|
||||
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
|
||||
"access.form.cloud.dns.api.token.not.empty": "Please enter CLOUD_DNS_API_TOKEN",
|
||||
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
|
||||
"access.form.go.daddy.api.key.not.empty": "Please enter GO_DADDY_API_KEY",
|
||||
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
|
||||
"access.form.go.daddy.api.secret.not.empty": "Please enter GO_DADDY_API_SECRET",
|
||||
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
|
||||
"access.form.namesilo.api.key.not.empty": "Please enter NAMESILO_API_KEY",
|
||||
"access.form.secret.id": "SecretId",
|
||||
"access.form.secret.id.not.empty": "Please enter SecretId",
|
||||
"access.form.secret.key": "SecretKey",
|
||||
"access.form.secret.key.not.empty": "Please enter SecretKey",
|
||||
"access.form.access.key": "AccessKey",
|
||||
"access.form.access.key.not.empty": "Please enter AccessKey",
|
||||
"access.form.region": "Region",
|
||||
"access.form.region.not.empty": "Please enter Region",
|
||||
"access.form.webhook.url": "Webhook URL",
|
||||
"access.form.webhook.url.not.empty": "Please enter Webhook URL",
|
||||
"access.form.ssh.group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
|
||||
"access.form.ssh.host": "Server Host",
|
||||
"access.form.ssh.host.not.empty": "Please enter Host",
|
||||
"access.form.ssh.port": "SSH Port",
|
||||
"access.form.ssh.port.not.empty": "Please enter Port",
|
||||
"access.form.ssh.key": "Key (Log in using certificate)",
|
||||
"access.form.ssh.key.not.empty": "Please enter Key",
|
||||
"access.form.ssh.key.file.not.empty": "Please select file",
|
||||
"access.form.ssh.cert.path": "Certificate Save Path",
|
||||
"access.form.ssh.cert.path.not.empty": "Please enter certificate save path",
|
||||
"access.form.ssh.key.path": "Private Key Save Path",
|
||||
"access.form.ssh.key.path.not.empty": "Please enter private key save path",
|
||||
"access.form.ssh.pre.command": "Pre-deployment Command",
|
||||
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
|
||||
"access.form.ssh.command": "Command",
|
||||
"access.form.ssh.command.not.empty": "Please enter command",
|
||||
"access.form.ding.access.token.placeholder": "Signature for signed addition"
|
||||
}
|
||||
17
ui/src/i18n/locales/en/index.ts
Normal file
17
ui/src/i18n/locales/en/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import nlsCommon from "./nls.common.json";
|
||||
import nlsLogin from "./nls.login.json";
|
||||
import nlsDashboard from "./nls.dashboard.json";
|
||||
import nlsSettings from "./nls.settings.json";
|
||||
import nlsDomain from "./nls.domain.json";
|
||||
import nlsAccess from "./nls.access.json";
|
||||
import nlsHistory from "./nls.history.json";
|
||||
|
||||
export default Object.freeze({
|
||||
...nlsCommon,
|
||||
...nlsLogin,
|
||||
...nlsDashboard,
|
||||
...nlsSettings,
|
||||
...nlsDomain,
|
||||
...nlsAccess,
|
||||
...nlsHistory,
|
||||
});
|
||||
77
ui/src/i18n/locales/en/nls.access.json
Normal file
77
ui/src/i18n/locales/en/nls.access.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"access.page.title": "Authorization Management",
|
||||
|
||||
"access.authorization.tab": "Authorization",
|
||||
"access.authorization.nodata": "Please add authorization to start deploying certificate.",
|
||||
|
||||
"access.authorization.add": "Add Authorization",
|
||||
"access.authorization.edit": "Edit Authorization",
|
||||
"access.authorization.copy": "Copy Authorization",
|
||||
"access.authorization.delete": "Delete Authorization",
|
||||
"access.authorization.delete.confirm": "Are you sure you want to delete the deployment authorization?",
|
||||
|
||||
"access.authorization.form.type.label": "Provider",
|
||||
"access.authorization.form.type.placeholder": "Please select a provider",
|
||||
"access.authorization.form.type.list": "Authorization List",
|
||||
"access.authorization.form.name.label": "Name",
|
||||
"access.authorization.form.name.placeholder": "Please enter authorization name",
|
||||
"access.authorization.form.config.label": "Configuration Type",
|
||||
"access.authorization.form.region.label": "Region",
|
||||
"access.authorization.form.region.placeholder": "Please enter Region",
|
||||
"access.authorization.form.access_key_id.label": "AccessKeyId",
|
||||
"access.authorization.form.access_key_id.placeholder": "Please enter AccessKeyId",
|
||||
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
|
||||
"access.authorization.form.access_key_secret.placeholder": "Please enter AccessKeySecret",
|
||||
"access.authorization.form.access_key.label": "AccessKey",
|
||||
"access.authorization.form.access_key.placeholder": "Please enter AccessKey",
|
||||
"access.authorization.form.secret_id.label": "SecretId",
|
||||
"access.authorization.form.secret_id.placeholder": "Please enter SecretId",
|
||||
"access.authorization.form.secret_key.label": "SecretKey",
|
||||
"access.authorization.form.secret_key.placeholder": "Please enter SecretKey",
|
||||
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
|
||||
"access.authorization.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN",
|
||||
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
|
||||
"access.authorization.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY",
|
||||
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
|
||||
"access.authorization.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET",
|
||||
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
|
||||
"access.authorization.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY",
|
||||
"access.authorization.form.username.label": "Username",
|
||||
"access.authorization.form.username.placeholder": "Please enter username",
|
||||
"access.authorization.form.password.label": "Password",
|
||||
"access.authorization.form.password.placeholder": "Please enter password",
|
||||
"access.authorization.form.access_group.placeholder": "Please select a group",
|
||||
"access.authorization.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
|
||||
"access.authorization.form.ssh_host.label": "Server Host",
|
||||
"access.authorization.form.ssh_host.placeholder": "Please enter Host",
|
||||
"access.authorization.form.ssh_port.label": "SSH Port",
|
||||
"access.authorization.form.ssh_port.placeholder": "Please enter Port",
|
||||
"access.authorization.form.ssh_key.label": "Key (Log in using private key)",
|
||||
"access.authorization.form.ssh_key.placeholder": "Please enter Key",
|
||||
"access.authorization.form.ssh_key_file.placeholder": "Please select file",
|
||||
"access.authorization.form.ssh_key_path.label": "Private Key Save Path",
|
||||
"access.authorization.form.ssh_key_path.placeholder": "Please enter private key save path",
|
||||
"access.authorization.form.ssh_cert_path.label": "Certificate Save Path",
|
||||
"access.authorization.form.ssh_cert_path.placeholder": "Please enter certificate save path",
|
||||
"access.authorization.form.ssh_pre_command.label": "Pre-deployment Command",
|
||||
"access.authorization.form.ssh_pre_command.placeholder": "Command to be executed before deploying the certificate",
|
||||
"access.authorization.form.ssh_command.label": "Command",
|
||||
"access.authorization.form.ssh_command.placeholder": "Please enter command",
|
||||
"access.authorization.form.webhook_url.label": "Webhook URL",
|
||||
"access.authorization.form.webhook_url.placeholder": "Please enter Webhook URL",
|
||||
|
||||
"access.group.tab": "Authorization Group",
|
||||
|
||||
"access.group.nodata": "No deployment authorization configuration yet, please add after starting use.",
|
||||
"access.group.total": "Totally {{total}} deployment authorization configuration",
|
||||
|
||||
"access.group.add": "Add Group",
|
||||
"access.group.delete": "Delete Group",
|
||||
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
|
||||
|
||||
"access.group.form.name.label": "Group Name",
|
||||
"access.group.form.name.errmsg.empty": "Please enter group name",
|
||||
|
||||
"access.group.domains": "All Authorizations",
|
||||
"access.group.domains.nodata": "Please add a domain to start deploying the certificate."
|
||||
}
|
||||
72
ui/src/i18n/locales/en/nls.common.json
Normal file
72
ui/src/i18n/locales/en/nls.common.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"common.save": "Save",
|
||||
"common.save.succeeded.message": "Save Successful",
|
||||
"common.save.failed.message": "Save Failed",
|
||||
"common.add": "Add",
|
||||
"common.edit": "Edit",
|
||||
"common.copy": "Copy",
|
||||
"common.download": "Download",
|
||||
"common.delete": "Delete",
|
||||
"common.delete.succeeded.message": "Delete Successful",
|
||||
"common.delete.failed.message": "Delete Failed",
|
||||
"common.next": "Next",
|
||||
"common.confirm": "Confirm",
|
||||
"common.cancel": "Cancel",
|
||||
"common.submit": "Submit",
|
||||
"common.update": "Update",
|
||||
"common.update.succeeded.message": "Update Successful",
|
||||
"common.update.failed.message": "Update Failed",
|
||||
|
||||
"common.text.domain": "Domain",
|
||||
"common.text.domain.empty": "No Domain",
|
||||
"common.text.ip": "IP Address",
|
||||
"common.text.ip.empty": "No IP address",
|
||||
"common.text.dns": "Domain Name Server",
|
||||
"common.text.dns.empty": "No DNS",
|
||||
"common.text.ca": "Certificate Authority",
|
||||
"common.text.provider": "Provider",
|
||||
"common.text.name": "Name",
|
||||
"common.text.created_at": "Created At",
|
||||
"common.text.updated_at": "Updated At",
|
||||
"common.text.operations": "Operations",
|
||||
"common.text.nodata": "No data available",
|
||||
|
||||
"common.menu.settings": "Settings",
|
||||
"common.menu.logout": "Logout",
|
||||
"common.menu.document": "Document",
|
||||
|
||||
"common.pagination.next": "Next",
|
||||
"common.pagination.prev": "Previous",
|
||||
"common.pagination.more": "More pages",
|
||||
|
||||
"common.theme.light": "Light",
|
||||
"common.theme.dark": "Dark",
|
||||
"common.theme.system": "System",
|
||||
|
||||
"common.errmsg.string_max": "Please enter no more than {{max}} characters",
|
||||
"common.errmsg.email_invalid": "Please enter a valid email address",
|
||||
"common.errmsg.email_empty": "Please enter email",
|
||||
"common.errmsg.email_duplicate": "Email already exists",
|
||||
"common.errmsg.domain_invalid": "Please enter domain",
|
||||
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
|
||||
"common.errmsg.ip_invalid": "Please enter IP",
|
||||
"common.errmsg.url_invalid": "Please enter a valid URL",
|
||||
|
||||
"common.provider.aliyun": "Alibaba Cloud",
|
||||
"common.provider.aliyun.cdn": "Alibaba Cloud-CDN",
|
||||
"common.provider.aliyun.oss": "Alibaba Cloud-OSS",
|
||||
"common.provider.aliyun.dcdn": "Alibaba Cloud-DCDN",
|
||||
"common.provider.tencent": "Tencent",
|
||||
"common.provider.tencent.cdn": "Tencent-CDN",
|
||||
"common.provider.huaweicloud": "Huawei Cloud",
|
||||
"common.provider.qiniu": "Qiniu",
|
||||
"common.provider.qiniu.cdn": "Qiniu-CDN",
|
||||
"common.provider.cloudflare": "Cloudflare",
|
||||
"common.provider.namesilo": "Namesilo",
|
||||
"common.provider.godaddy": "GoDaddy",
|
||||
"common.provider.local": "Local Deployment",
|
||||
"common.provider.ssh": "SSH Deployment",
|
||||
"common.provider.webhook": "Webhook",
|
||||
"common.provider.dingtalk": "DingTalk",
|
||||
"common.provider.telegram": "Telegram"
|
||||
}
|
||||
11
ui/src/i18n/locales/en/nls.dashboard.json
Normal file
11
ui/src/i18n/locales/en/nls.dashboard.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"dashboard.page.title": "Dashboard",
|
||||
|
||||
"dashboard.statistics.all": "All",
|
||||
"dashboard.statistics.near_expired": "About to Expire",
|
||||
"dashboard.statistics.enabled": "Enabled",
|
||||
"dashboard.statistics.disabled": "Not Enabled",
|
||||
"dashboard.statistics.unit": "",
|
||||
|
||||
"dashboard.history": "Deployment History"
|
||||
}
|
||||
65
ui/src/i18n/locales/en/nls.domain.json
Normal file
65
ui/src/i18n/locales/en/nls.domain.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"domain.page.title": "Domain List",
|
||||
|
||||
"domain.nodata": "Please add a domain to start deploying the certificate.",
|
||||
|
||||
"domain.add": "Add Domain",
|
||||
"domain.edit": "Edit Domain",
|
||||
"domain.delete": "Delete Domain",
|
||||
"domain.delete.confirm": "Are you sure you want to delete this domain?",
|
||||
"domain.history": "Deployment History",
|
||||
"domain.deploy": "Deploy Now",
|
||||
"domain.deploy.started.message": "Deploy Started",
|
||||
"domain.deploy.started.tips": "Deployment initiated, please check the deployment log later.",
|
||||
"domain.deploy.failed.message": "Execution Failed",
|
||||
"domain.deploy.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
|
||||
"domain.deploy_forced": "Force Deployment",
|
||||
|
||||
"domain.props.expiry": "Validity Period",
|
||||
"domain.props.expiry.date1": "Valid for {{date}} days",
|
||||
"domain.props.expiry.date2": "Expiry on {{date}}",
|
||||
"domain.props.last_execution_status": "Last Execution Status",
|
||||
"domain.props.last_execution_stage": "Last Execution Stage",
|
||||
"domain.props.last_execution_time": "Last Execution Time",
|
||||
"domain.props.enable": "Enable",
|
||||
"domain.props.enable.enabled": "Enable",
|
||||
"domain.props.enable.disabled": "Disable",
|
||||
|
||||
"domain.application.tab": "Apply Settings",
|
||||
"domain.application.form.domain.added.message": "Domain added successfully",
|
||||
"domain.application.form.domain.changed.message": "Domain updated successfully",
|
||||
"domain.application.form.email.label": "Email",
|
||||
"domain.application.form.email.tips": "(A email is required to apply for a certificate)",
|
||||
"domain.application.form.email.add": "Add Email",
|
||||
"domain.application.form.email.list": "Email List",
|
||||
"domain.application.form.email.errmsg.empty": "Please select email",
|
||||
"domain.application.form.access.label": "DNS Provider Authorization Configuration",
|
||||
"domain.application.form.access.placeholder": "Please select DNS provider authorization configuration",
|
||||
"domain.application.form.access.errmsg.empty": "Please select DNS provider authorization configuration",
|
||||
"domain.application.form.access.list": "Provider Authorization Configurations",
|
||||
"domain.application.form.timeout.label": "Timeout",
|
||||
"domain.application.form.timeoue.placeholder": "Timeout (seconds)",
|
||||
"domain.application.unsaved.message": "Please save applyment configuration first",
|
||||
|
||||
"domain.deployment.tab": "Deploy Settings",
|
||||
"domain.deployment.nodata": "Deployment not added yet",
|
||||
"domain.deployment.form.type.label": "Deploy Method",
|
||||
"domain.deployment.form.type.placeholder": "Please select deploy method",
|
||||
"domain.deployment.form.type.list": "Deploy Method List",
|
||||
"domain.deployment.form.access.label": "Access Configuration",
|
||||
"domain.deployment.form.access.placeholder": "Please select provider authorization configuration",
|
||||
"domain.deployment.form.access.list": "Provider Authorization Configurations",
|
||||
"domain.deployment.form.cdn_domain.label": "Deploy to domain",
|
||||
"domain.deployment.form.cdn_domain.placeholder": "Please enter CDN domain",
|
||||
"domain.deployment.form.oss_endpoint.label": "Endpoint",
|
||||
"domain.deployment.form.oss_bucket": "Bucket",
|
||||
"domain.deployment.form.oss_bucket.placeholder": "Please enter Bucket",
|
||||
"domain.deployment.form.variables.label": "Variable",
|
||||
"domain.deployment.form.variables.key": "Name",
|
||||
"domain.deployment.form.variables.value": "Value",
|
||||
"domain.deployment.form.variables.empty": "Variable not added yet",
|
||||
"domain.deployment.form.variables.key.required": "Variable name cannot be empty",
|
||||
"domain.deployment.form.variables.value.required": "Variable value cannot be empty",
|
||||
"domain.deployment.form.variables.key.placeholder": "Variable name",
|
||||
"domain.deployment.form.variables.value.placeholder": "Variable value"
|
||||
}
|
||||
15
ui/src/i18n/locales/en/nls.history.json
Normal file
15
ui/src/i18n/locales/en/nls.history.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"history.page.title": "Deployment",
|
||||
|
||||
"history.nodata": "You have not created any deployments yet, please add a domain to start deployment!",
|
||||
|
||||
"history.props.domain": "Domain",
|
||||
"history.props.status": "Status",
|
||||
"history.props.stage": "Stage",
|
||||
"history.props.stage.progress.check": "Check",
|
||||
"history.props.stage.progress.apply": "Apply",
|
||||
"history.props.stage.progress.deploy": "Deploy",
|
||||
"history.props.last_execution_time": "Last Execution Time",
|
||||
|
||||
"history.log": "Log"
|
||||
}
|
||||
9
ui/src/i18n/locales/en/nls.login.json
Normal file
9
ui/src/i18n/locales/en/nls.login.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"login.username.label": "Username",
|
||||
"login.username.placeholder": "Username/Email",
|
||||
"login.username.errmsg.invalid": "Please enter a valid email address",
|
||||
"login.password.label": "Password",
|
||||
"login.password.placeholder": "Password",
|
||||
"login.password.errmsg.invalid": "Password should be at least 10 characters",
|
||||
"login.submit": "Log In"
|
||||
}
|
||||
41
ui/src/i18n/locales/en/nls.settings.json
Normal file
41
ui/src/i18n/locales/en/nls.settings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"settings.page.title": "Settings",
|
||||
|
||||
"settings.account.relogin.message": "Please login again",
|
||||
|
||||
"settings.account.tab": "Account",
|
||||
"settings.account.email.label": "Email",
|
||||
"settings.account.email.placeholder": "Please enter email",
|
||||
"settings.account.email.errmsg.invalid": "Please enter a valid email address",
|
||||
"settings.account.email.changed.message": "Account email altered successfully",
|
||||
"settings.account.email.failed.message": "Account email alteration failed",
|
||||
|
||||
"settings.password.tab": "Password",
|
||||
"settings.password.current_password.label": "Current Password",
|
||||
"settings.password.current_password.placeholder": "Please enter the current password",
|
||||
"settings.password.new_password.label": "New Password",
|
||||
"settings.password.new_password.placeholder": "Please enter the new password",
|
||||
"settings.password.confirm_password.label": "Confirm Password",
|
||||
"settings.password.confirm_password.placeholder": "Please enter the new password again",
|
||||
"settings.password.password.errmsg.length": "Password should be at least 10 characters",
|
||||
"settings.password.password.errmsg.not_matched": "Passwords do not match",
|
||||
"settings.password.changed.message": "Password changed successfully",
|
||||
"settings.password.failed.message": "Password change failed",
|
||||
|
||||
"settings.notification.tab": "Notification",
|
||||
"settings.notification.template.label": "Template",
|
||||
"settings.notification.template.saved.message": "Notification template saved successfully",
|
||||
"settings.notification.template.variables.tips.title": "Optional variables ({COUNT}: number of expiring soon)",
|
||||
"settings.notification.template.variables.tips.content": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)",
|
||||
"settings.notification.config.enable": "Enable",
|
||||
"settings.notification.config.saved.message": "Configuration saved successfully",
|
||||
"settings.notification.config.failed.message": "Configuration save failed",
|
||||
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
||||
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
||||
|
||||
"settings.ca.tab": "Certificate Authority",
|
||||
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
||||
"settings.ca.eab_kid.errmsg.empty": "Please enter EAB_KID",
|
||||
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
|
||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Resource } from 'i18next'
|
||||
import { Resource } from "i18next";
|
||||
|
||||
import zh from './zh.json'
|
||||
import en from './en.json'
|
||||
import zh from "./zh";
|
||||
import en from "./en";
|
||||
|
||||
const resources: Resource = {
|
||||
zh: {
|
||||
name: '简体中文',
|
||||
translation: zh
|
||||
},
|
||||
en: {
|
||||
name: 'English',
|
||||
translation: en
|
||||
}
|
||||
}
|
||||
zh: {
|
||||
name: "简体中文",
|
||||
translation: zh,
|
||||
},
|
||||
en: {
|
||||
name: "English",
|
||||
translation: en,
|
||||
},
|
||||
};
|
||||
|
||||
export default resources;
|
||||
export default resources;
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
{
|
||||
"ca": "证书颁发机构",
|
||||
"username": "用户名",
|
||||
"username.not.empty": "请输入用户名",
|
||||
"password": "密码",
|
||||
"password.not.empty": "请输入密码",
|
||||
"email": "邮箱",
|
||||
"logout": "退出登录",
|
||||
"setting": "设置",
|
||||
"account": "账户",
|
||||
"template": "模版",
|
||||
"save": "保存",
|
||||
"no.data": "暂无数据",
|
||||
"status": "状态",
|
||||
"operation": "操作",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"deploy": "部署",
|
||||
"download": "下载",
|
||||
"delete": "删除",
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"edit": "编辑",
|
||||
"copy": "复制",
|
||||
"succeed": "成功",
|
||||
"add": "新增",
|
||||
"document": "文档",
|
||||
"variables": "变量",
|
||||
"dns": "域名服务器",
|
||||
"name": "名称",
|
||||
"create.time": "创建时间",
|
||||
"update.time": "更新时间",
|
||||
"created.in": "创建于",
|
||||
"updated.in": "更新于",
|
||||
"basic.setting": "基础设置",
|
||||
"advanced.setting": "高级设置",
|
||||
"operation.succeed": "操作成功",
|
||||
"save.succeed": "保存成功",
|
||||
"save.failed": "保存失败",
|
||||
"update.succeed": "修改成功",
|
||||
"update.failed": "修改失败",
|
||||
"delete.failed": "删除失败",
|
||||
"ding.talk": "钉钉",
|
||||
"telegram": "Telegram",
|
||||
"webhook": "Webhook",
|
||||
"local.deployment": "本地部署",
|
||||
"tencent": "腾讯云",
|
||||
"tencent.cdn": "腾讯云-CDN",
|
||||
"aliyun": "阿里云",
|
||||
"aliyun.cdn": "阿里云-CDN",
|
||||
"aliyun.oss": "阿里云-OSS",
|
||||
"aliyun.dcdn": "阿里云-DCDN",
|
||||
"huaweicloud": "华为云",
|
||||
"qiniu": "七牛云",
|
||||
"qiniu.cdn": "七牛云-CDN",
|
||||
"cloudflare": "Cloudflare",
|
||||
"namesilo": "Namesilo",
|
||||
"go.daddy": "GoDaddy",
|
||||
"ssh": "SSH 部署",
|
||||
"zod.rule.string.max": "请输入不超过 {{max}} 个字符",
|
||||
"zod.rule.url": "请输入有效的 url 地址",
|
||||
"zod.rule.ssh.host": "请输入正确的域名或IP",
|
||||
"login.submit": "登录",
|
||||
"login.username.no.empty.message": "请输入正确的邮箱地址",
|
||||
"login.password.length.message": "密码至少10个字符",
|
||||
"menu.auth.management": "授权管理",
|
||||
"theme.light": "浅色",
|
||||
"theme.dark": "暗黑",
|
||||
"theme.system": "系统",
|
||||
"dashboard": "控制面板",
|
||||
"dashboard.all": "所有",
|
||||
"dashboard.near.expired": "即将过期",
|
||||
"dashboard.enabled": "启用中",
|
||||
"dashboard.not.enabled": "未启用",
|
||||
"dashboard.unit": "个",
|
||||
"deployment.log.name": "部署历史",
|
||||
"deployment.log.empty": "你暂未创建任何部署,请先添加域名进行部署吧!",
|
||||
"deployment.log.status": "状态",
|
||||
"deployment.log.stage": "阶段",
|
||||
"deployment.log.last.execution.time": "最近执行时间",
|
||||
"deployment.log.detail.button.text": "日志",
|
||||
"deployment.log.detail": "部署详情",
|
||||
"pagination.next": "下一页",
|
||||
"pagination.prev": "上一页",
|
||||
"domain": "域名",
|
||||
"domain.add": "新增域名",
|
||||
"domain.edit": "编辑域名",
|
||||
"domain.delete": "删除域名",
|
||||
"domain.not.empty.verify.message": "请输入域名",
|
||||
"domain.management.name": "域名列表",
|
||||
"domain.management.start.deploy.succeed.tips": "已发起部署,请稍后查看部署日志。",
|
||||
"domain.management.execution.failed": "执行失败",
|
||||
"domain.management.execution.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
|
||||
"domain.management.empty": "请添加域名开始部署证书吧。",
|
||||
"domain.management.expiry.date": "有效期限",
|
||||
"domain.management.expiry.date1": "有效期 {{date}} 天",
|
||||
"domain.management.expiry.date2": "{{date}} 到期",
|
||||
"domain.management.last.execution.time": "最近执行时间",
|
||||
"domain.management.last.execution.status": "最近执行状态",
|
||||
"domain.management.last.execution.stage": "最近执行阶段",
|
||||
"domain.management.enable": "是否启用",
|
||||
"domain.management.start.deploying": "立即部署",
|
||||
"domain.management.forced.deployment": "强行部署",
|
||||
"domain.management.delete.confirm": "确定要删除域名吗?",
|
||||
"domain.management.edit.title": "编辑域名",
|
||||
"domain.management.edit.dns.access.label": "DNS 服务商授权配置",
|
||||
"domain.management.edit.dns.access.not.empty.message": "请选择DNS服务商授权配置",
|
||||
"domain.management.edit.access.label": "服务商授权配置",
|
||||
"domain.management.edit.access.not.empty.message": "请选择授权配置",
|
||||
"domain.management.edit.target.type": "部署服务类型",
|
||||
"domain.management.edit.target.type.not.empty.message": "请选择部署服务类型",
|
||||
"domain.management.edit.succeed.tips": "域名编辑成功",
|
||||
"domain.management.edit.target.access": "部署服务商授权配置",
|
||||
"domain.management.edit.target.access.content.label": "服务商授权配置",
|
||||
"domain.management.edit.target.access.not.empty.message": "请选择授权配置",
|
||||
"domain.management.edit.target.access.verify.msg": "部署授权和部署授权组至少选一个",
|
||||
"domain.management.edit.group.label": "部署配置组(用于将一个域名证书部署到多个 ssh 主机)",
|
||||
"domain.management.edit.group.not.empty.message": "请选择分组",
|
||||
"domain.management.edit.email.not.empty.message": "请选择邮箱",
|
||||
"domain.management.edit.email.description": "(申请证书需要提供邮箱)",
|
||||
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
|
||||
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
|
||||
"domain.management.add.succeed.tips": "域名添加成功",
|
||||
"email.add": "添加邮箱",
|
||||
"email.list": "邮箱列表",
|
||||
"email.valid.message": "请输入正确的邮箱地址",
|
||||
"email.already.exist": "邮箱已存在",
|
||||
"email.not.empty.message": "请输入邮箱",
|
||||
"setting.notify.menu": "消息推送",
|
||||
"setting.submit": "确认修改",
|
||||
"setting.account.email.valid.message": "请输入正确的邮箱地址",
|
||||
"setting.account.email.placeholder": "请输入邮箱",
|
||||
"setting.account.email.change.succeed": "修改账户邮箱成功",
|
||||
"setting.account.email.change.failed": "修改账户邮箱失败",
|
||||
"setting.account.log.back.in": "请重新登录",
|
||||
"setting.password.length.message": "密码至少10个字符",
|
||||
"setting.password.not.match": "两次密码不一致",
|
||||
"setting.password.change.succeed": "修改密码成功",
|
||||
"setting.password.change.failed": "修改密码失败",
|
||||
"setting.password.current.password": "当前密码",
|
||||
"setting.password.new.password": "新密码",
|
||||
"setting.password.confirm.password": "确认密码",
|
||||
"setting.notify.template.save.succeed": "通知模板保存成功",
|
||||
"setting.notify.template.variables.tips.title": "可选的变量, COUNT:即将过期张数",
|
||||
"setting.notify.template.variables.tips.content": "可选的变量, COUNT:即将过期张数,DOMAINS:域名列表",
|
||||
"setting.notify.config.enable": "是否启用",
|
||||
"setting.notify.config.save.succeed": "配置保存成功",
|
||||
"setting.notify.config.save.failed": "配置保存失败",
|
||||
"setting.notify.config.save.failed.url.not.valid": "Url格式不正确",
|
||||
"setting.ca.not.empty": "请选择证书分发机构",
|
||||
"setting.ca.eab_kid.not.empty": "请输入EAB_KID",
|
||||
"setting.ca.eab_hmac_key.not.empty": "请输入EAB_HMAC_KEY",
|
||||
"setting.ca.eab_kid_hmac_key.not.empty": "请输入EAB_KID和EAB_HMAC_KEY",
|
||||
"deploy.progress.check": "检查",
|
||||
"deploy.progress.apply": "获取",
|
||||
"deploy.progress.deploy": "部署",
|
||||
"access.management": "授权管理",
|
||||
"access.add": "添加授权",
|
||||
"access.edit": "编辑授权",
|
||||
"access.copy": "复制授权",
|
||||
"access.delete.confirm": "确定要删除授权吗?",
|
||||
"access.all": "所有授权",
|
||||
"access.list": "授权列表",
|
||||
"access.type": "服务商",
|
||||
"access.type.not.empty": "请选择服务商",
|
||||
"access.not.empty": "请选择云服务商",
|
||||
"access.empty": "请添加授权开始部署证书吧。",
|
||||
"access.group.management": "授权组管理",
|
||||
"access.group.add": "添加授权组",
|
||||
"access.group.not.empty": "请选择分组",
|
||||
"access.group.name": "组名",
|
||||
"access.group.name.not.empty": "请输入组名",
|
||||
"access.group.delete": "删除组",
|
||||
"access.group.delete.confirm": "确定要删除部署授权组吗?",
|
||||
"access.group.domain.empty": "请添加域名开始部署证书吧。",
|
||||
"access.group.empty": "暂无部署授权配置,请添加后开始使用吧",
|
||||
"access.group.total": "共有 {{total}} 个部署授权配置",
|
||||
"access.form.name.not.empty": "请输入授权名称",
|
||||
"access.form.config.field": "配置类型",
|
||||
"access.form.access.key.id": "AccessKeyId",
|
||||
"access.form.access.key.id.not.empty": "请输入 AccessKeyId",
|
||||
"access.form.access.key.secret": "AccessKeySecret",
|
||||
"access.form.access.key.secret.not.empty": "请输入 AccessKeySecret",
|
||||
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
|
||||
"access.form.cloud.dns.api.token.not.empty": "请输入 CLOUD_DNS_API_TOKEN",
|
||||
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
|
||||
"access.form.go.daddy.api.key.not.empty": "请输入 GO_DADDY_API_KEY",
|
||||
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
|
||||
"access.form.go.daddy.api.secret.not.empty": "请输入 GO_DADDY_API_SECRET",
|
||||
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
|
||||
"access.form.namesilo.api.key.not.empty": "请输入 NAMESILO_API_KEY",
|
||||
"access.form.secret.id": "SecretId",
|
||||
"access.form.secret.id.not.empty": "请输入 SecretId",
|
||||
"access.form.secret.key": "SecretKey",
|
||||
"access.form.secret.key.not.empty": "请输入 SecretKey",
|
||||
"access.form.access.key": "AccessKey",
|
||||
"access.form.access.key.not.empty": "请输入 AccessKey",
|
||||
"access.form.region": "Region",
|
||||
"access.form.region.not.empty": "请输入区域",
|
||||
"access.form.webhook.url": "Webhook URL",
|
||||
"access.form.webhook.url.not.empty": "请输入 Webhook URL",
|
||||
"access.form.ssh.group.label": "授权配置组(用于将一个域名证书部署到多个 ssh 主机)",
|
||||
"access.form.ssh.host": "服务器 Host",
|
||||
"access.form.ssh.host.not.empty": "请输入 Host",
|
||||
"access.form.ssh.port": "SSH 端口",
|
||||
"access.form.ssh.port.not.empty": "请输入 Port",
|
||||
"access.form.ssh.key": "Key(使用证书登录)",
|
||||
"access.form.ssh.key.not.empty": "请输入 Key",
|
||||
"access.form.ssh.key.file.not.empty": "请选择文件",
|
||||
"access.form.ssh.cert.path": "证书保存路径",
|
||||
"access.form.ssh.cert.path.not.empty": "请输入证书保存路径",
|
||||
"access.form.ssh.key.path": "私钥保存路径",
|
||||
"access.form.ssh.key.path.not.empty": "请输入私钥保存路径",
|
||||
"access.form.ssh.pre.command": "前置 Command",
|
||||
"access.form.ssh.pre.command.not.empty": "在部署证书前执行的前置命令",
|
||||
"access.form.ssh.command": "Command",
|
||||
"access.form.ssh.command.not.empty": "请输入要执行的命令",
|
||||
"access.form.ding.access.token.placeholder": "加签的签名"
|
||||
}
|
||||
17
ui/src/i18n/locales/zh/index.ts
Normal file
17
ui/src/i18n/locales/zh/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import nlsCommon from "./nls.common.json";
|
||||
import nlsLogin from "./nls.login.json";
|
||||
import nlsDashboard from "./nls.dashboard.json";
|
||||
import nlsSettings from "./nls.settings.json";
|
||||
import nlsDomain from "./nls.domain.json";
|
||||
import nlsAccess from "./nls.access.json";
|
||||
import nlsHistory from "./nls.history.json";
|
||||
|
||||
export default Object.freeze({
|
||||
...nlsCommon,
|
||||
...nlsLogin,
|
||||
...nlsDashboard,
|
||||
...nlsSettings,
|
||||
...nlsDomain,
|
||||
...nlsAccess,
|
||||
...nlsHistory,
|
||||
});
|
||||
78
ui/src/i18n/locales/zh/nls.access.json
Normal file
78
ui/src/i18n/locales/zh/nls.access.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"access.page.title": "授权管理",
|
||||
|
||||
"access.authorization.tab": "授权",
|
||||
"access.authorization.nodata": "请添加授权开始部署证书吧。",
|
||||
|
||||
"access.authorization.add": "新增授权",
|
||||
"access.authorization.edit": "编辑授权",
|
||||
"access.authorization.copy": "复制授权",
|
||||
"access.authorization.delete": "删除授权",
|
||||
"access.authorization.delete.confirm": "确定要删除授权吗?",
|
||||
|
||||
"access.authorization.form.type.label": "服务商",
|
||||
"access.authorization.form.type.placeholder": "请选择服务商",
|
||||
"access.authorization.form.type.list": "服务商列表",
|
||||
"access.authorization.form.name.label": "名称",
|
||||
"access.authorization.form.name.placeholder": "请输入授权名称",
|
||||
"access.authorization.form.config.label": "配置类型",
|
||||
"access.authorization.form.region.label": "Region",
|
||||
"access.authorization.form.region.placeholder": "请输入区域",
|
||||
"access.authorization.form.access_key_id.label": "AccessKeyId",
|
||||
"access.authorization.form.access_key_id.placeholder": "请输入 AccessKeyId",
|
||||
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
|
||||
"access.authorization.form.access_key_secret.placeholder": "请输入 AccessKeySecret",
|
||||
"access.authorization.form.access_key.label": "AccessKey",
|
||||
"access.authorization.form.access_key.placeholder": "请输入 AccessKey",
|
||||
"access.authorization.form.secret_id.label": "SecretId",
|
||||
"access.authorization.form.secret_id.placeholder": "请输入 SecretId",
|
||||
"access.authorization.form.secret_key.label": "SecretKey",
|
||||
"access.authorization.form.secret_key.placeholder": "请输入 SecretKey",
|
||||
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
|
||||
"access.authorization.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN",
|
||||
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
|
||||
"access.authorization.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY",
|
||||
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
|
||||
"access.authorization.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET",
|
||||
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
|
||||
"access.authorization.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY",
|
||||
"access.authorization.form.username.label": "用户名",
|
||||
"access.authorization.form.username.placeholder": "请输入用户名",
|
||||
"access.authorization.form.password.label": "密码",
|
||||
"access.authorization.form.password.placeholder": "请输入密码",
|
||||
"access.authorization.form.access_group.placeholder": "请选择分组",
|
||||
"access.authorization.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)",
|
||||
"access.authorization.form.ssh_host.label": "服务器 Host",
|
||||
"access.authorization.form.ssh_host.placeholder": "请输入 Host",
|
||||
"access.authorization.form.ssh_port.label": "SSH 端口",
|
||||
"access.authorization.form.ssh_port.placeholder": "请输入 Port",
|
||||
"access.authorization.form.ssh_key.label": "Key(使用私钥登录)",
|
||||
"access.authorization.form.ssh_key.placeholder": "请输入 Key",
|
||||
"access.authorization.form.ssh_key_file.placeholder": "请选择文件",
|
||||
"access.authorization.form.ssh_key.label.passphrase": "私钥密码",
|
||||
"access.authorization.form.ssh_key_path.label": "私钥保存路径",
|
||||
"access.authorization.form.ssh_key_path.placeholder": "请输入私钥保存路径",
|
||||
"access.authorization.form.ssh_cert_path.label": "证书保存路径",
|
||||
"access.authorization.form.ssh_cert_path.placeholder": "请输入证书保存路径",
|
||||
"access.authorization.form.ssh_pre_command.label": "前置 Command",
|
||||
"access.authorization.form.ssh_pre_command.placeholder": "在部署证书前执行的前置命令",
|
||||
"access.authorization.form.ssh_command.label": "Command",
|
||||
"access.authorization.form.ssh_command.placeholder": "请输入要执行的命令",
|
||||
"access.authorization.form.webhook_url.label": "Webhook URL",
|
||||
"access.authorization.form.webhook_url.placeholder": "请输入 Webhook URL",
|
||||
|
||||
"access.group.tab": "授权组",
|
||||
|
||||
"access.group.nodata": "暂无部署授权配置,请添加后开始使用吧",
|
||||
"access.group.total": "共有 {{total}} 个部署授权配置",
|
||||
|
||||
"access.group.add": "添加授权组",
|
||||
"access.group.delete": "删除组",
|
||||
"access.group.delete.confirm": "确定要删除部署授权组吗?",
|
||||
|
||||
"access.group.form.name.label": "组名",
|
||||
"access.group.form.name.errmsg.empty": "请输入组名",
|
||||
|
||||
"access.group.domains": "所有授权",
|
||||
"access.group.domains.nodata": "请添加域名开始部署证书吧。"
|
||||
}
|
||||
72
ui/src/i18n/locales/zh/nls.common.json
Normal file
72
ui/src/i18n/locales/zh/nls.common.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"common.add": "新增",
|
||||
"common.save": "保存",
|
||||
"common.save.succeeded.message": "保存成功",
|
||||
"common.save.failed.message": "保存失败",
|
||||
"common.edit": "编辑",
|
||||
"common.copy": "复制",
|
||||
"common.download": "下载",
|
||||
"common.delete": "刪除",
|
||||
"common.delete.succeeded.message": "删除成功",
|
||||
"common.delete.failed.message": "删除失败",
|
||||
"common.next": "下一步",
|
||||
"common.confirm": "确认",
|
||||
"common.cancel": "取消",
|
||||
"common.submit": "提交",
|
||||
"common.update": "更新",
|
||||
"common.update.succeeded.message": "修改成功",
|
||||
"common.update.failed.message": "修改失败",
|
||||
|
||||
"common.text.domain": "域名",
|
||||
"common.text.domain.empty": "无域名",
|
||||
"common.text.ip": "IP 地址",
|
||||
"common.text.ip.empty": "无 IP 地址",
|
||||
"common.text.dns": "DNS(域名服务器)",
|
||||
"common.text.dns.empty": "无 DNS 地址",
|
||||
"common.text.ca": "CA(证书颁发机构)",
|
||||
"common.text.name": "名称",
|
||||
"common.text.provider": "服务商",
|
||||
"common.text.created_at": "创建时间",
|
||||
"common.text.updated_at": "更新时间",
|
||||
"common.text.operations": "操作",
|
||||
"common.text.nodata": "暂无数据",
|
||||
|
||||
"common.menu.settings": "系统设置",
|
||||
"common.menu.logout": "退出登录",
|
||||
"common.menu.document": "文档",
|
||||
|
||||
"common.pagination.next": "下一页",
|
||||
"common.pagination.prev": "上一页",
|
||||
"common.pagination.more": "更多",
|
||||
|
||||
"common.theme.light": "浅色",
|
||||
"common.theme.dark": "暗黑",
|
||||
"common.theme.system": "跟随系统",
|
||||
|
||||
"common.errmsg.string_max": "请输入不超过 {{max}} 个字符",
|
||||
"common.errmsg.email_empty": "请输入邮箱",
|
||||
"common.errmsg.email_invalid": "请输入正确的邮箱",
|
||||
"common.errmsg.email_duplicate": "邮箱已存在",
|
||||
"common.errmsg.domain_invalid": "请输入正确的域名",
|
||||
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
|
||||
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
|
||||
"common.errmsg.url_invalid": "请输入正确的 URL",
|
||||
|
||||
"common.provider.tencent": "腾讯云",
|
||||
"common.provider.tencent.cdn": "腾讯云-CDN",
|
||||
"common.provider.aliyun": "阿里云",
|
||||
"common.provider.aliyun.cdn": "阿里云-CDN",
|
||||
"common.provider.aliyun.oss": "阿里云-OSS",
|
||||
"common.provider.aliyun.dcdn": "阿里云-DCDN",
|
||||
"common.provider.huaweicloud": "华为云",
|
||||
"common.provider.qiniu": "七牛云",
|
||||
"common.provider.qiniu.cdn": "七牛云-CDN",
|
||||
"common.provider.cloudflare": "Cloudflare",
|
||||
"common.provider.namesilo": "Namesilo",
|
||||
"common.provider.godaddy": "GoDaddy",
|
||||
"common.provider.local": "本地部署",
|
||||
"common.provider.ssh": "SSH 部署",
|
||||
"common.provider.webhook": "Webhook",
|
||||
"common.provider.dingtalk": "钉钉",
|
||||
"common.provider.telegram": "Telegram"
|
||||
}
|
||||
11
ui/src/i18n/locales/zh/nls.dashboard.json
Normal file
11
ui/src/i18n/locales/zh/nls.dashboard.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"dashboard.page.title": "仪表盘",
|
||||
|
||||
"dashboard.statistics.all": "所有",
|
||||
"dashboard.statistics.near_expired": "即将过期",
|
||||
"dashboard.statistics.enabled": "启用中",
|
||||
"dashboard.statistics.disabled": "未启用",
|
||||
"dashboard.statistics.unit": "个",
|
||||
|
||||
"dashboard.history": "部署历史"
|
||||
}
|
||||
65
ui/src/i18n/locales/zh/nls.domain.json
Normal file
65
ui/src/i18n/locales/zh/nls.domain.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"domain.page.title": "域名列表",
|
||||
|
||||
"domain.nodata": "请添加域名开始部署证书吧。",
|
||||
|
||||
"domain.add": "新增域名",
|
||||
"domain.edit": "编辑域名",
|
||||
"domain.delete": "删除域名",
|
||||
"domain.delete.confirm": "确定要删除域名吗?",
|
||||
"domain.history": "部署历史",
|
||||
"domain.deploy": "立即部署",
|
||||
"domain.deploy.started.message": "开始部署",
|
||||
"domain.deploy.started.tips": "已发起部署,请稍后查看部署日志。",
|
||||
"domain.deploy.failed.message": "执行失败",
|
||||
"domain.deploy.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
|
||||
"domain.deploy_forced": "强行部署",
|
||||
|
||||
"domain.props.expiry": "有效期限",
|
||||
"domain.props.expiry.date1": "有效期 {{date}} 天",
|
||||
"domain.props.expiry.date2": "{{date}} 到期",
|
||||
"domain.props.last_execution_status": "最近执行状态",
|
||||
"domain.props.last_execution_stage": "最近执行阶段",
|
||||
"domain.props.last_execution_time": "最近执行时间",
|
||||
"domain.props.enable": "是否启用",
|
||||
"domain.props.enable.enabled": "启用",
|
||||
"domain.props.enable.disabled": "禁用",
|
||||
|
||||
"domain.application.tab": "申请配置",
|
||||
"domain.application.form.domain.added.message": "域名添加成功",
|
||||
"domain.application.form.domain.changed.message": "域名编辑成功",
|
||||
"domain.application.form.email.label": "邮箱",
|
||||
"domain.application.form.email.tips": "(申请证书需要提供邮箱)",
|
||||
"domain.application.form.email.add": "添加邮箱",
|
||||
"domain.application.form.email.list": "邮箱列表",
|
||||
"domain.application.form.email.errmsg.empty": "请选择邮箱",
|
||||
"domain.application.form.access.label": "DNS 服务商授权配置",
|
||||
"domain.application.form.access.placeholder": "请选择 DNS 服务商授权配置",
|
||||
"domain.application.form.access.errmsg.empty": "请选择 DNS 服务商授权配置",
|
||||
"domain.application.form.access.list": "已有的 DNS 服务商授权配置",
|
||||
"domain.application.form.timeout.label": "超时时间",
|
||||
"domain.application.form.timeoue.placeholder": "超时时间(单位:秒)",
|
||||
"domain.application.unsaved.message": "请先保存申请配置",
|
||||
|
||||
"domain.deployment.tab": "部署配置",
|
||||
"domain.deployment.nodata": "暂无部署配置,请添加后开始部署证书吧",
|
||||
"domain.deployment.form.type.label": "部署方式",
|
||||
"domain.deployment.form.type.placeholder": "请选择部署方式",
|
||||
"domain.deployment.form.type.list": "支持的部署方式",
|
||||
"domain.deployment.form.access.label": "授权配置",
|
||||
"domain.deployment.form.access.placeholder": "请选择授权配置",
|
||||
"domain.deployment.form.access.list": "已有的服务商授权配置",
|
||||
"domain.deployment.form.cdn_domain.label": "部署到域名",
|
||||
"domain.deployment.form.cdn_domain.placeholder": "请输入 CDN 域名",
|
||||
"domain.deployment.form.oss_endpoint.label": "Endpoint",
|
||||
"domain.deployment.form.oss_bucket": "存储桶",
|
||||
"domain.deployment.form.oss_bucket.placeholder": "请输入存储桶名",
|
||||
"domain.deployment.form.variables.label": "变量",
|
||||
"domain.deployment.form.variables.key": "变量名",
|
||||
"domain.deployment.form.variables.value": "值",
|
||||
"domain.deployment.form.variables.empty": "尚未添加变量",
|
||||
"domain.deployment.form.variables.key.required": "变量名不能为空",
|
||||
"domain.deployment.form.variables.value.required": "变量值不能为空",
|
||||
"domain.deployment.form.variables.key.placeholder": "请输入变量名",
|
||||
"domain.deployment.form.variables.value.placeholder": "请输入变量值"
|
||||
}
|
||||
15
ui/src/i18n/locales/zh/nls.history.json
Normal file
15
ui/src/i18n/locales/zh/nls.history.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"history.page.title": "部署",
|
||||
|
||||
"history.nodata": "你暂未创建任何部署,请先添加域名进行部署吧!",
|
||||
|
||||
"history.props.domain": "域名",
|
||||
"history.props.status": "状态",
|
||||
"history.props.stage": "阶段",
|
||||
"history.props.stage.progress.check": "检查",
|
||||
"history.props.stage.progress.apply": "获取",
|
||||
"history.props.stage.progress.deploy": "部署",
|
||||
"history.props.last_execution_time": "最近执行时间",
|
||||
|
||||
"history.log": "日志"
|
||||
}
|
||||
9
ui/src/i18n/locales/zh/nls.login.json
Normal file
9
ui/src/i18n/locales/zh/nls.login.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"login.username.label": "用户名",
|
||||
"login.username.placeholder": "请输入用户名/邮箱",
|
||||
"login.username.errmsg.invalid": "请输入正确的用户名/邮箱",
|
||||
"login.password.label": "密码",
|
||||
"login.password.placeholder": "请输入密码",
|
||||
"login.password.errmsg.invalid": "密码至少 10 个字符",
|
||||
"login.submit": "登录"
|
||||
}
|
||||
41
ui/src/i18n/locales/zh/nls.settings.json
Normal file
41
ui/src/i18n/locales/zh/nls.settings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"settings.page.title": "系统设置",
|
||||
|
||||
"settings.account.relogin.message": "请重新登录",
|
||||
|
||||
"settings.account.tab": "账号",
|
||||
"settings.account.email.label": "登录邮箱",
|
||||
"settings.account.email.errmsg.invalid": "请输入正确的邮箱地址",
|
||||
"settings.account.email.placeholder": "请输入邮箱",
|
||||
"settings.account.email.changed.message": "修改账户邮箱成功",
|
||||
"settings.account.email.failed.message": "修改账户邮箱失败",
|
||||
|
||||
"settings.password.tab": "密码",
|
||||
"settings.password.password.errmsg.length": "密码至少10个字符",
|
||||
"settings.password.password.errmsg.not_matched": "两次密码不一致",
|
||||
"settings.password.current_password.label": "当前密码",
|
||||
"settings.password.current_password.placeholder": "请输入旧密码",
|
||||
"settings.password.new_password.label": "新密码",
|
||||
"settings.password.new_password.placeholder": "请输入新密码",
|
||||
"settings.password.confirm_password.label": "确认密码",
|
||||
"settings.password.confirm_password.placeholder": "请再次输入新密码",
|
||||
"settings.password.changed.message": "修改密码成功",
|
||||
"settings.password.failed.message": "修改密码失败",
|
||||
|
||||
"settings.notification.tab": "消息推送",
|
||||
"settings.notification.template.label": "内容模板",
|
||||
"settings.notification.template.saved.message": "通知模板保存成功",
|
||||
"settings.notification.template.variables.tips.title": "可选的变量({COUNT}: 即将过期张数)",
|
||||
"settings.notification.template.variables.tips.content": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)",
|
||||
"settings.notification.config.enable": "是否启用",
|
||||
"settings.notification.config.saved.message": "配置保存成功",
|
||||
"settings.notification.config.failed.message": "配置保存失败",
|
||||
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
||||
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
||||
|
||||
"settings.ca.tab": "证书颁发机构(CA)",
|
||||
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
||||
"settings.ca.eab_kid.errmsg.empty": "请输入EAB_KID",
|
||||
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
|
||||
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
@@ -30,7 +28,7 @@ import Version from "@/components/certimate/Version";
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
|
||||
return <Navigate to="/login" />;
|
||||
@@ -73,7 +71,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Home className="h-4 w-4" />
|
||||
{t('dashboard')}
|
||||
{t("dashboard.page.title")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/domains"
|
||||
@@ -83,7 +81,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Earth className="h-4 w-4" />
|
||||
{t('domain.management.name')}
|
||||
{t("domain.page.title")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/access"
|
||||
@@ -93,7 +91,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
{t('menu.auth.management')}
|
||||
{t("access.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
@@ -104,7 +102,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
{t('deployment.log.name')}
|
||||
{t("history.page.title")}
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -141,7 +139,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Home className="h-5 w-5" />
|
||||
{t('dashboard')}
|
||||
{t("dashboard.page.title")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/domains"
|
||||
@@ -151,7 +149,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Earth className="h-5 w-5" />
|
||||
{t('domain.management.name')}
|
||||
{t("domain.page.title")}
|
||||
</Link>
|
||||
<Link
|
||||
to="/access"
|
||||
@@ -161,7 +159,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<Server className="h-5 w-5" />
|
||||
{t('menu.auth.management')}
|
||||
{t("access.page.title")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
@@ -172,7 +170,7 @@ export default function Dashboard() {
|
||||
)}
|
||||
>
|
||||
<History className="h-5 w-5" />
|
||||
{t('deployment.log.name')}
|
||||
{t("history.page.title")}
|
||||
</Link>
|
||||
</nav>
|
||||
</SheetContent>
|
||||
@@ -192,15 +190,11 @@ export default function Dashboard() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>{t('account')}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={handleSettingClick}>
|
||||
{t('setting')}
|
||||
{t("common.menu.settings")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleLogoutClick}>
|
||||
{t('logout')}
|
||||
{t("common.menu.logout")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -22,7 +22,7 @@ const SettingLayout = () => {
|
||||
<div>
|
||||
<Toaster />
|
||||
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
|
||||
{t("setting")}
|
||||
{t("settings.page.title")}
|
||||
</div>
|
||||
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
|
||||
<Tabs defaultValue="account" className="w-full" value={tabValue}>
|
||||
@@ -35,8 +35,9 @@ const SettingLayout = () => {
|
||||
className="px-5"
|
||||
>
|
||||
<UserRound size={14} />
|
||||
<div className="ml-1">{t("account")}</div>
|
||||
<div className="ml-1">{t("settings.account.tab")}</div>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger
|
||||
value="password"
|
||||
onClick={() => {
|
||||
@@ -45,7 +46,7 @@ const SettingLayout = () => {
|
||||
className="px-5"
|
||||
>
|
||||
<KeyRound size={14} />
|
||||
<div className="ml-1">{t("password")}</div>
|
||||
<div className="ml-1">{t("settings.password.tab")}</div>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger
|
||||
@@ -56,8 +57,9 @@ const SettingLayout = () => {
|
||||
className="px-5"
|
||||
>
|
||||
<Megaphone size={14} />
|
||||
<div className="ml-1">{t("setting.notify.menu")}</div>
|
||||
<div className="ml-1">{t("settings.notification.tab")}</div>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger
|
||||
value="ssl-provider"
|
||||
onClick={() => {
|
||||
@@ -66,7 +68,7 @@ const SettingLayout = () => {
|
||||
className="px-5"
|
||||
>
|
||||
<ShieldCheck size={14} />
|
||||
<div className="ml-1">{t("ca")}</div>
|
||||
<div className="ml-1">{t("settings.ca.tab")}</div>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={tabValue}>
|
||||
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent, AlertDialogDescription, AlertDialogFooter,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog.tsx";
|
||||
|
||||
const Access = () => {
|
||||
@@ -56,9 +58,9 @@ const Access = () => {
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">{t("access.management")}</div>
|
||||
<div className="text-muted-foreground">{t("access.page.title")}</div>
|
||||
{tab != "access_group" ? (
|
||||
<AccessEdit trigger={<Button>{t("access.add")}</Button>} op="add" />
|
||||
<AccessEdit trigger={<Button>{t("access.authorization.add")}</Button>} op="add" />
|
||||
) : (
|
||||
<AccessGroupEdit trigger={<Button>{t("access.group.add")}</Button>} />
|
||||
)}
|
||||
@@ -76,7 +78,7 @@ const Access = () => {
|
||||
handleTabItemClick("access");
|
||||
}}
|
||||
>
|
||||
{t("access.management")}
|
||||
{t("access.authorization.tab")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="access_group"
|
||||
@@ -84,7 +86,7 @@ const Access = () => {
|
||||
handleTabItemClick("access_group");
|
||||
}}
|
||||
>
|
||||
{t("access.group.management")}
|
||||
{t("access.group.tab")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="access">
|
||||
@@ -95,10 +97,10 @@ const Access = () => {
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
{t("access.empty")}
|
||||
{t("access.authorization.nodata")}
|
||||
</div>
|
||||
<AccessEdit
|
||||
trigger={<Button>{t("access.add")}</Button>}
|
||||
trigger={<Button>{t("access.authorization.add")}</Button>}
|
||||
op="add"
|
||||
className="mt-3"
|
||||
/>
|
||||
@@ -106,15 +108,12 @@ const Access = () => {
|
||||
) : (
|
||||
<>
|
||||
<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("name")}</div>
|
||||
<div className="w-48">{t("access.type")}</div>
|
||||
<div className="w-48">{t("common.text.name")}</div>
|
||||
<div className="w-48">{t("common.text.provider")}</div>
|
||||
|
||||
<div className="w-60">{t("create.time")}</div>
|
||||
<div className="w-60">{t("update.time")}</div>
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t("access.list")}
|
||||
<div className="w-60">{t("common.text.created_at")}</div>
|
||||
<div className="w-60">{t("common.text.updated_at")}</div>
|
||||
<div className="grow">{t("common.text.operations")}</div>
|
||||
</div>
|
||||
{accesses
|
||||
.filter((item) => {
|
||||
@@ -140,18 +139,16 @@ const Access = () => {
|
||||
</div>
|
||||
|
||||
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{t("created.in")}{" "}
|
||||
{access.created && convertZulu2Beijing(access.created)}
|
||||
</div>
|
||||
<div className="sm:w-60 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{t("updated.in")}{" "}
|
||||
{access.updated && convertZulu2Beijing(access.updated)}
|
||||
</div>
|
||||
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("edit")}
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
}
|
||||
op="edit"
|
||||
@@ -159,40 +156,40 @@ const Access = () => {
|
||||
/>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("copy")}
|
||||
</Button>
|
||||
}
|
||||
op="copy"
|
||||
data={access}
|
||||
trigger={
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("common.copy")}
|
||||
</Button>
|
||||
}
|
||||
op="copy"
|
||||
data={access}
|
||||
/>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant={"link"} size={"sm"}>
|
||||
{t('delete')}
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="dark:text-gray-200">
|
||||
{t('access.group.delete')}
|
||||
{t("access.authorization.delete")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('access.delete.confirm')}
|
||||
{t("access.authorization.delete.confirm")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="dark:text-gray-200">
|
||||
{t('cancel')}
|
||||
{t("common.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
handleDelete(access);
|
||||
}}
|
||||
onClick={() => {
|
||||
handleDelete(access);
|
||||
}}
|
||||
>
|
||||
{t('confirm')}
|
||||
{t("common.confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
@@ -57,7 +57,7 @@ const Dashboard = () => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">{t("dashboard")}</div>
|
||||
<div className="text-muted-foreground">{t("dashboard.page.title")}</div>
|
||||
</div>
|
||||
<div className="flex mt-10 gap-5 flex-col flex-wrap md:flex-row">
|
||||
<div className="w-full md:w-[250px] 3xl:w-[300px] flex items-center rounded-md p-3 shadow-lg border">
|
||||
@@ -66,7 +66,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t("dashboard.all")}
|
||||
{t("dashboard.statistics.all")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@@ -79,7 +79,7 @@ const Dashboard = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||
{t("dashboard.unit")}
|
||||
{t("dashboard.statistics.unit")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t("dashboard.near.expired")}
|
||||
{t("dashboard.statistics.near_expired")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@@ -104,7 +104,7 @@ const Dashboard = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||
{t("dashboard.unit")}
|
||||
{t("dashboard.statistics.unit")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t("dashboard.enabled")}
|
||||
{t("dashboard.statistics.enabled")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@@ -133,7 +133,7 @@ const Dashboard = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||
{t("dashboard.unit")}
|
||||
{t("dashboard.statistics.unit")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@ const Dashboard = () => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground font-semibold">
|
||||
{t("dashboard.not.enabled")}
|
||||
{t("dashboard.statistics.disabled")}
|
||||
</div>
|
||||
<div className="flex items-baseline">
|
||||
<div className="text-3xl text-stone-700 dark:text-stone-200">
|
||||
@@ -161,28 +161,32 @@ const Dashboard = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1 text-stone-700 dark:text-stone-200">
|
||||
{t("dashboard.unit")}
|
||||
{t("dashboard.statistics.unit")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-4">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-muted-foreground mt-5 text-sm">
|
||||
{t("deployment.log.name")}
|
||||
{t("dashboard.history")}
|
||||
</div>
|
||||
|
||||
{deployments?.length == 0 ? (
|
||||
<>
|
||||
<Alert className="max-w-[40em] mt-10">
|
||||
<AlertTitle>{t("no.data")}</AlertTitle>
|
||||
<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("deployment.log.empty")}</div>
|
||||
<div className="ml-2"> {t("history.nodata")}</div>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
@@ -199,18 +203,15 @@ const Dashboard = () => {
|
||||
) : (
|
||||
<>
|
||||
<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("domain")}</div>
|
||||
<div className="w-48">{t("history.props.domain")}</div>
|
||||
|
||||
<div className="w-24">{t("deployment.log.status")}</div>
|
||||
<div className="w-56">{t("deployment.log.stage")}</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("deployment.log.last.execution.time")}
|
||||
{t("history.props.last_execution_time")}
|
||||
</div>
|
||||
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t("deployment.log.name")}
|
||||
<div className="grow">{t("common.text.operations")}</div>
|
||||
</div>
|
||||
|
||||
{deployments?.map((deployment) => (
|
||||
@@ -244,14 +245,14 @@ const Dashboard = () => {
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("deployment.log.detail.button.text")}
|
||||
{t("history.log")}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:max-w-5xl">
|
||||
<SheetHeader>
|
||||
<SheetTitle>
|
||||
{deployment.expand.domain?.domain}-{deployment.id}
|
||||
{t("deployment.log.detail")}
|
||||
{t("history.log")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
|
||||
@@ -23,36 +23,43 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Domain, targetTypeKeys, targetTypeMap } from "@/domain/domain";
|
||||
import { DeployConfig, Domain } from "@/domain/domain";
|
||||
import { save, get } from "@/repository/domains";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Plus } from "lucide-react";
|
||||
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
||||
import { accessTypeMap } from "@/domain/access";
|
||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EmailsSetting } from "@/domain/settings";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import StringList from "@/components/certimate/StringList";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import DeployList from "@/components/certimate/DeployList";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
|
||||
const Edit = () => {
|
||||
const {
|
||||
config: { accesses, emails, accessGroups },
|
||||
config: { accesses, emails },
|
||||
} = useConfig();
|
||||
|
||||
const [domain, setDomain] = useState<Domain>();
|
||||
const [domain, setDomain] = useState<Domain>({} as Domain);
|
||||
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [tab, setTab] = useState<"base" | "advance">("base");
|
||||
|
||||
const [targetType, setTargetType] = useState(domain ? domain.targetType : "");
|
||||
const [tab, setTab] = useState<"apply" | "deploy">("apply");
|
||||
|
||||
useEffect(() => {
|
||||
// Parsing query parameters
|
||||
@@ -62,7 +69,6 @@ const Edit = () => {
|
||||
const fetchData = async () => {
|
||||
const data = await get(id);
|
||||
setDomain(data);
|
||||
setTargetType(data.targetType);
|
||||
};
|
||||
fetchData();
|
||||
}
|
||||
@@ -71,19 +77,14 @@ const Edit = () => {
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
domain: z.string().min(1, {
|
||||
message: "domain.not.empty.verify.message",
|
||||
message: "common.errmsg.domain_invalid",
|
||||
}),
|
||||
email: z.string().email("email.valid.message").optional(),
|
||||
email: z.string().email("common.errmsg.email_invalid").optional(),
|
||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||
message: "domain.management.edit.dns.access.not.empty.message",
|
||||
message: "domain.application.form.access.errmsg.empty",
|
||||
}),
|
||||
targetAccess: z.string().optional(),
|
||||
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
||||
message: "domain.management.edit.target.type.not.empty.message",
|
||||
}),
|
||||
variables: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
nameservers: z.string().optional(),
|
||||
timeout: z.number().optional(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -93,11 +94,8 @@ const Edit = () => {
|
||||
domain: "",
|
||||
email: "",
|
||||
access: "",
|
||||
targetAccess: "",
|
||||
targetType: "",
|
||||
variables: "",
|
||||
group: "",
|
||||
nameservers: "",
|
||||
timeout: 60,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -106,74 +104,81 @@ const Edit = () => {
|
||||
form.reset({
|
||||
id: domain.id,
|
||||
domain: domain.domain,
|
||||
email: domain.email,
|
||||
access: domain.access,
|
||||
targetAccess: domain.targetAccess,
|
||||
targetType: domain.targetType,
|
||||
variables: domain.variables,
|
||||
group: domain.group,
|
||||
nameservers: domain.nameservers,
|
||||
email: domain.applyConfig?.email,
|
||||
access: domain.applyConfig?.access,
|
||||
nameservers: domain.applyConfig?.nameservers,
|
||||
timeout: domain.applyConfig?.timeout,
|
||||
});
|
||||
}
|
||||
}, [domain, form]);
|
||||
|
||||
const targetAccesses = accesses.filter((item) => {
|
||||
if (item.usage == "apply") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetType == "") {
|
||||
return true;
|
||||
}
|
||||
const types = targetType.split("-");
|
||||
return item.configType === types[0];
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
const group = data.group == "emptyId" ? "" : data.group;
|
||||
const targetAccess =
|
||||
data.targetAccess === "emptyId" ? "" : data.targetAccess;
|
||||
if (group == "" && targetAccess == "") {
|
||||
form.setError("group", {
|
||||
type: "manual",
|
||||
message: "domain.management.edit.target.access.verify.msg",
|
||||
});
|
||||
form.setError("targetAccess", {
|
||||
type: "manual",
|
||||
message: "domain.management.edit.target.access.verify.msg",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
const req: Domain = {
|
||||
id: data.id as string,
|
||||
crontab: "0 0 * * *",
|
||||
domain: data.domain,
|
||||
email: data.email,
|
||||
access: data.access,
|
||||
group: group,
|
||||
targetAccess: targetAccess,
|
||||
targetType: data.targetType,
|
||||
variables: data.variables,
|
||||
nameservers: data.nameservers,
|
||||
applyConfig: {
|
||||
email: data.email ?? "",
|
||||
access: data.access,
|
||||
nameservers: data.nameservers,
|
||||
timeout: data.timeout,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await save(req);
|
||||
let description = t("domain.management.edit.succeed.tips");
|
||||
const resp = await save(req);
|
||||
let description = t("domain.application.form.domain.changed.message");
|
||||
if (req.id == "") {
|
||||
description = t("domain.management.add.succeed.tips");
|
||||
description = t("domain.application.form.domain.added.message");
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t("succeed"),
|
||||
title: t("common.save.succeeded.message"),
|
||||
description,
|
||||
});
|
||||
navigate("/domains");
|
||||
|
||||
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;
|
||||
|
||||
@@ -195,403 +200,281 @@ const Edit = () => {
|
||||
<div className="">
|
||||
<Toaster />
|
||||
<div className=" h-5 text-muted-foreground">
|
||||
{domain?.id ? t("domain.edit") : t("domain.add")}
|
||||
<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="mt-5 flex w-full justify-center md:space-x-10 flex-col 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">
|
||||
<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 === "base" ? "text-primary" : ""
|
||||
tab === "apply" ? "text-primary" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
setTab("base");
|
||||
setTab("apply");
|
||||
}}
|
||||
>
|
||||
{t("basic.setting")}
|
||||
{t("domain.application.tab")}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"cursor-pointer text-right",
|
||||
tab === "advance" ? "text-primary" : ""
|
||||
tab === "deploy" ? "text-primary" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
setTab("advance");
|
||||
if (!domain?.id) {
|
||||
toast({
|
||||
title: t("domain.application.unsaved.message"),
|
||||
description: t("domain.application.unsaved.message"),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTab("deploy");
|
||||
}}
|
||||
>
|
||||
{t("advanced.setting")}
|
||||
{t("domain.deployment.tab")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full md:w-[35em] bg-gray-100 dark:bg-gray-900 p-5 rounded mt-3 md:mt-0">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domain"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<>
|
||||
<StringList
|
||||
value={field.value}
|
||||
valueType="domain"
|
||||
onValueChange={(domain: string) => {
|
||||
form.setValue("domain", domain);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>
|
||||
{t("email") +
|
||||
t("domain.management.edit.email.description")}
|
||||
</div>
|
||||
<EmailsEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("email", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.email.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{t("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>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="access"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>
|
||||
{t("domain.management.edit.dns.access.label")}
|
||||
</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("access", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.access.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>
|
||||
{t("domain.management.edit.access.label")}
|
||||
</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={
|
||||
accessTypeMap.get(
|
||||
item.configType
|
||||
)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetType"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel>
|
||||
{t("domain.management.edit.target.type")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
setTargetType(value);
|
||||
form.setValue("targetType", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.target.type.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>
|
||||
{t("domain.management.edit.target.type")}
|
||||
</SelectLabel>
|
||||
{targetTypeKeys.map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={targetTypeMap.get(key)?.[1]}
|
||||
/>
|
||||
<div>
|
||||
{t(targetTypeMap.get(key)?.[0] || "")}
|
||||
</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetAccess"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>{t("domain.management.edit.target.access")}</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t("add")}
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("targetAccess", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.target.access.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>
|
||||
{t(
|
||||
"domain.management.edit.target.access.content.label"
|
||||
)}{" "}
|
||||
{form.getValues().targetAccess}
|
||||
</SelectLabel>
|
||||
<SelectItem value="emptyId">
|
||||
<div className="flex items-center space-x-2">
|
||||
--
|
||||
</div>
|
||||
</SelectItem>
|
||||
{targetAccesses.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={
|
||||
accessTypeMap.get(item.configType)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="group"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>{t("domain.management.edit.group.label")}</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
defaultValue="emptyId"
|
||||
onValueChange={(value) => {
|
||||
form.setValue("group", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.management.edit.group.not.empty.message"
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="emptyId">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded cursor-pointer"
|
||||
)}
|
||||
>
|
||||
--
|
||||
<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 w-full justify-between">
|
||||
<div>
|
||||
{t("domain.application.form.email.label") +
|
||||
" " +
|
||||
t("domain.application.form.email.tips")}
|
||||
</div>
|
||||
<EmailsEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
{t("common.add")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
{accessGroups
|
||||
.filter((item) => {
|
||||
return (
|
||||
item.expand && item.expand?.access.length > 0
|
||||
);
|
||||
})
|
||||
.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>
|
||||
}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("email", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"domain.application.form.email.errmsg.empty"
|
||||
)}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="variables"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>{t("variables")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
"domain.management.edit.variables.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* 授权 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="access"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>{t("domain.application.form.access.label")}</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<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={
|
||||
accessTypeMap.get(
|
||||
item.configType
|
||||
)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nameservers"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>{t("dns")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
"domain.management.edit.dns.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
</FormControl>
|
||||
{/* 超时时间 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="timeout"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("domain.application.form.timeout.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={t(
|
||||
"ddomain.application.form.timeout.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
form.setValue(
|
||||
"timeout",
|
||||
parseInt(e.target.value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
{/* nameservers */}
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -113,8 +113,8 @@ const Home = () => {
|
||||
|
||||
const handleRightNowClick = async (domain: Domain) => {
|
||||
try {
|
||||
unsubscribeId(domain.id);
|
||||
subscribeId(domain.id, (resp) => {
|
||||
unsubscribeId(domain.id ?? "");
|
||||
subscribeId(domain.id ?? "", (resp) => {
|
||||
console.log(resp);
|
||||
const updatedDomains = domains.map((domain) => {
|
||||
if (domain.id === resp.id) {
|
||||
@@ -129,15 +129,15 @@ const Home = () => {
|
||||
await save(domain);
|
||||
|
||||
toast.toast({
|
||||
title: t("operation.succeed"),
|
||||
description: t("domain.management.start.deploy.succeed.tips"),
|
||||
title: t("domain.deploy.started.message"),
|
||||
description: t("domain.deploy.started.tips"),
|
||||
});
|
||||
} catch (e) {
|
||||
toast.toast({
|
||||
title: t("domain.management.execution.failed"),
|
||||
title: t("domain.deploy.failed.message"),
|
||||
description: (
|
||||
// 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json
|
||||
<Trans i18nKey="domain.management.execution.failed.tips">
|
||||
<Trans i18nKey="domain.deploy.failed.tips">
|
||||
text1
|
||||
<Link
|
||||
to={`/history?domain=${domain.id}`}
|
||||
@@ -178,9 +178,7 @@ const Home = () => {
|
||||
<div className="">
|
||||
<Toaster />
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">
|
||||
{t("domain.management.name")}
|
||||
</div>
|
||||
<div className="text-muted-foreground">{t("domain.page.title")}</div>
|
||||
<Button onClick={handleCreateClick}>{t("domain.add")}</Button>
|
||||
</div>
|
||||
|
||||
@@ -192,7 +190,7 @@ const Home = () => {
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
{t("domain.management.empty")}
|
||||
{t("domain.nodata")}
|
||||
</div>
|
||||
<Button onClick={handleCreateClick} className="mt-3">
|
||||
{t("domain.add")}
|
||||
@@ -202,22 +200,19 @@ const Home = () => {
|
||||
) : (
|
||||
<>
|
||||
<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("domain")}</div>
|
||||
<div className="w-40">{t("domain.management.expiry.date")}</div>
|
||||
<div className="w-36">{t("common.text.domain")}</div>
|
||||
<div className="w-40">{t("domain.props.expiry")}</div>
|
||||
<div className="w-32">
|
||||
{t("domain.management.last.execution.status")}
|
||||
{t("domain.props.last_execution_status")}
|
||||
</div>
|
||||
<div className="w-64">
|
||||
{t("domain.management.last.execution.stage")}
|
||||
{t("domain.props.last_execution_stage")}
|
||||
</div>
|
||||
<div className="w-40 sm:ml-2">
|
||||
{t("domain.management.last.execution.time")}
|
||||
{t("domain.props.last_execution_time")}
|
||||
</div>
|
||||
<div className="w-24">{t("domain.management.enable")}</div>
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t("domain")}
|
||||
<div className="w-24">{t("domain.props.enable")}</div>
|
||||
<div className="grow">{t("common.text.operations")}</div>
|
||||
</div>
|
||||
|
||||
{domains.map((domain) => (
|
||||
@@ -238,10 +233,10 @@ const Home = () => {
|
||||
{domain.expiredAt ? (
|
||||
<>
|
||||
<div>
|
||||
{t("domain.management.expiry.date1", { date: 90 })}
|
||||
{t("domain.props.expiry.date1", { date: 90 })}
|
||||
</div>
|
||||
<div>
|
||||
{t("domain.management.expiry.date2", {
|
||||
{t("domain.props.expiry.date2", {
|
||||
date: getDate(domain.expiredAt),
|
||||
})}
|
||||
</div>
|
||||
@@ -282,13 +277,15 @@ const Home = () => {
|
||||
<Switch
|
||||
checked={domain.enabled}
|
||||
onCheckedChange={() => {
|
||||
handelCheckedChange(domain.id);
|
||||
handelCheckedChange(domain.id ?? "");
|
||||
}}
|
||||
></Switch>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="border rounded-sm px-3 bg-background text-muted-foreground text-xs">
|
||||
{domain.enabled ? t("disable") : t("enable")}
|
||||
{domain.enabled
|
||||
? t("domain.props.enable.disabled")
|
||||
: t("domain.props.enable.enabled")}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -298,9 +295,9 @@ const Home = () => {
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="p-0"
|
||||
onClick={() => handleHistoryClick(domain.id)}
|
||||
onClick={() => handleHistoryClick(domain.id ?? "")}
|
||||
>
|
||||
{t("deployment.log.name")}
|
||||
{t("domain.history")}
|
||||
</Button>
|
||||
<Show when={domain.enabled ? true : false}>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
@@ -309,7 +306,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleRightNowClick(domain)}
|
||||
>
|
||||
{t("domain.management.start.deploying")}
|
||||
{t("domain.deploy")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@@ -326,7 +323,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleForceClick(domain)}
|
||||
>
|
||||
{t("domain.management.forced.deployment")}
|
||||
{t("domain.deploy_forced")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@@ -337,7 +334,7 @@ const Home = () => {
|
||||
className="p-0"
|
||||
onClick={() => handleDownloadClick(domain)}
|
||||
>
|
||||
{t("download")}
|
||||
{t("common.download")}
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
@@ -347,7 +344,7 @@ const Home = () => {
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("delete")}
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
@@ -356,17 +353,19 @@ const Home = () => {
|
||||
{t("domain.delete")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("domain.management.delete.confirm")}
|
||||
{t("domain.delete.confirm")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogCancel>
|
||||
{t("common.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
handleDeleteClick(domain.id);
|
||||
handleDeleteClick(domain.id ?? "");
|
||||
}}
|
||||
>
|
||||
{t("confirm")}
|
||||
{t("common.confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -376,9 +375,9 @@ const Home = () => {
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="p-0"
|
||||
onClick={() => handleEditClick(domain.id)}
|
||||
onClick={() => handleEditClick(domain.id ?? "")}
|
||||
>
|
||||
{t("edit")}
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -40,17 +40,17 @@ const History = () => {
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-[80vh] overflow-hidden">
|
||||
<div className="text-muted-foreground">{t("deployment.log.name")}</div>
|
||||
<div className="text-muted-foreground">{t("history.page.title")}</div>
|
||||
{!deployments?.length ? (
|
||||
<>
|
||||
<Alert className="max-w-[40em] mx-auto mt-20">
|
||||
<AlertTitle>{t("no.data")}</AlertTitle>
|
||||
<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("deployment.log.empty")}</div>
|
||||
<div className="ml-2"> {t("history.nodata")}</div>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
@@ -67,18 +67,15 @@ const History = () => {
|
||||
) : (
|
||||
<>
|
||||
<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("domain")}</div>
|
||||
<div className="w-48">{t("history.props.domain")}</div>
|
||||
|
||||
<div className="w-24">{t("deployment.log.status")}</div>
|
||||
<div className="w-56">{t("deployment.log.stage")}</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("deployment.log.last.execution.time")}
|
||||
{t("history.props.last_execution_time")}
|
||||
</div>
|
||||
|
||||
<div className="grow">{t("operation")}</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
{t("deployment.log.name")}
|
||||
<div className="grow">{t("common.text.operations")}</div>
|
||||
</div>
|
||||
|
||||
{deployments?.map((deployment) => (
|
||||
@@ -112,14 +109,14 @@ const History = () => {
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant={"link"} className="p-0">
|
||||
{t("deployment.log.detail.button.text")}
|
||||
{t("history.log")}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:max-w-5xl">
|
||||
<SheetHeader>
|
||||
<SheetTitle>
|
||||
{deployment.expand.domain?.domain}-{deployment.id}
|
||||
{t("deployment.log.detail")}
|
||||
{t("history.log")}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="bg-gray-950 text-stone-100 p-5 text-sm h-[80dvh]">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { z } from "zod";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -19,15 +19,15 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().email({
|
||||
message: "login.username.no.empty.message",
|
||||
message: "login.username.errmsg.invalid",
|
||||
}),
|
||||
password: z.string().min(10, {
|
||||
message: "login.password.length.message",
|
||||
message: "login.password.errmsg.invalid",
|
||||
}),
|
||||
});
|
||||
|
||||
const Login = () => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -65,9 +65,9 @@ const Login = () => {
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('username')}</FormLabel>
|
||||
<FormLabel>{t("login.username.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="email" {...field} />
|
||||
<Input placeholder={t("login.username.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -80,9 +80,9 @@ const Login = () => {
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('password')}</FormLabel>
|
||||
<FormLabel>{t("login.password.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="password" {...field} type="password" />
|
||||
<Input placeholder={t("login.password.placeholder")} {...field} type="password" />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -90,7 +90,7 @@ const Login = () => {
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('login.submit')}</Button>
|
||||
<Button type="submit">{t("login.submit")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { z } from "zod";
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email("setting.account.email.valid.message"),
|
||||
email: z.string().email("settings.account.email.errmsg.invalid"),
|
||||
});
|
||||
|
||||
const Account = () => {
|
||||
@@ -45,8 +45,8 @@ const Account = () => {
|
||||
|
||||
getPb().authStore.clear();
|
||||
toast({
|
||||
title: t("setting.account.email.change.succeed"),
|
||||
description: t("setting.account.log.back.in"),
|
||||
title: t("settings.account.email.changed.message"),
|
||||
description: t("settings.account.relogin.message"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate("/login");
|
||||
@@ -54,7 +54,7 @@ const Account = () => {
|
||||
} catch (e) {
|
||||
const message = getErrMessage(e);
|
||||
toast({
|
||||
title: t("setting.account.email.change.failed"),
|
||||
title: t("settings.account.email.failed.message"),
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -74,10 +74,10 @@ const Account = () => {
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('email')}</FormLabel>
|
||||
<FormLabel>{t("settings.account.email.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('setting.email.placeholder')}
|
||||
placeholder={t("settings.account.email.placeholder")}
|
||||
{...field}
|
||||
type="email"
|
||||
onChange={(e) => {
|
||||
@@ -94,10 +94,10 @@ const Account = () => {
|
||||
|
||||
<div className="flex justify-end">
|
||||
{changed ? (
|
||||
<Button type="submit">{t('setting.submit')}</Button>
|
||||
<Button type="submit">{t("common.update")}</Button>
|
||||
) : (
|
||||
<Button type="submit" disabled variant={"secondary"}>
|
||||
{t('setting.submit')}
|
||||
{t("common.update")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,9 @@ const Notify = () => {
|
||||
<div className="border rounded-sm p-5 shadow-lg">
|
||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t('template')}</AccordionTrigger>
|
||||
<AccordionTrigger>
|
||||
{t("settings.notification.template.label")}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<NotifyTemplate />
|
||||
</AccordionContent>
|
||||
@@ -30,21 +32,21 @@ const Notify = () => {
|
||||
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||
<Accordion type={"single"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t('ding.talk')}</AccordionTrigger>
|
||||
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DingTalk />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t('telegram')}</AccordionTrigger>
|
||||
<AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Telegram />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||
<AccordionTrigger>{t('webhook')}</AccordionTrigger>
|
||||
<AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Webhook />
|
||||
</AccordionContent>
|
||||
|
||||
@@ -21,17 +21,17 @@ import { z } from "zod";
|
||||
const formSchema = z
|
||||
.object({
|
||||
oldPassword: z.string().min(10, {
|
||||
message: "setting.password.length.message",
|
||||
message: "settings.password.password.errmsg.length",
|
||||
}),
|
||||
newPassword: z.string().min(10, {
|
||||
message: "setting.password.length.message",
|
||||
message: "settings.password.password.errmsg.length",
|
||||
}),
|
||||
confirmPassword: z.string().min(10, {
|
||||
message: "setting.password.length.message",
|
||||
message: "settings.password.password.errmsg.length",
|
||||
}),
|
||||
})
|
||||
.refine((data) => data.newPassword === data.confirmPassword, {
|
||||
message: "setting.password.not.match",
|
||||
message: "settings.password.password.errmsg.not_matched",
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
|
||||
@@ -68,8 +68,8 @@ const Password = () => {
|
||||
|
||||
getPb().authStore.clear();
|
||||
toast({
|
||||
title: t('setting.password.change.succeed'),
|
||||
description: t("setting.account.log.back.in"),
|
||||
title: t("settings.password.changed.message"),
|
||||
description: t("settings.account.relogin.message"),
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate("/login");
|
||||
@@ -77,7 +77,7 @@ const Password = () => {
|
||||
} catch (e) {
|
||||
const message = getErrMessage(e);
|
||||
toast({
|
||||
title: t('setting.password.change.failed'),
|
||||
title: t("settings.password.failed.message"),
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -97,9 +97,17 @@ const Password = () => {
|
||||
name="oldPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('setting.password.current.password')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.password.current_password.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('setting.password.current.password')} {...field} type="password" />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"settings.password.current_password.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -112,10 +120,14 @@ const Password = () => {
|
||||
name="newPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('setting.password.new.password')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.password.new_password.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="newPassword"
|
||||
placeholder={t(
|
||||
"settings.password.new_password.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
@@ -131,10 +143,14 @@ const Password = () => {
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('setting.password.confirm.password')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.password.confirm_password.label")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="confirmPassword"
|
||||
placeholder={t(
|
||||
"settings.password.confirm_password.placeholder"
|
||||
)}
|
||||
{...field}
|
||||
type="password"
|
||||
/>
|
||||
@@ -145,7 +161,7 @@ const Password = () => {
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t('setting.submit')}</Button>
|
||||
<Button type="submit">{t("common.update")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -35,7 +35,7 @@ const SSLProvider = () => {
|
||||
|
||||
const formSchema = z.object({
|
||||
provider: z.enum(["letsencrypt", "zerossl"], {
|
||||
message: t("setting.ca.not.empty"),
|
||||
message: t("settings.ca.provider.errmsg.empty"),
|
||||
}),
|
||||
eabKid: z.string().optional(),
|
||||
eabHmacKey: z.string().optional(),
|
||||
@@ -89,12 +89,12 @@ const SSLProvider = () => {
|
||||
if (values.provider === "zerossl") {
|
||||
if (!values.eabKid) {
|
||||
form.setError("eabKid", {
|
||||
message: t("setting.ca.eab_kid_hmac_key.not.empty"),
|
||||
message: t("settings.ca.eab_kid_hmac_key.errmsg.empty"),
|
||||
});
|
||||
}
|
||||
if (!values.eabHmacKey) {
|
||||
form.setError("eabHmacKey", {
|
||||
message: t("setting.ca.eab_kid_hmac_key.not.empty"),
|
||||
message: t("settings.ca.eab_kid_hmac_key.errmsg.empty"),
|
||||
});
|
||||
}
|
||||
if (!values.eabKid || !values.eabHmacKey) {
|
||||
@@ -120,13 +120,13 @@ const SSLProvider = () => {
|
||||
try {
|
||||
await update(setting);
|
||||
toast({
|
||||
title: t("update.succeed"),
|
||||
description: t("update.succeed"),
|
||||
title: t("common.update.succeeded.message"),
|
||||
description: t("common.update.succeeded.message"),
|
||||
});
|
||||
} catch (e) {
|
||||
const message = getErrMessage(e);
|
||||
toast({
|
||||
title: t("update.failed"),
|
||||
title: t("common.update.failed.message"),
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
@@ -146,7 +146,7 @@ const SSLProvider = () => {
|
||||
name="provider"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("ca")}</FormLabel>
|
||||
<FormLabel>{t("common.text.ca")}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
{...field}
|
||||
@@ -202,7 +202,7 @@ const SSLProvider = () => {
|
||||
<FormLabel>EAB_KID</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t("setting.ca.eab_kid.not.empty")}
|
||||
placeholder={t("settings.ca.eab_kid.errmsg.empty")}
|
||||
{...field}
|
||||
type="text"
|
||||
/>
|
||||
@@ -221,7 +221,9 @@ const SSLProvider = () => {
|
||||
<FormLabel>EAB_HMAC_KEY</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t("setting.ca.eab_hmac_key.not.empty")}
|
||||
placeholder={t(
|
||||
"settings.ca.eab_hmac_key.errmsg.empty"
|
||||
)}
|
||||
{...field}
|
||||
type="text"
|
||||
/>
|
||||
@@ -238,7 +240,7 @@ const SSLProvider = () => {
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("setting.submit")}</Button>
|
||||
<Button type="submit">{t("common.update")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user