mirror of
https://github.com/usual2970/certimate.git
synced 2025-07-11 13:39:56 +00:00
Compare commits
6 Commits
d509445519
...
52d24ff2f2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
52d24ff2f2 | ||
![]() |
7a66bdf139 | ||
![]() |
16bc12c15b | ||
![]() |
0556d68a4e | ||
![]() |
586c7fa927 | ||
![]() |
9ef16ebcf9 |
3
go.sum
3
go.sum
@ -392,6 +392,7 @@ github.com/gojek/heimdall/v7 v7.0.3/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5
|
|||||||
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM=
|
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM=
|
||||||
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
|
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
|
||||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
@ -697,6 +698,8 @@ github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU
|
|||||||
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||||
github.com/pocketbase/pocketbase v0.22.21 h1:DGPCxn6co8VuTV0mton4NFO/ON49XiFMszRr+Mysy48=
|
github.com/pocketbase/pocketbase v0.22.21 h1:DGPCxn6co8VuTV0mton4NFO/ON49XiFMszRr+Mysy48=
|
||||||
github.com/pocketbase/pocketbase v0.22.21/go.mod h1:Cw5E4uoGhKItBIE2lJL3NfmiUr9Syk2xaNJ2G7Dssow=
|
github.com/pocketbase/pocketbase v0.22.21/go.mod h1:Cw5E4uoGhKItBIE2lJL3NfmiUr9Syk2xaNJ2G7Dssow=
|
||||||
|
github.com/pocketbase/pocketbase v0.23.12 h1:HB4THFbzaliF0C3wvpx+kNOZxIwCEMDqN3/17gn5N7E=
|
||||||
|
github.com/pocketbase/pocketbase v0.23.12/go.mod h1:OcFJNMO0Vzt3f9+lweMbup6iL7V13ckxu1pdEY6FeM0=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/tools/cron"
|
"github.com/pocketbase/pocketbase/tools/cron"
|
||||||
)
|
)
|
||||||
|
|
||||||
var schedulerOnce sync.Once
|
|
||||||
|
|
||||||
var scheduler *cron.Cron
|
var scheduler *cron.Cron
|
||||||
|
|
||||||
|
var schedulerOnce sync.Once
|
||||||
|
|
||||||
func GetScheduler() *cron.Cron {
|
func GetScheduler() *cron.Cron {
|
||||||
schedulerOnce.Do(func() {
|
schedulerOnce.Do(func() {
|
||||||
scheduler = cron.New()
|
scheduler = cron.New()
|
43
internal/applicant/acmehttpreq.go
Normal file
43
internal/applicant/acmehttpreq.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package applicant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/httpreq"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type acmeHttpReqApplicant struct {
|
||||||
|
option *ApplyOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewACMEHttpReqApplicant(option *ApplyOption) Applicant {
|
||||||
|
return &acmeHttpReqApplicant{
|
||||||
|
option: option,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *acmeHttpReqApplicant) Apply() (*Certificate, error) {
|
||||||
|
access := &domain.HttpreqAccess{}
|
||||||
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
|
config := httpreq.NewDefaultConfig()
|
||||||
|
endpoint, _ := url.Parse(access.Endpoint)
|
||||||
|
config.Endpoint = endpoint
|
||||||
|
config.Mode = access.Mode
|
||||||
|
config.Username = access.Username
|
||||||
|
config.Password = access.Password
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := httpreq.NewDNSProviderConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(a.option, provider)
|
||||||
|
}
|
@ -2,35 +2,38 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aliyun struct {
|
type aliyunApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAliyun(option *ApplyOption) Applicant {
|
func NewAliyunApplicant(option *ApplyOption) Applicant {
|
||||||
return &aliyun{
|
return &aliyunApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *aliyun) Apply() (*Certificate, error) {
|
func (a *aliyunApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.AliyunAccess{}
|
access := &domain.AliyunAccess{}
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("ALICLOUD_ACCESS_KEY", access.AccessKeyId)
|
config := alidns.NewDefaultConfig()
|
||||||
os.Setenv("ALICLOUD_SECRET_KEY", access.AccessKeySecret)
|
config.APIKey = access.AccessKeyId
|
||||||
os.Setenv("ALICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
config.SecretKey = access.AccessKeySecret
|
||||||
dnsProvider, err := alidns.NewDNSProvider()
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := alidns.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -208,29 +208,33 @@ func GetWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) {
|
func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) {
|
||||||
|
/*
|
||||||
|
注意:如果追加新的常量值,请保持以 ASCII 排序。
|
||||||
|
NOTICE: If you add new constant, please keep ASCII order.
|
||||||
|
*/
|
||||||
switch t {
|
switch t {
|
||||||
case configTypeAliyun:
|
|
||||||
return NewAliyun(option), nil
|
|
||||||
case configTypeTencentCloud:
|
|
||||||
return NewTencent(option), nil
|
|
||||||
case configTypeHuaweiCloud:
|
|
||||||
return NewHuaweiCloud(option), nil
|
|
||||||
case configTypeAWS:
|
|
||||||
return NewAws(option), nil
|
|
||||||
case configTypeCloudflare:
|
|
||||||
return NewCloudflare(option), nil
|
|
||||||
case configTypeNameSilo:
|
|
||||||
return NewNamesilo(option), nil
|
|
||||||
case configTypeGoDaddy:
|
|
||||||
return NewGodaddy(option), nil
|
|
||||||
case configTypePowerDNS:
|
|
||||||
return NewPdns(option), nil
|
|
||||||
case configTypeACMEHttpReq:
|
case configTypeACMEHttpReq:
|
||||||
return NewHttpreq(option), nil
|
return NewACMEHttpReqApplicant(option), nil
|
||||||
|
case configTypeAliyun:
|
||||||
|
return NewAliyunApplicant(option), nil
|
||||||
|
case configTypeAWS:
|
||||||
|
return NewAWSApplicant(option), nil
|
||||||
|
case configTypeCloudflare:
|
||||||
|
return NewCloudflareApplicant(option), nil
|
||||||
|
case configTypeGoDaddy:
|
||||||
|
return NewGoDaddyApplicant(option), nil
|
||||||
|
case configTypeHuaweiCloud:
|
||||||
|
return NewHuaweiCloudApplicant(option), nil
|
||||||
|
case configTypeNameSilo:
|
||||||
|
return NewNamesiloApplicant(option), nil
|
||||||
|
case configTypePowerDNS:
|
||||||
|
return NewPowerDNSApplicant(option), nil
|
||||||
|
case configTypeTencentCloud:
|
||||||
|
return NewTencentCloudApplicant(option), nil
|
||||||
case configTypeVolcEngine:
|
case configTypeVolcEngine:
|
||||||
return NewVolcengine(option), nil
|
return NewVolcEngineApplicant(option), nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown config type")
|
return nil, fmt.Errorf("unsupported applicant type: %s", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,38 +2,40 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/route53"
|
"github.com/go-acme/lego/v4/providers/dns/route53"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aws struct {
|
type awsApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAws(option *ApplyOption) Applicant {
|
func NewAWSApplicant(option *ApplyOption) Applicant {
|
||||||
return &aws{
|
return &awsApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *aws) Apply() (*Certificate, error) {
|
func (a *awsApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.AwsAccess{}
|
access := &domain.AwsAccess{}
|
||||||
json.Unmarshal([]byte(t.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("AWS_REGION", access.Region)
|
config := route53.NewDefaultConfig()
|
||||||
os.Setenv("AWS_ACCESS_KEY_ID", access.AccessKeyId)
|
config.AccessKeyID = access.AccessKeyId
|
||||||
os.Setenv("AWS_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
config.SecretAccessKey = access.SecretAccessKey
|
||||||
os.Setenv("AWS_HOSTED_ZONE_ID", access.HostedZoneId)
|
config.Region = access.Region
|
||||||
os.Setenv("AWS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
config.HostedZoneID = access.HostedZoneId
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
dnsProvider, err := route53.NewDNSProvider()
|
provider, err := route53.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(t.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,37 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cloudflare struct {
|
type cloudflareApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloudflare(option *ApplyOption) Applicant {
|
func NewCloudflareApplicant(option *ApplyOption) Applicant {
|
||||||
return &cloudflare{
|
return &cloudflareApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cloudflare) Apply() (*Certificate, error) {
|
func (a *cloudflareApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.CloudflareAccess{}
|
access := &domain.CloudflareAccess{}
|
||||||
json.Unmarshal([]byte(c.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("CLOUDFLARE_DNS_API_TOKEN", access.DnsApiToken)
|
config := cloudflare.NewDefaultConfig()
|
||||||
os.Setenv("CLOUDFLARE_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", c.option.Timeout))
|
config.AuthToken = access.DnsApiToken
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
provider, err := cf.NewDNSProvider()
|
provider, err := cloudflare.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(c.option, provider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -2,36 +2,38 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
|
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type godaddy struct {
|
type godaddyApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGodaddy(option *ApplyOption) Applicant {
|
func NewGoDaddyApplicant(option *ApplyOption) Applicant {
|
||||||
return &godaddy{
|
return &godaddyApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *godaddy) Apply() (*Certificate, error) {
|
func (a *godaddyApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.GodaddyAccess{}
|
access := &domain.GodaddyAccess{}
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("GODADDY_API_KEY", access.ApiKey)
|
config := godaddy.NewDefaultConfig()
|
||||||
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
|
config.APIKey = access.ApiKey
|
||||||
os.Setenv("GODADDY_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
config.APISecret = access.ApiSecret
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
dnsProvider, err := godaddyProvider.NewDNSProvider()
|
provider, err := godaddy.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package applicant
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/httpreq"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type httpReq struct {
|
|
||||||
option *ApplyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpreq(option *ApplyOption) Applicant {
|
|
||||||
return &httpReq{
|
|
||||||
option: option,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *httpReq) Apply() (*Certificate, error) {
|
|
||||||
access := &domain.HttpreqAccess{}
|
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
|
||||||
|
|
||||||
os.Setenv("HTTPREQ_ENDPOINT", access.Endpoint)
|
|
||||||
os.Setenv("HTTPREQ_MODE", access.Mode)
|
|
||||||
os.Setenv("HTTPREQ_USERNAME", access.Username)
|
|
||||||
os.Setenv("HTTPREQ_PASSWORD", access.Password)
|
|
||||||
os.Setenv("HTTPREQ_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
|
||||||
dnsProvider, err := httpreq.NewDNSProvider()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
|
||||||
}
|
|
@ -2,42 +2,45 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
huaweicloud "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type huaweicloud struct {
|
type huaweicloudApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHuaweiCloud(option *ApplyOption) Applicant {
|
func NewHuaweiCloudApplicant(option *ApplyOption) Applicant {
|
||||||
return &huaweicloud{
|
return &huaweicloudApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *huaweicloud) Apply() (*Certificate, error) {
|
func (a *huaweicloudApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.HuaweiCloudAccess{}
|
access := &domain.HuaweiCloudAccess{}
|
||||||
json.Unmarshal([]byte(t.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
region := access.Region
|
region := access.Region
|
||||||
if region == "" {
|
if region == "" {
|
||||||
|
// 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
||||||
region = "cn-north-1"
|
region = "cn-north-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Setenv("HUAWEICLOUD_REGION", region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
|
config := huaweicloud.NewDefaultConfig()
|
||||||
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
|
config.AccessKeyID = access.AccessKeyId
|
||||||
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
|
config.SecretAccessKey = access.SecretAccessKey
|
||||||
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
|
config.Region = region
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
|
provider, err := huaweicloud.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(t.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,37 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
|
namesilo "github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type namesilo struct {
|
type namesiloApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNamesilo(option *ApplyOption) Applicant {
|
func NewNamesiloApplicant(option *ApplyOption) Applicant {
|
||||||
return &namesilo{
|
return &namesiloApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *namesilo) Apply() (*Certificate, error) {
|
func (a *namesiloApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.NameSiloAccess{}
|
access := &domain.NameSiloAccess{}
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("NAMESILO_API_KEY", access.ApiKey)
|
config := namesilo.NewDefaultConfig()
|
||||||
os.Setenv("NAMESILO_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
config.APIKey = access.ApiKey
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
dnsProvider, err := namesiloProvider.NewDNSProvider()
|
provider, err := namesilo.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package applicant
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type powerdns struct {
|
|
||||||
option *ApplyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPdns(option *ApplyOption) Applicant {
|
|
||||||
return &powerdns{
|
|
||||||
option: option,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *powerdns) Apply() (*Certificate, error) {
|
|
||||||
access := &domain.PdnsAccess{}
|
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
|
||||||
|
|
||||||
os.Setenv("PDNS_API_URL", access.ApiUrl)
|
|
||||||
os.Setenv("PDNS_API_KEY", access.ApiKey)
|
|
||||||
os.Setenv("PDNS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
|
||||||
dnsProvider, err := pdns.NewDNSProvider()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
|
||||||
}
|
|
41
internal/applicant/powerdns.go
Normal file
41
internal/applicant/powerdns.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package applicant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type powerdnsApplicant struct {
|
||||||
|
option *ApplyOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPowerDNSApplicant(option *ApplyOption) Applicant {
|
||||||
|
return &powerdnsApplicant{
|
||||||
|
option: option,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *powerdnsApplicant) Apply() (*Certificate, error) {
|
||||||
|
access := &domain.PdnsAccess{}
|
||||||
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
|
config := pdns.NewDefaultConfig()
|
||||||
|
host, _ := url.Parse(access.ApiUrl)
|
||||||
|
config.Host = host
|
||||||
|
config.APIKey = access.ApiKey
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := pdns.NewDNSProviderConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(a.option, provider)
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
package applicant
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tencent struct {
|
|
||||||
option *ApplyOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTencent(option *ApplyOption) Applicant {
|
|
||||||
return &tencent{
|
|
||||||
option: option,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tencent) Apply() (*Certificate, error) {
|
|
||||||
access := &domain.TencentAccess{}
|
|
||||||
json.Unmarshal([]byte(t.option.Access), access)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return apply(t.option, dnsProvider)
|
|
||||||
}
|
|
39
internal/applicant/tencentcloud.go
Normal file
39
internal/applicant/tencentcloud.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package applicant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tencentcloudApplicant struct {
|
||||||
|
option *ApplyOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTencentCloudApplicant(option *ApplyOption) Applicant {
|
||||||
|
return &tencentcloudApplicant{
|
||||||
|
option: option,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *tencentcloudApplicant) Apply() (*Certificate, error) {
|
||||||
|
access := &domain.TencentAccess{}
|
||||||
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
|
config := tencentcloud.NewDefaultConfig()
|
||||||
|
config.SecretID = access.SecretId
|
||||||
|
config.SecretKey = access.SecretKey
|
||||||
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := tencentcloud.NewDNSProviderConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(a.option, provider)
|
||||||
|
}
|
@ -2,34 +2,37 @@ package applicant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"time"
|
||||||
"os"
|
|
||||||
|
|
||||||
volcengineDns "github.com/go-acme/lego/v4/providers/dns/volcengine"
|
"github.com/go-acme/lego/v4/providers/dns/volcengine"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type volcengine struct {
|
type volcengineApplicant struct {
|
||||||
option *ApplyOption
|
option *ApplyOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVolcengine(option *ApplyOption) Applicant {
|
func NewVolcEngineApplicant(option *ApplyOption) Applicant {
|
||||||
return &volcengine{
|
return &volcengineApplicant{
|
||||||
option: option,
|
option: option,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *volcengine) Apply() (*Certificate, error) {
|
func (a *volcengineApplicant) Apply() (*Certificate, error) {
|
||||||
access := &domain.VolcEngineAccess{}
|
access := &domain.VolcEngineAccess{}
|
||||||
json.Unmarshal([]byte(a.option.Access), access)
|
json.Unmarshal([]byte(a.option.Access), access)
|
||||||
|
|
||||||
os.Setenv("VOLC_ACCESSKEY", access.AccessKeyId)
|
config := volcengine.NewDefaultConfig()
|
||||||
os.Setenv("VOLC_SECRETKEY", access.SecretAccessKey)
|
config.AccessKey = access.AccessKeyId
|
||||||
os.Setenv("VOLC_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
|
config.SecretKey = access.SecretAccessKey
|
||||||
dnsProvider, err := volcengineDns.NewDNSProvider()
|
if a.option.Timeout != 0 {
|
||||||
|
config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := volcengine.NewDNSProviderConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apply(a.option, dnsProvider)
|
return apply(a.option, provider)
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
defaultExpireSubject = "您有 ${COUNT} 张证书即将过期"
|
||||||
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
defaultExpireMessage = "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CertificateRepository interface {
|
type CertificateRepository interface {
|
||||||
@ -88,11 +88,11 @@ func buildMsg(records []domain.Certificate) *domain.NotifyMessage {
|
|||||||
countStr := strconv.Itoa(count)
|
countStr := strconv.Itoa(count)
|
||||||
domainStr := strings.Join(domains, ";")
|
domainStr := strings.Join(domains, ";")
|
||||||
|
|
||||||
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
subject = strings.ReplaceAll(subject, "${COUNT}", countStr)
|
||||||
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
subject = strings.ReplaceAll(subject, "${DOMAINS}", domainStr)
|
||||||
|
|
||||||
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
message = strings.ReplaceAll(message, "${COUNT}", countStr)
|
||||||
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
message = strings.ReplaceAll(message, "${DOMAINS}", domainStr)
|
||||||
|
|
||||||
// 返回消息
|
// 返回消息
|
||||||
return &domain.NotifyMessage{
|
return &domain.NotifyMessage{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { getPocketBase } from "@/repository/pocketbase";
|
import { getPocketBase } from "@/repository/pocketbase";
|
||||||
|
|
||||||
export const notifyTest = async (channel: string) => {
|
export const notifyTest = async (channel: string) => {
|
||||||
@ -14,7 +16,7 @@ export const notifyTest = async (channel: string) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (resp.code != 0) {
|
if (resp.code != 0) {
|
||||||
throw new Error(resp.msg);
|
throw new ClientResponseError({ status: resp.code, response: resp, data: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Statistics } from "@/domain/statistics";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
import { type Statistics } from "@/domain/statistics";
|
||||||
import { getPocketBase } from "@/repository/pocketbase";
|
import { getPocketBase } from "@/repository/pocketbase";
|
||||||
|
|
||||||
export const get = async () => {
|
export const get = async () => {
|
||||||
@ -8,8 +10,8 @@ export const get = async () => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp.code !== 0) {
|
if (resp.code != 0) {
|
||||||
throw new Error(resp.msg);
|
throw new ClientResponseError({ status: resp.code, response: resp, data: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.data as Statistics;
|
return resp.data as Statistics;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { getPocketBase } from "@/repository/pocketbase";
|
import { getPocketBase } from "@/repository/pocketbase";
|
||||||
|
|
||||||
export const run = async (id: string) => {
|
export const run = async (id: string) => {
|
||||||
@ -14,7 +16,7 @@ export const run = async (id: string) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (resp.code != 0) {
|
if (resp.code != 0) {
|
||||||
throw new Error(resp.msg);
|
throw new ClientResponseError({ status: resp.code, response: resp, data: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
275
ui/src/components/ui/MultipleInput.tsx
Normal file
275
ui/src/components/ui/MultipleInput.tsx
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, useMemo, useRef, type ChangeEvent } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useControllableValue } from "ahooks";
|
||||||
|
import { Button, Input, Space, type InputRef, type InputProps } from "antd";
|
||||||
|
import { produce } from "immer";
|
||||||
|
import { ArrowDown as ArrowDownIcon, ArrowUp as ArrowUpIcon, Minus as MinusIcon, Plus as PlusIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export type MultipleInputProps = Omit<InputProps, "count" | "defaultValue" | "showCount" | "value" | "onChange" | "onPressEnter" | "onClear"> & {
|
||||||
|
allowClear?: boolean;
|
||||||
|
defaultValue?: string[];
|
||||||
|
maxCount?: number;
|
||||||
|
minCount?: number;
|
||||||
|
showSortButton?: boolean;
|
||||||
|
value?: string[];
|
||||||
|
onChange?: (index: number, e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onCreate?: (index: number) => void;
|
||||||
|
onRemove?: (index: number) => void;
|
||||||
|
onSort?: (oldIndex: number, newIndex: number) => void;
|
||||||
|
onValueChange?: (value: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MultipleInput = ({
|
||||||
|
allowClear = false,
|
||||||
|
disabled,
|
||||||
|
maxCount,
|
||||||
|
minCount,
|
||||||
|
showSortButton = true,
|
||||||
|
onChange,
|
||||||
|
onCreate,
|
||||||
|
onSort,
|
||||||
|
onRemove,
|
||||||
|
...props
|
||||||
|
}: MultipleInputProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const itemRefs = useRef<MultipleInputItemInstance[]>([]);
|
||||||
|
|
||||||
|
const [value, setValue] = useControllableValue<string[]>(props, {
|
||||||
|
valuePropName: "value",
|
||||||
|
defaultValue: [],
|
||||||
|
defaultValuePropName: "defaultValue",
|
||||||
|
trigger: "onValueChange",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
draft.push("");
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
setTimeout(() => itemRefs.current[newValue.length - 1]?.focus(), 0);
|
||||||
|
|
||||||
|
onCreate?.(newValue.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (index: number, e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
draft[index] = e.target.value;
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
|
||||||
|
onChange?.(index, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputBlur = (index: number) => {
|
||||||
|
if (!allowClear && !value[index]) {
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
draft.splice(index, 1);
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickUp = (index: number) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
const temp = draft[index - 1];
|
||||||
|
draft[index - 1] = draft[index];
|
||||||
|
draft[index] = temp;
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
|
||||||
|
onSort?.(index, index - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickDown = (index: number) => {
|
||||||
|
if (index === value.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
const temp = draft[index + 1];
|
||||||
|
draft[index + 1] = draft[index];
|
||||||
|
draft[index] = temp;
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
|
||||||
|
onSort?.(index, index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickAdd = (index: number) => {
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
draft.splice(index + 1, 0, "");
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
setTimeout(() => itemRefs.current[index + 1]?.focus(), 0);
|
||||||
|
|
||||||
|
onCreate?.(index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickRemove = (index: number) => {
|
||||||
|
const newValue = produce(value, (draft) => {
|
||||||
|
draft.splice(index, 1);
|
||||||
|
});
|
||||||
|
setValue(newValue);
|
||||||
|
|
||||||
|
onRemove?.(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return value == null || value.length === 0 ? (
|
||||||
|
<Button block color="primary" disabled={disabled || maxCount === 0} size={props.size} variant="dashed" onClick={handleCreate}>
|
||||||
|
{t("common.button.add")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Space className="w-full" direction="vertical" size="small">
|
||||||
|
{value.map((element, index) => {
|
||||||
|
const allowUp = index > 0;
|
||||||
|
const allowDown = index < value.length - 1;
|
||||||
|
const allowRemove = minCount == null || value.length > minCount;
|
||||||
|
const allowAdd = maxCount == null || value.length < maxCount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultipleInputItem
|
||||||
|
{...props}
|
||||||
|
ref={(ref) => (itemRefs.current[index] = ref!)}
|
||||||
|
allowAdd={allowAdd}
|
||||||
|
allowClear={allowClear}
|
||||||
|
allowDown={allowDown}
|
||||||
|
allowRemove={allowRemove}
|
||||||
|
allowUp={allowUp}
|
||||||
|
disabled={disabled}
|
||||||
|
defaultValue={undefined}
|
||||||
|
showSortButton={showSortButton}
|
||||||
|
value={element}
|
||||||
|
onBlur={() => handleInputBlur(index)}
|
||||||
|
onChange={(val) => handleInputChange(index, val)}
|
||||||
|
onClickAdd={() => handleClickAdd(index)}
|
||||||
|
onClickDown={() => handleClickDown(index)}
|
||||||
|
onClickUp={() => handleClickUp(index)}
|
||||||
|
onClickRemove={() => handleClickRemove(index)}
|
||||||
|
onValueChange={undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type MultipleInputItemProps = Omit<
|
||||||
|
MultipleInputProps,
|
||||||
|
"defaultValue" | "maxCount" | "minCount" | "preset" | "value" | "onChange" | "onCreate" | "onRemove" | "onSort" | "onValueChange"
|
||||||
|
> & {
|
||||||
|
allowAdd: boolean;
|
||||||
|
allowRemove: boolean;
|
||||||
|
allowUp: boolean;
|
||||||
|
allowDown: boolean;
|
||||||
|
defaultValue?: string;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onClickAdd?: () => void;
|
||||||
|
onClickDown?: () => void;
|
||||||
|
onClickUp?: () => void;
|
||||||
|
onClickRemove?: () => void;
|
||||||
|
onValueChange?: (value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MultipleInputItemInstance = {
|
||||||
|
focus: () => void;
|
||||||
|
blur: () => void;
|
||||||
|
select: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MultipleInputItem = forwardRef<MultipleInputItemInstance, MultipleInputItemProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
allowAdd,
|
||||||
|
allowClear,
|
||||||
|
allowDown,
|
||||||
|
allowRemove,
|
||||||
|
allowUp,
|
||||||
|
disabled,
|
||||||
|
showSortButton,
|
||||||
|
onChange,
|
||||||
|
onClickAdd,
|
||||||
|
onClickDown,
|
||||||
|
onClickUp,
|
||||||
|
onClickRemove,
|
||||||
|
...props
|
||||||
|
}: MultipleInputItemProps,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const inputRef = useRef<InputRef>(null);
|
||||||
|
|
||||||
|
const [value, setValue] = useControllableValue<string>(props, {
|
||||||
|
valuePropName: "value",
|
||||||
|
defaultValue: "",
|
||||||
|
defaultValuePropName: "defaultValue",
|
||||||
|
trigger: "onValueChange",
|
||||||
|
});
|
||||||
|
|
||||||
|
const upBtn = useMemo(() => {
|
||||||
|
if (!showSortButton) return null;
|
||||||
|
return <Button icon={<ArrowUpIcon size={14} />} color="default" disabled={disabled || !allowUp} shape="circle" variant="text" onClick={onClickUp} />;
|
||||||
|
}, [allowUp, disabled, showSortButton, onClickUp]);
|
||||||
|
const downBtn = useMemo(() => {
|
||||||
|
if (!showSortButton) return null;
|
||||||
|
return (
|
||||||
|
<Button icon={<ArrowDownIcon size={14} />} color="default" disabled={disabled || !allowDown} shape="circle" variant="text" onClick={onClickDown} />
|
||||||
|
);
|
||||||
|
}, [allowDown, disabled, showSortButton, onClickDown]);
|
||||||
|
const removeBtn = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Button icon={<MinusIcon size={14} />} color="default" disabled={disabled || !allowRemove} shape="circle" variant="text" onClick={onClickRemove} />
|
||||||
|
);
|
||||||
|
}, [allowRemove, disabled, onClickRemove]);
|
||||||
|
const addBtn = useMemo(() => {
|
||||||
|
return <Button icon={<PlusIcon size={14} />} color="default" disabled={disabled || !allowAdd} shape="circle" variant="text" onClick={onClickAdd} />;
|
||||||
|
}, [allowAdd, disabled, onClickAdd]);
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setValue(e.target.value);
|
||||||
|
|
||||||
|
onChange?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
focus: () => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
},
|
||||||
|
blur: () => {
|
||||||
|
inputRef.current?.blur();
|
||||||
|
},
|
||||||
|
select: () => {
|
||||||
|
inputRef.current?.select();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-nowrap items-center space-x-2">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
ref={inputRef}
|
||||||
|
className={undefined}
|
||||||
|
style={undefined}
|
||||||
|
allowClear={allowClear}
|
||||||
|
defaultValue={undefined}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{removeBtn}
|
||||||
|
{upBtn}
|
||||||
|
{downBtn}
|
||||||
|
{addBtn}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MultipleInput;
|
@ -33,8 +33,8 @@ export type NotifyTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const defaultNotifyTemplate: NotifyTemplate = {
|
export const defaultNotifyTemplate: NotifyTemplate = {
|
||||||
subject: "您有 {COUNT} 张证书即将过期",
|
subject: "您有 ${COUNT} 张证书即将过期",
|
||||||
message: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!",
|
message: "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!",
|
||||||
};
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ i18n
|
|||||||
backend: {
|
backend: {
|
||||||
loadPath: "/locales/{{lng}}.json",
|
loadPath: "/locales/{{lng}}.json",
|
||||||
},
|
},
|
||||||
|
detection: {
|
||||||
|
lookupLocalStorage: "certimate-ui-lang",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const localeNames = {
|
export const localeNames = {
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
"settings.notification.template.card.title": "Template",
|
"settings.notification.template.card.title": "Template",
|
||||||
"settings.notification.template.form.subject.label": "Subject",
|
"settings.notification.template.form.subject.label": "Subject",
|
||||||
"settings.notification.template.form.subject.placeholder": "Please enter notification subject",
|
"settings.notification.template.form.subject.placeholder": "Please enter notification subject",
|
||||||
"settings.notification.template.form.subject.tooltip": "Optional variables ({COUNT}: number of expiring soon)",
|
"settings.notification.template.form.subject.tooltip": "Optional variables (${COUNT}: number of expiring soon)",
|
||||||
"settings.notification.template.form.message.label": "Message",
|
"settings.notification.template.form.message.label": "Message",
|
||||||
"settings.notification.template.form.message.placeholder": "Please enter notification message",
|
"settings.notification.template.form.message.placeholder": "Please enter notification message",
|
||||||
"settings.notification.template.form.message.tooltip": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)",
|
"settings.notification.template.form.message.tooltip": "Optional variables (${COUNT}: number of expiring soon. ${DOMAINS}: Domain list)",
|
||||||
"settings.notification.channels.card.title": "Channels",
|
"settings.notification.channels.card.title": "Channels",
|
||||||
"settings.notification.channel.enabled.on": "On",
|
"settings.notification.channel.enabled.on": "On",
|
||||||
"settings.notification.channel.enabled.off": "Off",
|
"settings.notification.channel.enabled.off": "Off",
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
"workflow.props.name.default": "Unnamed",
|
"workflow.props.name.default": "Unnamed",
|
||||||
"workflow.props.description": "Description",
|
"workflow.props.description": "Description",
|
||||||
"workflow.props.description.placeholder": "Please enter description",
|
"workflow.props.description.placeholder": "Please enter description",
|
||||||
"workflow.props.execution_method": "Execution Method",
|
"workflow.props.trigger": "Trigger",
|
||||||
|
"workflow.props.trigger.auto": "Auto",
|
||||||
|
"workflow.props.trigger.manual": "Manual",
|
||||||
"workflow.props.state": "State",
|
"workflow.props.state": "State",
|
||||||
"workflow.props.state.filter.enabled": "Enabled",
|
"workflow.props.state.filter.enabled": "Enabled",
|
||||||
"workflow.props.state.filter.disabled": "Disabled",
|
"workflow.props.state.filter.disabled": "Disabled",
|
||||||
|
@ -19,10 +19,10 @@
|
|||||||
"settings.notification.template.card.title": "通知模板",
|
"settings.notification.template.card.title": "通知模板",
|
||||||
"settings.notification.template.form.subject.label": "通知主题",
|
"settings.notification.template.form.subject.label": "通知主题",
|
||||||
"settings.notification.template.form.subject.placeholder": "请输入通知主题",
|
"settings.notification.template.form.subject.placeholder": "请输入通知主题",
|
||||||
"settings.notification.template.form.subject.tooltip": "可选的变量({COUNT}: 即将过期张数)",
|
"settings.notification.template.form.subject.tooltip": "可选的变量(${COUNT}: 即将过期张数)",
|
||||||
"settings.notification.template.form.message.label": "通知内容",
|
"settings.notification.template.form.message.label": "通知内容",
|
||||||
"settings.notification.template.form.message.placeholder": "请输入通知内容",
|
"settings.notification.template.form.message.placeholder": "请输入通知内容",
|
||||||
"settings.notification.template.form.message.tooltip": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)",
|
"settings.notification.template.form.message.tooltip": "可选的变量(${COUNT}: 即将过期张数;${DOMAINS}: 域名列表)",
|
||||||
"settings.notification.channels.card.title": "通知渠道",
|
"settings.notification.channels.card.title": "通知渠道",
|
||||||
"settings.notification.channel.enabled.on": "启用",
|
"settings.notification.channel.enabled.on": "启用",
|
||||||
"settings.notification.channel.enabled.off": "未启用",
|
"settings.notification.channel.enabled.off": "未启用",
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
"workflow.props.name.default": "未命名工作流",
|
"workflow.props.name.default": "未命名工作流",
|
||||||
"workflow.props.description": "描述",
|
"workflow.props.description": "描述",
|
||||||
"workflow.props.description.placeholder": "请输入描述",
|
"workflow.props.description.placeholder": "请输入描述",
|
||||||
"workflow.props.execution_method": "执行方式",
|
"workflow.props.trigger": "触发方式",
|
||||||
|
"workflow.props.trigger.auto": "自动",
|
||||||
|
"workflow.props.trigger.manual": "手动",
|
||||||
"workflow.props.state": "启用状态",
|
"workflow.props.state": "启用状态",
|
||||||
"workflow.props.state.filter.enabled": "启用",
|
"workflow.props.state.filter.enabled": "启用",
|
||||||
"workflow.props.state.filter.disabled": "未启用",
|
"workflow.props.state.filter.disabled": "未启用",
|
||||||
|
@ -61,19 +61,19 @@ const WorkflowList = () => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "type",
|
key: "trigger",
|
||||||
title: t("workflow.props.execution_method"),
|
title: t("workflow.props.trigger"),
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
const method = record.type;
|
const trigger = record.type;
|
||||||
if (!method) {
|
if (!trigger) {
|
||||||
return "-";
|
return "-";
|
||||||
} else if (method === "manual") {
|
} else if (trigger === "manual") {
|
||||||
return <Typography.Text>{t("workflow.node.start.form.executionMethod.options.manual")}</Typography.Text>;
|
return <Typography.Text>{t("workflow.props.trigger.manual")}</Typography.Text>;
|
||||||
} else if (method === "auto") {
|
} else if (trigger === "auto") {
|
||||||
return (
|
return (
|
||||||
<Space className="max-w-full" direction="vertical" size={4}>
|
<Space className="max-w-full" direction="vertical" size={4}>
|
||||||
<Typography.Text>{t("workflow.node.start.form.executionMethod.options.auto")}</Typography.Text>
|
<Typography.Text>{t("workflow.props.trigger.auto")}</Typography.Text>
|
||||||
<Typography.Text type="secondary">{record.crontab ?? ""}</Typography.Text>
|
<Typography.Text type="secondary">{record.crontab ?? ""}</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user