diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index c4614c69..3a71cf36 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -220,6 +220,10 @@ func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient { return pb.NewACMEUserServiceClient(this.pickConn()) } +func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient { + return pb.NewACMETaskServiceClient(this.pickConn()) +} + // 构造Admin上下文 func (this *RPCClient) Context(adminId int64) context.Context { ctx := context.Background() diff --git a/internal/web/actions/default/dns/providers/delete.go b/internal/web/actions/default/dns/providers/delete.go index 36c9202a..fda6ec12 100644 --- a/internal/web/actions/default/dns/providers/delete.go +++ b/internal/web/actions/default/dns/providers/delete.go @@ -19,14 +19,24 @@ func (this *DeleteAction) RunPost(params struct { // 记录日志 defer this.CreateLog(oplogs.LevelInfo, "删除DNS服务商 %d", params.ProviderId) - // 检查是否被使用 + // 检查是否被集群使用 countClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithDNSProviderId(this.AdminContext(), &pb.CountAllEnabledNodeClustersWithDNSProviderIdRequest{DnsProviderId: params.ProviderId}) if err != nil { this.ErrorPage(err) return } 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证书申请任务后再操作。") } // 执行删除 diff --git a/internal/web/actions/default/dns/providers/index.go b/internal/web/actions/default/dns/providers/index.go index b68dcd25..d8a3ec8a 100644 --- a/internal/web/actions/default/dns/providers/index.go +++ b/internal/web/actions/default/dns/providers/index.go @@ -16,7 +16,9 @@ func (this *IndexAction) Init() { } 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 { this.ErrorPage(err) return @@ -26,8 +28,9 @@ func (this *IndexAction) RunGet(params struct{}) { this.Data["page"] = page.AsHTML() providersResp, err := this.RPC().DNSProviderRPC().ListEnabledDNSProviders(this.AdminContext(), &pb.ListEnabledDNSProvidersRequest{ - Offset: page.Offset, - Size: page.Size, + AdminId: this.AdminId(), + Offset: page.Offset, + Size: page.Size, }) if err != nil { this.ErrorPage(err) diff --git a/internal/web/actions/default/servers/certs/acme/create.go b/internal/web/actions/default/servers/certs/acme/create.go index 091df02d..5a03d26a 100644 --- a/internal/web/actions/default/servers/certs/acme/create.go +++ b/internal/web/actions/default/servers/certs/acme/create.go @@ -1,15 +1,137 @@ 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 { actionutils.ParentAction } func (this *CreateAction) Init() { - this.Nav("", "", "") + this.Nav("", "", "create") } 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() } + +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() +} diff --git a/internal/web/actions/default/servers/certs/acme/deleteTask.go b/internal/web/actions/default/servers/certs/acme/deleteTask.go new file mode 100644 index 00000000..c4c1ce0b --- /dev/null +++ b/internal/web/actions/default/servers/certs/acme/deleteTask.go @@ -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() +} diff --git a/internal/web/actions/default/servers/certs/acme/index.go b/internal/web/actions/default/servers/certs/acme/index.go index b7ee3e04..cf6f6c91 100644 --- a/internal/web/actions/default/servers/certs/acme/index.go +++ b/internal/web/actions/default/servers/certs/acme/index.go @@ -1,15 +1,75 @@ 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 { actionutils.ParentAction } func (this *IndexAction) Init() { - this.Nav("", "", "cert") + this.Nav("", "", "task") + this.SecondMenu("list") } 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() } diff --git a/internal/web/actions/default/servers/certs/acme/run.go b/internal/web/actions/default/servers/certs/acme/run.go new file mode 100644 index 00000000..986b90d2 --- /dev/null +++ b/internal/web/actions/default/servers/certs/acme/run.go @@ -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() +} diff --git a/internal/web/actions/default/servers/certs/acme/updateTaskPopup.go b/internal/web/actions/default/servers/certs/acme/updateTaskPopup.go new file mode 100644 index 00000000..efd0e9ef --- /dev/null +++ b/internal/web/actions/default/servers/certs/acme/updateTaskPopup.go @@ -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() +} diff --git a/internal/web/actions/default/servers/certs/acme/users/createPopup.go b/internal/web/actions/default/servers/certs/acme/users/createPopup.go index 829e04f4..41fd1828 100644 --- a/internal/web/actions/default/servers/certs/acme/users/createPopup.go +++ b/internal/web/actions/default/servers/certs/acme/users/createPopup.go @@ -4,6 +4,7 @@ import ( "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/maps" ) type CreatePopupAction struct { @@ -39,6 +40,13 @@ func (this *CreatePopupAction) RunPost(params struct { return } + // 返回数据 + this.Data["acmeUser"] = maps.Map{ + "id": createResp.AcmeUserId, + "description": params.Description, + "email": params.Email, + } + // 日志 defer this.CreateLogInfo("创建ACME用户 %d", createResp.AcmeUserId) diff --git a/internal/web/actions/default/servers/certs/acme/users/delete.go b/internal/web/actions/default/servers/certs/acme/users/delete.go index 0155e83c..048fef5e 100644 --- a/internal/web/actions/default/servers/certs/acme/users/delete.go +++ b/internal/web/actions/default/servers/certs/acme/users/delete.go @@ -14,13 +14,13 @@ func (this *DeleteAction) RunPost(params struct { }) { 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 { this.ErrorPage(err) return } if countResp.Count > 0 { - this.Fail("有证书正在和这个用户关联,所以不能删除") + this.Fail("有任务正在和这个用户关联,所以不能删除") } _, err = this.RPC().ACMEUserRPC().DeleteACMEUser(this.AdminContext(), &pb.DeleteACMEUserRequest{AcmeUserId: params.UserId}) diff --git a/internal/web/actions/default/servers/certs/helper.go b/internal/web/actions/default/servers/certs/helper.go index 54697eed..ce54fa82 100644 --- a/internal/web/actions/default/servers/certs/helper.go +++ b/internal/web/actions/default/servers/certs/helper.go @@ -27,7 +27,7 @@ func (this *Helper) BeforeAction(action *actions.ActionObject) { "isActive": action.Data.GetString("leftMenuItem") == "cert", }, { - "name": "免费证书", + "name": "申请证书", "url": "/servers/certs/acme", "isActive": action.Data.GetString("leftMenuItem") == "acme", }, diff --git a/internal/web/actions/default/servers/certs/init.go b/internal/web/actions/default/servers/certs/init.go index 7fb2d0c9..9af61b81 100644 --- a/internal/web/actions/default/servers/certs/init.go +++ b/internal/web/actions/default/servers/certs/init.go @@ -35,6 +35,9 @@ func init() { Data("leftMenuItem", "acme"). Get("", new(acme.IndexAction)). 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"). Get("", new(users.IndexAction)). diff --git a/internal/web/actions/default/servers/certs/viewCert.go b/internal/web/actions/default/servers/certs/viewCert.go index 90923a67..d746097c 100644 --- a/internal/web/actions/default/servers/certs/viewCert.go +++ b/internal/web/actions/default/servers/certs/viewCert.go @@ -24,6 +24,11 @@ func (this *ViewCertAction) RunGet(params struct { return } + if len(certResp.CertJSON) == 0 { + this.NotFound("sslCert", params.CertId) + return + } + certConfig := &sslconfigs.SSLCertConfig{} err = json.Unmarshal(certResp.CertJSON, certConfig) if err != nil { diff --git a/web/views/@default/servers/certs/acme/@menu.html b/web/views/@default/servers/certs/acme/@menu.html index 919facd6..9a6fbc42 100644 --- a/web/views/@default/servers/certs/acme/@menu.html +++ b/web/views/@default/servers/certs/acme/@menu.html @@ -1,4 +1,5 @@ - 证书 - 用户 + 所有任务 + 新申请 + ACME用户 diff --git a/web/views/@default/servers/certs/acme/create.css b/web/views/@default/servers/certs/acme/create.css new file mode 100644 index 00000000..bf7c84b3 --- /dev/null +++ b/web/views/@default/servers/certs/acme/create.css @@ -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 */ \ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/create.css.map b/web/views/@default/servers/certs/acme/create.css.map new file mode 100644 index 00000000..f8120b7f --- /dev/null +++ b/web/views/@default/servers/certs/acme/create.css.map @@ -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"} \ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/create.html b/web/views/@default/servers/certs/acme/create.html new file mode 100644 index 00000000..98626ee2 --- /dev/null +++ b/web/views/@default/servers/certs/acme/create.html @@ -0,0 +1,121 @@ +{$layout} +{$template "/left_menu_top"} + +
+ {$template "menu"} + +
+ +
+
+
+ 准备工作 +
+
+ 选择用户 +
+
+ 设置域名解析 +
+
+ 完成 +
+
+ + +
+ 我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录,请先确保你已经在"域名解析" -- "DNS服务商" 中已经添加了对应的DNS服务商账号。 + +
+ +
+
+ + +
+ + + + + +
选择用户 +
+
+
+ +
+ +
+
+ +

选择一个作为申请证书的用户。

+
+ +
+ + +
+
+ + +
+ + + + + + + + + + + + + + + + + +
选择DNS服务商 * +
+ +
+

用于自动创建域名解析记录。

+
顶级域名 * + +

用于在DNS服务商账号中操作解析记录的域名,比如 example.com,不要输入二级或别的多级域名。

+
证书域名列表 * + +

需要申请的证书中包含的域名列表,所有域名必须是同一个顶级域名。

+
自动续期 + +

在免费证书临近到期之前,是否尝试自动续期。

+
+ +
+ + + +
+
+ + +
+
+

恭喜,证书申请成功!你可以在证书列表里看到刚申请的证书,也可以 点击这里 查看证书详情。

+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/create.js b/web/views/@default/servers/certs/acme/create.js new file mode 100644 index 00000000..f58e7769 --- /dev/null +++ b/web/views/@default/servers/certs/acme/create.js @@ -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" + }) + } +}) \ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/create.less b/web/views/@default/servers/certs/acme/create.less new file mode 100644 index 00000000..332ecc58 --- /dev/null +++ b/web/views/@default/servers/certs/acme/create.less @@ -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; + } +} \ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/index.html b/web/views/@default/servers/certs/acme/index.html index 02064593..b7f5dabd 100644 --- a/web/views/@default/servers/certs/acme/index.html +++ b/web/views/@default/servers/certs/acme/index.html @@ -3,4 +3,51 @@
{$template "menu"} + +

暂时还没有证书申请任务。

+ + + + + + + + + + + + + + + + + + + + + + + + +
ACME用户域名服务商顶级域名证书域名到期时间自动续期关联证书操作
{{task.acmeUser.email}}{{task.dnsProvider.name}}{{task.dnsDomain}} +
+ {{domain}} +
+
+ + + Y + N + + + - + + 修改   + 执行   + 删除 +
+ +
\ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/index.js b/web/views/@default/servers/certs/acme/index.js new file mode 100644 index 00000000..9c3c0f63 --- /dev/null +++ b/web/views/@default/servers/certs/acme/index.js @@ -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() + }) + } +}) \ No newline at end of file diff --git a/web/views/@default/servers/certs/acme/updateTaskPopup.html b/web/views/@default/servers/certs/acme/updateTaskPopup.html new file mode 100644 index 00000000..75298b9a --- /dev/null +++ b/web/views/@default/servers/certs/acme/updateTaskPopup.html @@ -0,0 +1,46 @@ +{$layout "layout_popup"} + +

修改申请任务

+
+ + + + + + + + + + + + + + + + + + + + + +
选择DNS服务商 * +
+ +
+

用于自动创建域名解析记录。

+
顶级域名 * + +

用于在DNS服务商账号中操作解析记录的域名,比如 example.com,不要输入二级或别的多级域名。

+
证书域名列表 * + +

需要申请的证书中包含的域名列表,所有域名必须是同一个顶级域名。

+
自动续期 + +

在免费证书临近到期之前,是否尝试自动续期。

+
+ + +
\ No newline at end of file