mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-07 23:30:26 +08:00
[HTTPS]可以直接点击按钮申请免费证书
This commit is contained in:
@@ -120,8 +120,8 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
|
|
||||||
// 域名列表
|
// 域名列表
|
||||||
serverNames := []*serverconfigs.ServerNameConfig{}
|
serverNames := []*serverconfigs.ServerNameConfig{}
|
||||||
if len(server.ServerNamesJON) > 0 {
|
if len(server.ServerNamesJSON) > 0 {
|
||||||
err = json.Unmarshal(server.ServerNamesJON, &serverNames)
|
err = json.Unmarshal(server.ServerNamesJSON, &serverNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ func init() {
|
|||||||
server.
|
server.
|
||||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||||
Helper(NewHelper()).
|
Helper(NewHelper()).
|
||||||
Data("teaModule", "server").
|
|
||||||
Prefix("/servers").
|
Prefix("/servers").
|
||||||
Get("", new(IndexAction)).
|
Get("", new(IndexAction)).
|
||||||
GetPost("/create", new(CreateAction)).
|
GetPost("/create", new(CreateAction)).
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func init() {
|
|||||||
Helper(serverutils.NewServerHelper()).
|
Helper(serverutils.NewServerHelper()).
|
||||||
Prefix("/servers/server/settings/https").
|
Prefix("/servers/server/settings/https").
|
||||||
GetPost("", new(IndexAction)).
|
GetPost("", new(IndexAction)).
|
||||||
|
GetPost("/requestCertPopup", new(RequestCertPopupAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package https
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"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/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/lists"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestCertPopupAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RequestCertPopupAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RequestCertPopupAction) RunGet(params struct {
|
||||||
|
ServerId int64
|
||||||
|
ExcludeServerNames string
|
||||||
|
}) {
|
||||||
|
serverNamesResp, err := this.RPC().ServerRPC().FindServerNames(this.AdminContext(), &pb.FindServerNamesRequest{ServerId: params.ServerId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serverNameConfigs := []*serverconfigs.ServerNameConfig{}
|
||||||
|
err = json.Unmarshal(serverNamesResp.ServerNamesJSON, &serverNameConfigs)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeServerNames := []string{}
|
||||||
|
if len(params.ExcludeServerNames) > 0 {
|
||||||
|
excludeServerNames = strings.Split(params.ExcludeServerNames, ",")
|
||||||
|
}
|
||||||
|
serverNames := []string{}
|
||||||
|
for _, c := range serverNameConfigs {
|
||||||
|
if len(c.SubNames) == 0 {
|
||||||
|
if domainutils.ValidateDomainFormat(c.Name) && !lists.ContainsString(excludeServerNames, c.Name) {
|
||||||
|
serverNames = append(serverNames, c.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, subName := range c.SubNames {
|
||||||
|
if domainutils.ValidateDomainFormat(subName) && !lists.ContainsString(excludeServerNames, subName) {
|
||||||
|
serverNames = append(serverNames, subName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["serverNames"] = serverNames
|
||||||
|
|
||||||
|
// 用户
|
||||||
|
acmeUsersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
|
||||||
|
AdminId: this.AdminId(),
|
||||||
|
UserId: 0,
|
||||||
|
})
|
||||||
|
userMaps := []maps.Map{}
|
||||||
|
for _, user := range acmeUsersResp.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
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RequestCertPopupAction) RunPost(params struct {
|
||||||
|
ServerNames []string
|
||||||
|
|
||||||
|
UserId int64
|
||||||
|
UserEmail string
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
CSRF *actionutils.CSRF
|
||||||
|
}) {
|
||||||
|
// 检查域名
|
||||||
|
if len(params.ServerNames) == 0 {
|
||||||
|
this.Fail("必须包含至少一个或多个域名")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册用户
|
||||||
|
var acmeUserId int64
|
||||||
|
if params.UserId > 0 {
|
||||||
|
// TODO 检查当前管理员是否可以使用此用户
|
||||||
|
acmeUserId = params.UserId
|
||||||
|
} else if len(params.UserEmail) > 0 {
|
||||||
|
params.Must.
|
||||||
|
Field("userEmail", params.UserEmail).
|
||||||
|
Email("Email格式错误")
|
||||||
|
|
||||||
|
createUserResp, err := this.RPC().ACMEUserRPC().CreateACMEUser(this.AdminContext(), &pb.CreateACMEUserRequest{
|
||||||
|
Email: params.UserEmail,
|
||||||
|
Description: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer this.CreateLogInfo("创建ACME用户", createUserResp.AcmeUserId)
|
||||||
|
acmeUserId = createUserResp.AcmeUserId
|
||||||
|
|
||||||
|
this.Data["acmeUser"] = maps.Map{
|
||||||
|
"id": acmeUserId,
|
||||||
|
"email": params.UserEmail,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.Fail("请选择或者填写用户")
|
||||||
|
}
|
||||||
|
|
||||||
|
createTaskResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
|
||||||
|
AcmeUserId: acmeUserId,
|
||||||
|
DnsProviderId: 0,
|
||||||
|
DnsDomain: "",
|
||||||
|
Domains: params.ServerNames,
|
||||||
|
AutoRenew: true,
|
||||||
|
AuthType: "http",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
taskId := createTaskResp.AcmeTaskId
|
||||||
|
|
||||||
|
defer this.CreateLogInfo("自动申请证书,任务 %d", taskId)
|
||||||
|
|
||||||
|
runResp, err := this.RPC().ACMETaskRPC().RunACMETask(this.AdminContext(), &pb.RunACMETaskRequest{AcmeTaskId: taskId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if runResp.IsOk {
|
||||||
|
certId := runResp.SslCertId
|
||||||
|
|
||||||
|
configResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: certId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certConfig := &sslconfigs.SSLCertConfig{}
|
||||||
|
err = json.Unmarshal(configResp.CertJSON, certConfig)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certConfig.CertData = nil // 去掉不必要的数据
|
||||||
|
certConfig.KeyData = nil // 去掉不必要的数据
|
||||||
|
this.Data["cert"] = certConfig
|
||||||
|
this.Data["certRef"] = &sslconfigs.SSLCertRef{
|
||||||
|
IsOn: true,
|
||||||
|
CertId: certId,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
} else {
|
||||||
|
this.Fail(runResp.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,8 +28,8 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverNamesConfig := []*serverconfigs.ServerNameConfig{}
|
serverNamesConfig := []*serverconfigs.ServerNameConfig{}
|
||||||
if len(server.ServerNamesJON) > 0 {
|
if len(server.ServerNamesJSON) > 0 {
|
||||||
err := json.Unmarshal(server.ServerNamesJON, &serverNamesConfig)
|
err := json.Unmarshal(server.ServerNamesJSON, &serverNamesConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.ErrorPage(err)
|
this.ErrorPage(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
Vue.component("ssl-certs-box", {
|
Vue.component("ssl-certs-box", {
|
||||||
props: ["v-certs", "v-protocol", "v-view-size", "v-single-mode"],
|
props: [
|
||||||
|
"v-certs", // 证书列表
|
||||||
|
"v-protocol", // 协议:https|tls
|
||||||
|
"v-view-size", // 弹窗尺寸
|
||||||
|
"v-single-mode" // 单证书模式
|
||||||
|
],
|
||||||
data: function () {
|
data: function () {
|
||||||
let certs = this.vCerts
|
let certs = this.vCerts
|
||||||
if (certs == null) {
|
if (certs == null) {
|
||||||
certs = []
|
certs = []
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certs: certs
|
certs: certs
|
||||||
}
|
}
|
||||||
@@ -82,7 +88,7 @@ Vue.component("ssl-certs-box", {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="buttonsVisible()">
|
<div v-if="buttonsVisible()">
|
||||||
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
|
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
|
||||||
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button>
|
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Vue.component("ssl-config-box", {
|
Vue.component("ssl-config-box", {
|
||||||
props: ["v-ssl-policy", "v-protocol"],
|
props: ["v-ssl-policy", "v-protocol", "v-server-id"],
|
||||||
created: function () {
|
created: function () {
|
||||||
let that = this
|
let that = this
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -119,6 +119,25 @@ Vue.component("ssl-config-box", {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 申请证书
|
||||||
|
requestCert: function () {
|
||||||
|
// 已经在证书中的域名
|
||||||
|
let excludeServerNames = []
|
||||||
|
if (this.policy != null && this.policy.certs.length > 0) {
|
||||||
|
this.policy.certs.forEach(function (cert) {
|
||||||
|
excludeServerNames.$pushAll(cert.dnsNames)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/server/settings/https/requestCertPopup?serverId=" + this.vServerId + "&excludeServerNames=" + excludeServerNames.join(","), {
|
||||||
|
callback: function () {
|
||||||
|
that.policy.certRefs.push(resp.data.certRef)
|
||||||
|
that.policy.certs.push(resp.data.cert)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 更多选项
|
// 更多选项
|
||||||
changeOptionsVisible: function () {
|
changeOptionsVisible: function () {
|
||||||
this.moreOptionsVisible = !this.moreOptionsVisible
|
this.moreOptionsVisible = !this.moreOptionsVisible
|
||||||
@@ -338,7 +357,8 @@ Vue.component("ssl-config-box", {
|
|||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
|
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
|
||||||
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button>
|
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button>
|
||||||
|
<button class="ui button tiny" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- SSL配置 -->
|
<!-- SSL配置 -->
|
||||||
<ssl-config-box :v-ssl-policy="httpsConfig.sslPolicy" :v-protocol="'https'" v-show="httpsConfig.isOn"></ssl-config-box>
|
<ssl-config-box :v-ssl-policy="httpsConfig.sslPolicy" :v-protocol="'https'" v-show="httpsConfig.isOn" :v-server-id="serverId"></ssl-config-box>
|
||||||
|
|
||||||
<submit-btn></submit-btn>
|
<submit-btn></submit-btn>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{$layout "layout_popup"}
|
||||||
|
|
||||||
|
<h3>申请免费证书</h3>
|
||||||
|
<form method="post" class="ui form" data-tea-success="success" data-tea-action="$" data-tea-timeout="300" data-tea-before="beforeSubmit" data-tea-fail="fail">
|
||||||
|
<csrf-token></csrf-token>
|
||||||
|
|
||||||
|
<table class="ui table definition selectable">
|
||||||
|
<tr>
|
||||||
|
<td class="title">证书包含的域名 *</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="serverNames.length == 0" class="disabled">还没有设置域名,暂时不能申请。</span>
|
||||||
|
<div v-if="serverNames.length > 0">
|
||||||
|
<div v-for="(serverName, index) in serverNames" class="ui tiny basic label">
|
||||||
|
<input type="hidden" name="serverNames" :value="serverName"/>
|
||||||
|
{{serverName}}
|
||||||
|
<a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>证书用户 *</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="users.length > 0">
|
||||||
|
<select class="ui dropdown auto-width" name="userId" v-model="userId">
|
||||||
|
<option value="0">[请选择]</option>
|
||||||
|
<option v-for="user in users" :value="user.id">{{user.email}}{{user.description}}</option>>
|
||||||
|
</select>
|
||||||
|
<p class="comment">用来申请证书的用户。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="users.length == 0">
|
||||||
|
<input type="text" name="userEmail" maxlength="100" placeholder="用户E-mail" ref="focus"/>
|
||||||
|
<p class="comment">用来申请证书的用户邮箱,可以任意填写,只要格式正确即可。</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<submit-btn v-if="!isRequesting">提交</submit-btn>
|
||||||
|
<button class="ui button" type="button" v-if="isRequesting">处理中...</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.isRequesting = false
|
||||||
|
this.userId = 0
|
||||||
|
|
||||||
|
this.remove = function (index) {
|
||||||
|
this.serverNames.$remove(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beforeSubmit = function () {
|
||||||
|
this.isRequesting = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fail = function (resp) {
|
||||||
|
this.isRequesting = false
|
||||||
|
teaweb.warn(resp.message)
|
||||||
|
if (resp.data.acmeUser != null) {
|
||||||
|
this.users.push({
|
||||||
|
id: resp.data.acmeUser.id,
|
||||||
|
email: resp.data.acmeUser.email,
|
||||||
|
description: ""
|
||||||
|
})
|
||||||
|
this.userId = resp.data.acmeUser.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user