mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-14 04:10:26 +08:00
[SSL证书]实现对ACME任务的增删改查
This commit is contained in:
@@ -220,6 +220,10 @@ func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
|
|||||||
return pb.NewACMEUserServiceClient(this.pickConn())
|
return pb.NewACMEUserServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
|
||||||
|
return pb.NewACMETaskServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
// 构造Admin上下文
|
// 构造Admin上下文
|
||||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -19,14 +19,24 @@ func (this *DeleteAction) RunPost(params struct {
|
|||||||
// 记录日志
|
// 记录日志
|
||||||
defer this.CreateLog(oplogs.LevelInfo, "删除DNS服务商 %d", params.ProviderId)
|
defer this.CreateLog(oplogs.LevelInfo, "删除DNS服务商 %d", params.ProviderId)
|
||||||
|
|
||||||
// 检查是否被使用
|
// 检查是否被集群使用
|
||||||
countClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithDNSProviderId(this.AdminContext(), &pb.CountAllEnabledNodeClustersWithDNSProviderIdRequest{DnsProviderId: params.ProviderId})
|
countClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithDNSProviderId(this.AdminContext(), &pb.CountAllEnabledNodeClustersWithDNSProviderIdRequest{DnsProviderId: params.ProviderId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if countClustersResp.Count > 0 {
|
if countClustersResp.Count > 0 {
|
||||||
this.Fail("当前DNS服务商账号正在被" + numberutils.FormatInt64(countClustersResp.Count) + "个集群所使用,所以不能删除。请修改后再操作。")
|
this.Fail("当前DNS服务商账号正在被" + numberutils.FormatInt64(countClustersResp.Count) + "个集群所使用,所以不能删除。请修改集群设置后再操作。")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否被ACME任务使用
|
||||||
|
countACMETasksResp, err := this.RPC().ACMETaskRPC().CountEnabledACMETasksWithDNSProviderId(this.AdminContext(), &pb.CountEnabledACMETasksWithDNSProviderIdRequest{DnsProviderId: params.ProviderId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if countACMETasksResp.Count > 0 {
|
||||||
|
this.Fail("当前DNS服务商账号正在被" + numberutils.FormatInt64(countACMETasksResp.Count) + "个ACME证书申请任务使用,所以不能删除。请修改ACME证书申请任务后再操作。")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行删除
|
// 执行删除
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ func (this *IndexAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct{}) {
|
func (this *IndexAction) RunGet(params struct{}) {
|
||||||
countResp, err := this.RPC().DNSProviderRPC().CountAllEnabledDNSProviders(this.AdminContext(), &pb.CountAllEnabledDNSProvidersRequest{})
|
countResp, err := this.RPC().DNSProviderRPC().CountAllEnabledDNSProviders(this.AdminContext(), &pb.CountAllEnabledDNSProvidersRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
@@ -26,8 +28,9 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
this.Data["page"] = page.AsHTML()
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
providersResp, err := this.RPC().DNSProviderRPC().ListEnabledDNSProviders(this.AdminContext(), &pb.ListEnabledDNSProvidersRequest{
|
providersResp, err := this.RPC().DNSProviderRPC().ListEnabledDNSProviders(this.AdminContext(), &pb.ListEnabledDNSProvidersRequest{
|
||||||
Offset: page.Offset,
|
AdminId: this.AdminId(),
|
||||||
Size: page.Size,
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
|
|||||||
@@ -1,15 +1,137 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type CreateAction struct {
|
type CreateAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreateAction) Init() {
|
func (this *CreateAction) Init() {
|
||||||
this.Nav("", "", "")
|
this.Nav("", "", "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreateAction) RunGet(params struct{}) {
|
func (this *CreateAction) RunGet(params struct{}) {
|
||||||
|
// 获取所有可用的用户
|
||||||
|
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userMaps := []maps.Map{}
|
||||||
|
for _, user := range usersResp.AcmeUsers {
|
||||||
|
description := user.Description
|
||||||
|
if len(description) > 0 {
|
||||||
|
description = "(" + description + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
userMaps = append(userMaps, maps.Map{
|
||||||
|
"id": user.Id,
|
||||||
|
"description": description,
|
||||||
|
"email": user.Email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["users"] = userMaps
|
||||||
|
|
||||||
|
// 域名解析服务商
|
||||||
|
providersResp, err := this.RPC().DNSProviderRPC().FindAllEnabledDNSProviders(this.AdminContext(), &pb.FindAllEnabledDNSProvidersRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
providerMaps := []maps.Map{}
|
||||||
|
for _, provider := range providersResp.DnsProviders {
|
||||||
|
providerMaps = append(providerMaps, maps.Map{
|
||||||
|
"id": provider.Id,
|
||||||
|
"name": provider.Name,
|
||||||
|
"typeName": provider.TypeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["providers"] = providerMaps
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *CreateAction) RunPost(params struct {
|
||||||
|
TaskId int64
|
||||||
|
AcmeUserId int64
|
||||||
|
DnsProviderId int64
|
||||||
|
DnsDomain string
|
||||||
|
Domains []string
|
||||||
|
AutoRenew bool
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
}) {
|
||||||
|
if params.AcmeUserId <= 0 {
|
||||||
|
this.Fail("请选择一个申请证书的用户")
|
||||||
|
}
|
||||||
|
if params.DnsProviderId <= 0 {
|
||||||
|
this.Fail("请选择DNS服务商")
|
||||||
|
}
|
||||||
|
if len(params.DnsDomain) == 0 {
|
||||||
|
this.Fail("请输入顶级域名")
|
||||||
|
}
|
||||||
|
dnsDomain := strings.ToLower(params.DnsDomain)
|
||||||
|
if !domainutils.ValidateDomainFormat(dnsDomain) {
|
||||||
|
this.Fail("请输入正确的顶级域名")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.Domains) == 0 {
|
||||||
|
this.Fail("请输入证书域名列表")
|
||||||
|
}
|
||||||
|
realDomains := []string{}
|
||||||
|
for _, domain := range params.Domains {
|
||||||
|
domain = strings.ToLower(domain)
|
||||||
|
if !strings.HasSuffix(domain, "."+dnsDomain) && domain != dnsDomain {
|
||||||
|
this.Fail("证书域名中的" + domain + "和顶级域名不一致")
|
||||||
|
}
|
||||||
|
realDomains = append(realDomains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.TaskId == 0 {
|
||||||
|
createResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
|
||||||
|
AcmeUserId: params.AcmeUserId,
|
||||||
|
DnsProviderId: params.DnsProviderId,
|
||||||
|
DnsDomain: dnsDomain,
|
||||||
|
Domains: realDomains,
|
||||||
|
AutoRenew: params.AutoRenew,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params.TaskId = createResp.AcmeTaskId
|
||||||
|
defer this.CreateLogInfo("创建证书申请任务 %d", createResp.AcmeTaskId)
|
||||||
|
} else {
|
||||||
|
_, err := this.RPC().ACMETaskRPC().UpdateACMETask(this.AdminContext(), &pb.UpdateACMETaskRequest{
|
||||||
|
AcmeTaskId: params.TaskId,
|
||||||
|
AcmeUserId: params.AcmeUserId,
|
||||||
|
DnsProviderId: params.DnsProviderId,
|
||||||
|
DnsDomain: dnsDomain,
|
||||||
|
Domains: realDomains,
|
||||||
|
AutoRenew: params.AutoRenew,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer this.CreateLogInfo("修改证书申请任务 %d", params.TaskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["taskId"] = params.TaskId
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteTaskAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DeleteTaskAction) RunPost(params struct {
|
||||||
|
TaskId int64
|
||||||
|
}) {
|
||||||
|
defer this.CreateLogInfo("删除证书申请任务 %d", params.TaskId)
|
||||||
|
|
||||||
|
_, err := this.RPC().ACMETaskRPC().DeleteACMETask(this.AdminContext(), &pb.DeleteACMETaskRequest{AcmeTaskId: params.TaskId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -1,15 +1,75 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) Init() {
|
func (this *IndexAction) Init() {
|
||||||
this.Nav("", "", "cert")
|
this.Nav("", "", "task")
|
||||||
|
this.SecondMenu("list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct{}) {
|
func (this *IndexAction) RunGet(params struct{}) {
|
||||||
|
countResp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count := countResp.Count
|
||||||
|
page := this.NewPage(count)
|
||||||
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
|
tasksResp, err := this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
taskMaps := []maps.Map{}
|
||||||
|
for _, task := range tasksResp.AcmeTasks {
|
||||||
|
if task.AcmeUser == nil || task.DnsProvider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var certMap maps.Map = nil
|
||||||
|
if task.SslCert != nil {
|
||||||
|
certMap = maps.Map{
|
||||||
|
"id": task.SslCert.Id,
|
||||||
|
"name": task.SslCert.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taskMaps = append(taskMaps, maps.Map{
|
||||||
|
"id": task.Id,
|
||||||
|
"acmeUser": maps.Map{
|
||||||
|
"id": task.AcmeUser.Id,
|
||||||
|
"email": task.AcmeUser.Email,
|
||||||
|
},
|
||||||
|
"dnsProvider": maps.Map{
|
||||||
|
"id": task.DnsProvider.Id,
|
||||||
|
"name": task.DnsProvider.Name,
|
||||||
|
},
|
||||||
|
"dnsDomain": task.DnsDomain,
|
||||||
|
"domains": task.Domains,
|
||||||
|
"autoRenew": task.AutoRenew,
|
||||||
|
"cert": certMap,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["tasks"] = taskMaps
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
15
internal/web/actions/default/servers/certs/acme/run.go
Normal file
15
internal/web/actions/default/servers/certs/acme/run.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RunAction) RunPost(params struct{}) {
|
||||||
|
time.Sleep(5 * time.Second) // TODO
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateTaskPopupAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UpdateTaskPopupAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UpdateTaskPopupAction) RunGet(params struct {
|
||||||
|
TaskId int64
|
||||||
|
}) {
|
||||||
|
taskResp, err := this.RPC().ACMETaskRPC().FindEnabledACMETask(this.AdminContext(), &pb.FindEnabledACMETaskRequest{AcmeTaskId: params.TaskId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
task := taskResp.AcmeTask
|
||||||
|
if task == nil {
|
||||||
|
this.NotFound("acmeTask", params.TaskId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsProviderMap maps.Map
|
||||||
|
if task.DnsProvider != nil {
|
||||||
|
dnsProviderMap = maps.Map{
|
||||||
|
"id": task.DnsProvider.Id,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dnsProviderMap = maps.Map{
|
||||||
|
"id": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var acmeUserMap maps.Map
|
||||||
|
if task.AcmeUser != nil {
|
||||||
|
acmeUserMap = maps.Map{
|
||||||
|
"id": task.AcmeUser.Id,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acmeUserMap = maps.Map{
|
||||||
|
"id": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["task"] = maps.Map{
|
||||||
|
"id": task.Id,
|
||||||
|
"acmeUser": acmeUserMap,
|
||||||
|
"dnsProviderId": task.DnsProvider.Id,
|
||||||
|
"dnsDomain": task.DnsDomain,
|
||||||
|
"domains": task.Domains,
|
||||||
|
"autoRenew": task.AutoRenew,
|
||||||
|
"isOn": task.IsOn,
|
||||||
|
"dnsProvider": dnsProviderMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 域名解析服务商
|
||||||
|
providersResp, err := this.RPC().DNSProviderRPC().FindAllEnabledDNSProviders(this.AdminContext(), &pb.FindAllEnabledDNSProvidersRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
providerMaps := []maps.Map{}
|
||||||
|
for _, provider := range providersResp.DnsProviders {
|
||||||
|
providerMaps = append(providerMaps, maps.Map{
|
||||||
|
"id": provider.Id,
|
||||||
|
"name": provider.Name,
|
||||||
|
"typeName": provider.TypeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["providers"] = providerMaps
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *UpdateTaskPopupAction) RunPost(params struct {
|
||||||
|
TaskId int64
|
||||||
|
AcmeUserId int64
|
||||||
|
DnsProviderId int64
|
||||||
|
DnsDomain string
|
||||||
|
Domains []string
|
||||||
|
AutoRenew bool
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
CSRF *actionutils.CSRF
|
||||||
|
}) {
|
||||||
|
defer this.CreateLogInfo("修改证书申请任务 %d", params.TaskId)
|
||||||
|
|
||||||
|
if params.AcmeUserId <= 0 {
|
||||||
|
this.Fail("请选择一个申请证书的用户")
|
||||||
|
}
|
||||||
|
if params.DnsProviderId <= 0 {
|
||||||
|
this.Fail("请选择DNS服务商")
|
||||||
|
}
|
||||||
|
if len(params.DnsDomain) == 0 {
|
||||||
|
this.Fail("请输入顶级域名")
|
||||||
|
}
|
||||||
|
dnsDomain := strings.ToLower(params.DnsDomain)
|
||||||
|
if !domainutils.ValidateDomainFormat(dnsDomain) {
|
||||||
|
this.Fail("请输入正确的顶级域名")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.Domains) == 0 {
|
||||||
|
this.Fail("请输入证书域名列表")
|
||||||
|
}
|
||||||
|
realDomains := []string{}
|
||||||
|
for _, domain := range params.Domains {
|
||||||
|
domain = strings.ToLower(domain)
|
||||||
|
if !strings.HasSuffix(domain, "."+dnsDomain) && domain != dnsDomain {
|
||||||
|
this.Fail("证书域名中的" + domain + "和顶级域名不一致")
|
||||||
|
}
|
||||||
|
realDomains = append(realDomains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := this.RPC().ACMETaskRPC().UpdateACMETask(this.AdminContext(), &pb.UpdateACMETaskRequest{
|
||||||
|
AcmeTaskId: params.TaskId,
|
||||||
|
AcmeUserId: params.AcmeUserId,
|
||||||
|
DnsProviderId: params.DnsProviderId,
|
||||||
|
DnsDomain: dnsDomain,
|
||||||
|
Domains: realDomains,
|
||||||
|
AutoRenew: params.AutoRenew,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreatePopupAction struct {
|
type CreatePopupAction struct {
|
||||||
@@ -39,6 +40,13 @@ func (this *CreatePopupAction) RunPost(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回数据
|
||||||
|
this.Data["acmeUser"] = maps.Map{
|
||||||
|
"id": createResp.AcmeUserId,
|
||||||
|
"description": params.Description,
|
||||||
|
"email": params.Email,
|
||||||
|
}
|
||||||
|
|
||||||
// 日志
|
// 日志
|
||||||
defer this.CreateLogInfo("创建ACME用户 %d", createResp.AcmeUserId)
|
defer this.CreateLogInfo("创建ACME用户 %d", createResp.AcmeUserId)
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ func (this *DeleteAction) RunPost(params struct {
|
|||||||
}) {
|
}) {
|
||||||
defer this.CreateLogInfo("删除ACME用户 %d", params.UserId)
|
defer this.CreateLogInfo("删除ACME用户 %d", params.UserId)
|
||||||
|
|
||||||
countResp, err := this.RPC().SSLCertRPC().CountSSLCertsWithACMEUserId(this.AdminContext(), &pb.CountSSLCertsWithACMEUserIdRequest{AcmeUserId: params.UserId})
|
countResp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasksWithACMEUserId(this.AdminContext(), &pb.CountAllEnabledACMETasksWithACMEUserIdRequest{AcmeUserId: params.UserId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if countResp.Count > 0 {
|
if countResp.Count > 0 {
|
||||||
this.Fail("有证书正在和这个用户关联,所以不能删除")
|
this.Fail("有任务正在和这个用户关联,所以不能删除")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = this.RPC().ACMEUserRPC().DeleteACMEUser(this.AdminContext(), &pb.DeleteACMEUserRequest{AcmeUserId: params.UserId})
|
_, err = this.RPC().ACMEUserRPC().DeleteACMEUser(this.AdminContext(), &pb.DeleteACMEUserRequest{AcmeUserId: params.UserId})
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (this *Helper) BeforeAction(action *actions.ActionObject) {
|
|||||||
"isActive": action.Data.GetString("leftMenuItem") == "cert",
|
"isActive": action.Data.GetString("leftMenuItem") == "cert",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "免费证书",
|
"name": "申请证书",
|
||||||
"url": "/servers/certs/acme",
|
"url": "/servers/certs/acme",
|
||||||
"isActive": action.Data.GetString("leftMenuItem") == "acme",
|
"isActive": action.Data.GetString("leftMenuItem") == "acme",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ func init() {
|
|||||||
Data("leftMenuItem", "acme").
|
Data("leftMenuItem", "acme").
|
||||||
Get("", new(acme.IndexAction)).
|
Get("", new(acme.IndexAction)).
|
||||||
GetPost("/create", new(acme.CreateAction)).
|
GetPost("/create", new(acme.CreateAction)).
|
||||||
|
Post("/run", new(acme.RunAction)).
|
||||||
|
GetPost("/updateTaskPopup", new(acme.UpdateTaskPopupAction)).
|
||||||
|
Post("/deleteTask", new(acme.DeleteTaskAction)).
|
||||||
|
|
||||||
Prefix("/servers/certs/acme/users").
|
Prefix("/servers/certs/acme/users").
|
||||||
Get("", new(users.IndexAction)).
|
Get("", new(users.IndexAction)).
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ func (this *ViewCertAction) RunGet(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(certResp.CertJSON) == 0 {
|
||||||
|
this.NotFound("sslCert", params.CertId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
certConfig := &sslconfigs.SSLCertConfig{}
|
certConfig := &sslconfigs.SSLCertConfig{}
|
||||||
err = json.Unmarshal(certResp.CertJSON, certConfig)
|
err = json.Unmarshal(certResp.CertJSON, certConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<first-menu>
|
<first-menu>
|
||||||
<menu-item href="/servers/certs/acme" code="cert">证书</menu-item>
|
<menu-item href="/servers/certs/acme" code="task">所有任务</menu-item>
|
||||||
<menu-item href="/servers/certs/acme/users" code="user">用户</menu-item>
|
<menu-item href="/servers/certs/acme/create" code="create">新申请</menu-item>
|
||||||
|
<menu-item href="/servers/certs/acme/users" code="user">ACME用户</menu-item>
|
||||||
</first-menu>
|
</first-menu>
|
||||||
|
|||||||
15
web/views/@default/servers/certs/acme/create.css
Normal file
15
web/views/@default/servers/certs/acme/create.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.button-group {
|
||||||
|
margin-top: 4em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.button-group .button.primary {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.success-box {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2em;
|
||||||
|
}
|
||||||
|
.success-box p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=create.css.map */
|
||||||
1
web/views/@default/servers/certs/acme/create.css.map
Normal file
1
web/views/@default/servers/certs/acme/create.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["create.less"],"names":[],"mappings":"AAAA;EACC,eAAA;EACA,UAAA;;AAFD,aAIC,QAAO;EACN,YAAA;;AAIF;EACC,kBAAA;EACA,gBAAA;;AAFD,YAIC;EACC,gBAAA","file":"create.css"}
|
||||||
121
web/views/@default/servers/certs/acme/create.html
Normal file
121
web/views/@default/servers/certs/acme/create.html
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{$layout}
|
||||||
|
{$template "/left_menu_top"}
|
||||||
|
|
||||||
|
<div class="right-box without-tabbar">
|
||||||
|
{$template "menu"}
|
||||||
|
|
||||||
|
<div class="margin"></div>
|
||||||
|
|
||||||
|
<form class="ui form">
|
||||||
|
<div class="ui steps fluid small">
|
||||||
|
<div class="ui step" :class="{active:step == 'prepare'}">
|
||||||
|
准备工作
|
||||||
|
</div>
|
||||||
|
<div class="ui step" :class="{active:step == 'user'}">
|
||||||
|
选择用户
|
||||||
|
</div>
|
||||||
|
<div class="ui step" :class="{active:step == 'dns'}">
|
||||||
|
设置域名解析
|
||||||
|
</div>
|
||||||
|
<div class="ui step" :class="{active:step == 'finish'}">
|
||||||
|
完成
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 准备工作 -->
|
||||||
|
<div v-show="step == 'prepare'">
|
||||||
|
我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录,请先确保你已经在"域名解析" -- <a href="/dns/providers" target="_blank">"DNS服务商"</a> 中已经添加了对应的DNS服务商账号。
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="ui button primary" @click.prevent="doPrepare">下一步</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选择用户 -->
|
||||||
|
<div v-show="step == 'user'">
|
||||||
|
<table class="ui table definition selectable">
|
||||||
|
<tr>
|
||||||
|
<td class="title">选择用户</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="users.length > 0">
|
||||||
|
<div class="ui fields inline">
|
||||||
|
<div class="ui field">
|
||||||
|
<select class="ui dropdown" v-model="userId">
|
||||||
|
<option value="0">[请选择]</option>
|
||||||
|
<option v-for="user in users" :value="user.id">{{user.email}}{{user.description}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ui field">
|
||||||
|
<a href="" @click.prevent="createUser">[新创建]</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else><a href="" @click.prevent="createUser">暂时还没有用户,点此创建</a></div>
|
||||||
|
<p class="comment">选择一个作为申请证书的用户。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="ui button" @click.prevent="goPrepare">上一步</button>
|
||||||
|
<button type="button" class="ui button primary" @click.prevent="doUser">下一步</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设置域名解析 -->
|
||||||
|
<div v-show="step == 'dns'">
|
||||||
|
<table class="ui table definition selectable">
|
||||||
|
<tr>
|
||||||
|
<td class="title">选择DNS服务商 *</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="providers.length > 0">
|
||||||
|
<select class="ui dropdown auto-width" v-model="dnsProviderId">
|
||||||
|
<option value="0">[请选择]</option>
|
||||||
|
<option v-for="provider in providers" :value="provider.id">{{provider.name}}({{provider.typeName}})</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="comment">用于自动创建域名解析记录。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>顶级域名 *</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" maxlength="100" v-model="dnsDomain"/>
|
||||||
|
<p class="comment">用于在DNS服务商账号中操作解析记录的域名,比如 example.com,不要输入二级或别的多级域名。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>证书域名列表 *</td>
|
||||||
|
<td>
|
||||||
|
<values-box name="" placeholder="域名" size="30" @change="changeDomains"></values-box>
|
||||||
|
<p class="comment">需要申请的证书中包含的域名列表,所有域名必须是同一个顶级域名。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>自动续期</td>
|
||||||
|
<td>
|
||||||
|
<checkbox v-model="autoRenew"></checkbox>
|
||||||
|
<p class="comment">在免费证书临近到期之前,是否尝试自动续期。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="ui button" @click.prevent="goUser">上一步</button>
|
||||||
|
<button type="button" class="ui button primary" @click.prevent="doDNS" v-if="!isRequesting">下一步</button>
|
||||||
|
<button type="button" class="ui button primary disabled" v-if="isRequesting">提交中...</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完成 -->
|
||||||
|
<div v-show="step == 'finish'">
|
||||||
|
<div class="success-box">
|
||||||
|
<p><span class="green">恭喜,证书申请成功!</span>你可以在证书列表里看到刚申请的证书,也可以 <a href="" @click.prevent="viewCert">点击这里</a> 查看证书详情。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="ui button primary" @click.prevent="doFinish">返回</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
120
web/views/@default/servers/certs/acme/create.js
Normal file
120
web/views/@default/servers/certs/acme/create.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.step = "prepare"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 准备工作
|
||||||
|
*/
|
||||||
|
this.doPrepare = function () {
|
||||||
|
this.step = "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择用户
|
||||||
|
*/
|
||||||
|
this.userId = 0
|
||||||
|
|
||||||
|
this.goPrepare = function () {
|
||||||
|
this.step = "prepare"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createUser = function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/certs/acme/users/createPopup", {
|
||||||
|
callback: function (resp) {
|
||||||
|
teaweb.successToast("创建成功")
|
||||||
|
|
||||||
|
let acmeUser = resp.data.acmeUser
|
||||||
|
let description = acmeUser.description
|
||||||
|
if (description.length > 0) {
|
||||||
|
description = "(" + description + ")"
|
||||||
|
}
|
||||||
|
that.userId = acmeUser.id
|
||||||
|
that.users.unshift({
|
||||||
|
id: acmeUser.id,
|
||||||
|
description: description,
|
||||||
|
email: acmeUser.email
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doUser = function () {
|
||||||
|
if (this.userId == 0) {
|
||||||
|
teaweb.warn("请选择一个申请证书的用户")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.step = "dns"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置DNS解析
|
||||||
|
*/
|
||||||
|
this.dnsProviderId = 0
|
||||||
|
this.dnsDomain = ""
|
||||||
|
this.autoRenew = true
|
||||||
|
this.domains = []
|
||||||
|
this.taskId = 0
|
||||||
|
this.isRequesting = false
|
||||||
|
|
||||||
|
this.goUser = function () {
|
||||||
|
this.step = "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeDomains = function (v) {
|
||||||
|
this.domains = v
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doDNS = function () {
|
||||||
|
this.isRequesting = true
|
||||||
|
let that = this
|
||||||
|
|
||||||
|
this.$post("$")
|
||||||
|
.params({
|
||||||
|
acmeUserId: this.userId,
|
||||||
|
dnsProviderId: this.dnsProviderId,
|
||||||
|
dnsDomain: this.dnsDomain,
|
||||||
|
domains: this.domains,
|
||||||
|
autoRenew: this.autoRenew ? 1 : 0,
|
||||||
|
taskId: this.taskId
|
||||||
|
})
|
||||||
|
.success(function (resp) {
|
||||||
|
this.taskId = resp.data.taskId
|
||||||
|
|
||||||
|
this.isRequesting = true
|
||||||
|
this.$post(".run")
|
||||||
|
.params({
|
||||||
|
taskId: this.taskId
|
||||||
|
})
|
||||||
|
.success(function (resp) {
|
||||||
|
that.certId = resp.data.certId
|
||||||
|
that.step = "finish"
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
that.isRequesting = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
this.isRequesting = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成
|
||||||
|
*/
|
||||||
|
this.certId = 0
|
||||||
|
|
||||||
|
this.goDNS = function () {
|
||||||
|
this.step = "dns"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doFinish = function () {
|
||||||
|
window.location = "/servers/certs/acme"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewCert = function () {
|
||||||
|
teaweb.popup("/servers/certs/certPopup?certId=" + this.certId, {
|
||||||
|
height: "28em",
|
||||||
|
width: "48em"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
17
web/views/@default/servers/certs/acme/create.less
Normal file
17
web/views/@default/servers/certs/acme/create.less
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.button-group {
|
||||||
|
margin-top: 4em;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.button.primary {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-box {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2em;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,51 @@
|
|||||||
|
|
||||||
<div class="right-box without-tabbar">
|
<div class="right-box without-tabbar">
|
||||||
{$template "menu"}
|
{$template "menu"}
|
||||||
|
|
||||||
|
<p class="comment" v-if="tasks.length == 0">暂时还没有证书申请任务。</p>
|
||||||
|
|
||||||
|
<table class="ui table selectable" v-if="tasks.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ACME用户</th>
|
||||||
|
<th>域名服务商</th>
|
||||||
|
<th>顶级域名</th>
|
||||||
|
<th>证书域名</th>
|
||||||
|
<th>到期时间</th>
|
||||||
|
<th class="center width10">自动续期</th>
|
||||||
|
<th>关联证书</th>
|
||||||
|
<th class="three op">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="task in tasks">
|
||||||
|
<td>{{task.acmeUser.email}}</td>
|
||||||
|
<td>{{task.dnsProvider.name}}</td>
|
||||||
|
<td>{{task.dnsDomain}}</td>
|
||||||
|
<td>
|
||||||
|
<div v-for="domain in task.domains">
|
||||||
|
<span class="ui label small basic">{{domain}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td class="center">
|
||||||
|
<span class="green" v-if="task.autoRenew">Y</span>
|
||||||
|
<span v-else class="disabled">N</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="task.cert != null">
|
||||||
|
<a href="" @click.prevent="viewCert(task.cert.id)">{{task.cert.name}}</a>
|
||||||
|
</div>
|
||||||
|
<span class="disabled" v-else="">-</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="" @click.prevent="updateTask(task.id)">修改</a>
|
||||||
|
<a href="">执行</a>
|
||||||
|
<a href="" @click.prevent="deleteTask(task.id)">删除</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="page" v-html="page"></div>
|
||||||
</div>
|
</div>
|
||||||
30
web/views/@default/servers/certs/acme/index.js
Normal file
30
web/views/@default/servers/certs/acme/index.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.viewCert = function (certId) {
|
||||||
|
teaweb.popup("/servers/certs/certPopup?certId=" + certId, {
|
||||||
|
height: "28em",
|
||||||
|
width: "48em"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTask = function (taskId) {
|
||||||
|
teaweb.popup("/servers/certs/acme/updateTaskPopup?taskId=" + taskId, {
|
||||||
|
height: "26em",
|
||||||
|
callback: function () {
|
||||||
|
teaweb.success("保存成功,如果证书域名发生了改变,请重新执行生成新证书", function () {
|
||||||
|
teaweb.reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deleteTask = function (taskId) {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定要删除此任务吗?", function () {
|
||||||
|
that.$post("/servers/certs/acme/deleteTask")
|
||||||
|
.params({
|
||||||
|
taskId: taskId
|
||||||
|
})
|
||||||
|
.refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
46
web/views/@default/servers/certs/acme/updateTaskPopup.html
Normal file
46
web/views/@default/servers/certs/acme/updateTaskPopup.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{$layout "layout_popup"}
|
||||||
|
|
||||||
|
<h3>修改申请任务</h3>
|
||||||
|
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||||
|
<csrf-token></csrf-token>
|
||||||
|
<input type="hidden" name="taskId" :value="task.id"/>
|
||||||
|
<input type="hidden" name="acmeUserId" :value="task.acmeUser.id"/>
|
||||||
|
|
||||||
|
<table class="ui table definition selectable">
|
||||||
|
<tr>
|
||||||
|
<td class="title">选择DNS服务商 *</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="providers.length > 0">
|
||||||
|
<select class="ui dropdown auto-width" name="dnsProviderId" v-model="task.dnsProvider.id">
|
||||||
|
<option value="0">[请选择]</option>
|
||||||
|
<option v-for="provider in providers" :value="provider.id">{{provider.name}}({{provider.typeName}})</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="comment">用于自动创建域名解析记录。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>顶级域名 *</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" maxlength="100" name="dnsDomain" v-model="task.dnsDomain"/>
|
||||||
|
<p class="comment">用于在DNS服务商账号中操作解析记录的域名,比如 example.com,不要输入二级或别的多级域名。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>证书域名列表 *</td>
|
||||||
|
<td>
|
||||||
|
<values-box name="domains" :values="task.domains" placeholder="域名" size="30"></values-box>
|
||||||
|
<p class="comment">需要申请的证书中包含的域名列表,所有域名必须是同一个顶级域名。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>自动续期</td>
|
||||||
|
<td>
|
||||||
|
<checkbox name="autoRenew" v-model="task.autoRenew"></checkbox>
|
||||||
|
<p class="comment">在免费证书临近到期之前,是否尝试自动续期。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<submit-btn></submit-btn>
|
||||||
|
</form>
|
||||||
Reference in New Issue
Block a user