mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-08 16:00:26 +08:00
实现HTTPS配置
This commit is contained in:
@@ -130,6 +130,10 @@ func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
|
|||||||
return pb.NewSSLCertServiceClient(this.pickConn())
|
return pb.NewSSLCertServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) SSLPolicyRPC() pb.SSLPolicyServiceClient {
|
||||||
|
return pb.NewSSLPolicyServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
// 构造上下文
|
// 构造上下文
|
||||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package ssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 所有相关数据
|
||||||
|
type DatajsAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DatajsAction) Init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DatajsAction) RunGet(params struct{}) {
|
||||||
|
this.AddHeader("Content-Type", "text/javascript; charset=utf-8")
|
||||||
|
|
||||||
|
{
|
||||||
|
cipherSuitesJSON, err := json.Marshal(sslconfigs.AllTLSCipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.WriteString("window.SSL_ALL_CIPHER_SUITES = " + string(cipherSuitesJSON) + ";\n")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
modernCipherSuitesJSON, err := json.Marshal(sslconfigs.TLSModernCipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.WriteString("window.SSL_MODERN_CIPHER_SUITES = " + string(modernCipherSuitesJSON) + ";\n")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
intermediateCipherSuitesJSON, err := json.Marshal(sslconfigs.TLSIntermediateCipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.WriteString("window.SSL_INTERMEDIATE_CIPHER_SUITES = " + string(intermediateCipherSuitesJSON) + ";\n")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sslVersionsJSON, err := json.Marshal(sslconfigs.AllTlsVersions)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.WriteString("window.SSL_ALL_VERSIONS = " + string(sslVersionsJSON) + ";\n")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
clientAuthTypesJSON, err := json.Marshal(sslconfigs.AllSSLClientAuthTypes())
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.WriteString("window.SSL_ALL_CLIENT_AUTH_TYPES = " + string(clientAuthTypesJSON) + ";\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ func init() {
|
|||||||
Get("/downloadKey", new(DownloadKeyAction)).
|
Get("/downloadKey", new(DownloadKeyAction)).
|
||||||
Get("/downloadCert", new(DownloadCertAction)).
|
Get("/downloadCert", new(DownloadCertAction)).
|
||||||
Get("/downloadZip", new(DownloadZipAction)).
|
Get("/downloadZip", new(DownloadZipAction)).
|
||||||
|
Get("/selectPopup", new(SelectPopupAction)).
|
||||||
|
Get("/datajs", new(DatajsAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package ssl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 选择证书
|
||||||
|
type SelectPopupAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectPopupAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SelectPopupAction) RunGet(params struct{}) {
|
||||||
|
// TODO 支持关键词搜索
|
||||||
|
// TODO 列出常用的证书供用户选择
|
||||||
|
|
||||||
|
countResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := this.NewPage(countResp.Count)
|
||||||
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
|
listResp, err := this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
})
|
||||||
|
|
||||||
|
certConfigs := []*sslconfigs.SSLCertConfig{}
|
||||||
|
err = json.Unmarshal(listResp.CertsJSON, &certConfigs)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["certs"] = certConfigs
|
||||||
|
|
||||||
|
certMaps := []maps.Map{}
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
for _, certConfig := range certConfigs {
|
||||||
|
countServersResp, err := this.RPC().ServerRPC().CountServersWithSSLCertId(this.AdminContext(), &pb.CountServersWithSSLCertIdRequest{CertId: certConfig.Id})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
certMaps = append(certMaps, maps.Map{
|
||||||
|
"beginDay": timeutil.FormatTime("Y-m-d", certConfig.TimeBeginAt),
|
||||||
|
"endDay": timeutil.FormatTime("Y-m-d", certConfig.TimeEndAt),
|
||||||
|
"isExpired": nowTime > certConfig.TimeEndAt,
|
||||||
|
"isAvailable": nowTime <= certConfig.TimeEndAt,
|
||||||
|
"countServers": countServersResp.Count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["certInfos"] = certMaps
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ssl
|
package ssl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"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/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
@@ -73,7 +74,7 @@ func (this *UploadPopupAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
_, err = this.RPC().SSLCertRPC().CreateSSLCert(this.AdminContext(), &pb.CreateSSLCertRequest{
|
createResp, err := this.RPC().SSLCertRPC().CreateSSLCert(this.AdminContext(), &pb.CreateSSLCertRequest{
|
||||||
IsOn: params.IsOn,
|
IsOn: params.IsOn,
|
||||||
Name: params.Name,
|
Name: params.Name,
|
||||||
Description: params.Description,
|
Description: params.Description,
|
||||||
@@ -91,5 +92,26 @@ func (this *UploadPopupAction) RunPost(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询已创建的证书并返回,方便调用者进行后续处理
|
||||||
|
certId := createResp.CertId
|
||||||
|
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()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package https
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -37,10 +40,29 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
httpsConfig.IsOn = true
|
httpsConfig.IsOn = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sslPolicy *sslconfigs.SSLPolicy
|
||||||
|
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
|
||||||
|
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sslPolicyConfigJSON := sslPolicyConfigResp.SslPolicyJSON
|
||||||
|
if len(sslPolicyConfigJSON) > 0 {
|
||||||
|
sslPolicy = &sslconfigs.SSLPolicy{}
|
||||||
|
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["serverType"] = server.Type
|
this.Data["serverType"] = server.Type
|
||||||
this.Data["httpsConfig"] = maps.Map{
|
this.Data["httpsConfig"] = maps.Map{
|
||||||
"isOn": httpsConfig.IsOn,
|
"isOn": httpsConfig.IsOn,
|
||||||
"addresses": httpsConfig.Listen,
|
"addresses": httpsConfig.Listen,
|
||||||
|
"sslPolicy": sslPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
@@ -51,6 +73,8 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
IsOn bool
|
IsOn bool
|
||||||
Addresses string
|
Addresses string
|
||||||
|
|
||||||
|
SslPolicyJSON []byte
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
addresses := []*serverconfigs.NetworkAddressConfig{}
|
addresses := []*serverconfigs.NetworkAddressConfig{}
|
||||||
@@ -59,6 +83,73 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
this.Fail("端口地址解析失败:" + err.Error())
|
this.Fail("端口地址解析失败:" + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 校验addresses
|
||||||
|
|
||||||
|
// 校验SSL
|
||||||
|
var sslPolicyId = int64(0)
|
||||||
|
if params.SslPolicyJSON != nil {
|
||||||
|
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||||
|
err = json.Unmarshal(params.SslPolicyJSON, sslPolicy)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(errors.New("解析SSL配置时发生了错误:" + err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sslPolicyId = sslPolicy.Id
|
||||||
|
|
||||||
|
certsJSON, err := json.Marshal(sslPolicy.CertRefs)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hstsJSON, err := json.Marshal(sslPolicy.HSTS)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCACertsJSON, err := json.Marshal(sslPolicy.ClientCARefs)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslPolicyId > 0 {
|
||||||
|
_, err := this.RPC().SSLPolicyRPC().UpdateSSLPolicy(this.AdminContext(), &pb.UpdateSSLPolicyRequest{
|
||||||
|
SslPolicyId: sslPolicyId,
|
||||||
|
Http2Enabled: sslPolicy.HTTP2Enabled,
|
||||||
|
MinVersion: sslPolicy.MinVersion,
|
||||||
|
CertsJSON: certsJSON,
|
||||||
|
HstsJSON: hstsJSON,
|
||||||
|
ClientAuthType: types.Int32(sslPolicy.ClientAuthType),
|
||||||
|
ClientCACertsJSON: clientCACertsJSON,
|
||||||
|
CipherSuitesIsOn: sslPolicy.CipherSuitesIsOn,
|
||||||
|
CipherSuites: sslPolicy.CipherSuites,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
|
||||||
|
Http2Enabled: sslPolicy.HTTP2Enabled,
|
||||||
|
MinVersion: sslPolicy.MinVersion,
|
||||||
|
CertsJSON: certsJSON,
|
||||||
|
HstsJSON: hstsJSON,
|
||||||
|
ClientAuthType: types.Int32(sslPolicy.ClientAuthType),
|
||||||
|
ClientCACertsJSON: clientCACertsJSON,
|
||||||
|
CipherSuitesIsOn: sslPolicy.CipherSuitesIsOn,
|
||||||
|
CipherSuites: sslPolicy.CipherSuites,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sslPolicyId = resp.SslPolicyId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
server, _, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
|
server, _, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
|
||||||
if !isOk {
|
if !isOk {
|
||||||
return
|
return
|
||||||
@@ -72,6 +163,10 @@ func (this *IndexAction) RunPost(params struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||||
|
IsOn: true,
|
||||||
|
SSLPolicyId: sslPolicyId,
|
||||||
|
}
|
||||||
httpsConfig.IsOn = params.IsOn
|
httpsConfig.IsOn = params.IsOn
|
||||||
httpsConfig.Listen = addresses
|
httpsConfig.Listen = addresses
|
||||||
configData, err := json.Marshal(httpsConfig)
|
configData, err := json.Marshal(httpsConfig)
|
||||||
|
|||||||
483
web/public/js/components/server/ssl-config-box.js
Normal file
483
web/public/js/components/server/ssl-config-box.js
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
Vue.component("ssl-config-box", {
|
||||||
|
props: ["v-ssl-policy", "v-protocol"],
|
||||||
|
created: function () {
|
||||||
|
let that = this
|
||||||
|
setTimeout(function () {
|
||||||
|
that.sortableCipherSuites()
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
let policy = this.vSslPolicy
|
||||||
|
if (policy == null) {
|
||||||
|
policy = {
|
||||||
|
id: 0,
|
||||||
|
isOn: true,
|
||||||
|
certRefs: [],
|
||||||
|
certs: [],
|
||||||
|
clientCARefs: [],
|
||||||
|
clientCACerts: [],
|
||||||
|
clientAuthType: 0,
|
||||||
|
minVersion: "TLS 1.1",
|
||||||
|
hsts: null,
|
||||||
|
cipherSuitesIsOn: false,
|
||||||
|
cipherSuites: [],
|
||||||
|
http2Enabled: true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (policy.certRefs == null) {
|
||||||
|
policy.certRefs = []
|
||||||
|
}
|
||||||
|
if (policy.certs == null) {
|
||||||
|
policy.certs = []
|
||||||
|
}
|
||||||
|
if (policy.clientCARefs == null) {
|
||||||
|
policy.clientCARefs = []
|
||||||
|
}
|
||||||
|
if (policy.clientCACerts == null) {
|
||||||
|
policy.clientCACerts = []
|
||||||
|
}
|
||||||
|
if (policy.cipherSuites == null) {
|
||||||
|
policy.cipherSuites = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hsts = policy.hsts
|
||||||
|
if (hsts == null) {
|
||||||
|
hsts = {
|
||||||
|
isOn: false,
|
||||||
|
maxAge: 0,
|
||||||
|
includeSubDomains: false,
|
||||||
|
preload: false,
|
||||||
|
domains: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
policy: policy,
|
||||||
|
|
||||||
|
// hsts
|
||||||
|
hsts: hsts,
|
||||||
|
hstsOptionsVisible: false,
|
||||||
|
hstsDomainAdding: false,
|
||||||
|
addingHstsDomain: "",
|
||||||
|
hstsDomainEditingIndex: -1,
|
||||||
|
|
||||||
|
// 相关数据
|
||||||
|
allVersions: window.SSL_ALL_VERSIONS,
|
||||||
|
allCipherSuites: window.SSL_ALL_CIPHER_SUITES.$copy(),
|
||||||
|
modernCipherSuites: window.SSL_MODERN_CIPHER_SUITES,
|
||||||
|
intermediateCipherSuites: window.SSL_INTERMEDIATE_CIPHER_SUITES,
|
||||||
|
allClientAuthTypes: window.SSL_ALL_CLIENT_AUTH_TYPES,
|
||||||
|
cipherSuitesVisible: false,
|
||||||
|
|
||||||
|
// 高级选项
|
||||||
|
moreOptionsVisible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
hsts: {
|
||||||
|
deep: true,
|
||||||
|
handler: function () {
|
||||||
|
this.policy.hsts = this.hsts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 删除证书
|
||||||
|
removeCert: function (index) {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () {
|
||||||
|
that.policy.certRefs.$remove(index)
|
||||||
|
that.policy.certs.$remove(index)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 选择证书
|
||||||
|
selectCert: function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/components/ssl/selectPopup", {
|
||||||
|
width: "50em",
|
||||||
|
height: "30em",
|
||||||
|
callback: function (resp) {
|
||||||
|
that.policy.certRefs.push(resp.data.certRef)
|
||||||
|
that.policy.certs.push(resp.data.cert)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传证书
|
||||||
|
uploadCert: function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/components/ssl/uploadPopup", {
|
||||||
|
height: "28em",
|
||||||
|
callback: function (resp) {
|
||||||
|
teaweb.success("上传成功", function () {
|
||||||
|
that.policy.certRefs.push(resp.data.certRef)
|
||||||
|
that.policy.certs.push(resp.data.cert)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更多选项
|
||||||
|
changeOptionsVisible: function () {
|
||||||
|
this.moreOptionsVisible = !this.moreOptionsVisible
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
formatTime: function (timestamp) {
|
||||||
|
return new Date(timestamp * 1000).format("Y-m-d")
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化加密套件
|
||||||
|
formatCipherSuite: function (cipherSuite) {
|
||||||
|
return cipherSuite.replace(/(AES|3DES)/, "<var style=\"font-weight: bold\">$1</var>")
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加单个套件
|
||||||
|
addCipherSuite: function (cipherSuite) {
|
||||||
|
if (!this.policy.cipherSuites.$contains(cipherSuite)) {
|
||||||
|
this.policy.cipherSuites.push(cipherSuite)
|
||||||
|
}
|
||||||
|
this.allCipherSuites.$removeValue(cipherSuite)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除单个套件
|
||||||
|
removeCipherSuite: function (cipherSuite) {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定要删除此套件吗?", function () {
|
||||||
|
that.policy.cipherSuites.$removeValue(cipherSuite)
|
||||||
|
that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$findAll(function (k, v) {
|
||||||
|
return !that.policy.cipherSuites.$contains(v)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清除所选套件
|
||||||
|
clearCipherSuites: function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定要清除所有已选套件吗?", function () {
|
||||||
|
that.policy.cipherSuites = []
|
||||||
|
that.allCipherSuites = window.SSL_ALL_CIPHER_SUITES.$copy()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 批量添加套件
|
||||||
|
addBatchCipherSuites: function (suites) {
|
||||||
|
var that = this
|
||||||
|
teaweb.confirm("确定要批量添加套件?", function () {
|
||||||
|
suites.$each(function (k, v) {
|
||||||
|
if (that.policy.cipherSuites.$contains(v)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
that.policy.cipherSuites.push(v)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 套件拖动排序
|
||||||
|
*/
|
||||||
|
sortableCipherSuites: function () {
|
||||||
|
var box = document.querySelector(".cipher-suites-box")
|
||||||
|
Sortable.create(box, {
|
||||||
|
draggable: ".label",
|
||||||
|
handle: ".icon.handle",
|
||||||
|
onStart: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
onUpdate: function (event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示所有套件
|
||||||
|
showAllCipherSuites: function () {
|
||||||
|
this.cipherSuitesVisible = !this.cipherSuitesVisible
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示HSTS更多选项
|
||||||
|
showMoreHSTS: function () {
|
||||||
|
this.hstsOptionsVisible = !this.hstsOptionsVisible;
|
||||||
|
if (this.hstsOptionsVisible) {
|
||||||
|
this.changeHSTSMaxAge()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 监控HSTS有效期修改
|
||||||
|
changeHSTSMaxAge: function () {
|
||||||
|
var v = this.hsts.maxAge
|
||||||
|
if (isNaN(v)) {
|
||||||
|
this.hsts.days = "-"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.hsts.days = parseInt(v / 86400)
|
||||||
|
if (isNaN(this.hsts.days)) {
|
||||||
|
this.hsts.days = "-"
|
||||||
|
} else if (this.hsts.days < 0) {
|
||||||
|
this.hsts.days = "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置HSTS有效期
|
||||||
|
setHSTSMaxAge: function (maxAge) {
|
||||||
|
this.hsts.maxAge = maxAge
|
||||||
|
this.changeHSTSMaxAge()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加HSTS域名
|
||||||
|
addHstsDomain: function () {
|
||||||
|
this.hstsDomainAdding = true
|
||||||
|
this.hstsDomainEditingIndex = -1
|
||||||
|
let that = this
|
||||||
|
setTimeout(function () {
|
||||||
|
that.$refs.addingHstsDomain.focus()
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改HSTS域名
|
||||||
|
editHstsDomain: function (index) {
|
||||||
|
this.hstsDomainEditingIndex = index
|
||||||
|
this.addingHstsDomain = this.hsts.domains[index]
|
||||||
|
this.hstsDomainAdding = true
|
||||||
|
let that = this
|
||||||
|
setTimeout(function () {
|
||||||
|
that.$refs.addingHstsDomain.focus()
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 确认HSTS域名添加
|
||||||
|
confirmAddHstsDomain: function () {
|
||||||
|
this.addingHstsDomain = this.addingHstsDomain.trim()
|
||||||
|
if (this.addingHstsDomain.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.hstsDomainEditingIndex > -1) {
|
||||||
|
this.hsts.domains[this.hstsDomainEditingIndex] = this.addingHstsDomain
|
||||||
|
} else {
|
||||||
|
this.hsts.domains.push(this.addingHstsDomain)
|
||||||
|
}
|
||||||
|
this.cancelHstsDomainAdding()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 取消HSTS域名添加
|
||||||
|
cancelHstsDomainAdding: function () {
|
||||||
|
this.hstsDomainAdding = false
|
||||||
|
this.addingHstsDomain = ""
|
||||||
|
this.hstsDomainEditingIndex = -1
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除HSTS域名
|
||||||
|
removeHstsDomain: function (index) {
|
||||||
|
this.cancelHstsDomainAdding()
|
||||||
|
this.hsts.domains.$remove(index)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 选择客户端CA证书
|
||||||
|
selectClientCACert: function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/components/ssl/selectPopup?isCA=1", {
|
||||||
|
width: "50em",
|
||||||
|
height: "30em",
|
||||||
|
callback: function (resp) {
|
||||||
|
that.policy.clientCARefs.push(resp.data.certRef)
|
||||||
|
that.policy.clientCACerts.push(resp.data.cert)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传CA证书
|
||||||
|
uploadClientCACert: function () {
|
||||||
|
let that = this
|
||||||
|
teaweb.popup("/servers/components/ssl/uploadPopup?isCA=1", {
|
||||||
|
height: "28em",
|
||||||
|
callback: function (resp) {
|
||||||
|
teaweb.success("上传成功", function () {
|
||||||
|
that.policy.clientCARefs.push(resp.data.certRef)
|
||||||
|
that.policy.clientCACerts.push(resp.data.cert)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除客户端CA证书
|
||||||
|
removeClientCACert: function (index) {
|
||||||
|
let that = this
|
||||||
|
teaweb.confirm("确定删除此证书吗?证书数据仍然保留,只是当前服务不再使用此证书。", function () {
|
||||||
|
that.policy.clientCARefs.$remove(index)
|
||||||
|
that.policy.clientCACerts.$remove(index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `<div>
|
||||||
|
<h4>SSL/TLS相关配置</h4>
|
||||||
|
<input type="hidden" name="sslPolicyJSON" :value="JSON.stringify(policy)"/>
|
||||||
|
<table class="ui table definition selectable">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="title">用HTTP/2</td>
|
||||||
|
<td>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" value="1" v-model="policy.http2Enabled"/>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>选择证书</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="policy.certs != null && policy.certs.length > 0">
|
||||||
|
<div class="ui label small" v-for="(cert, index) in policy.certs">
|
||||||
|
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} <a href="" title="删除" @click.prevent="removeCert()"><i class="icon remove"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button>
|
||||||
|
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>TLS最低版本</td>
|
||||||
|
<td>
|
||||||
|
<select v-model="policy.minVersion" class="ui dropdown auto-width">
|
||||||
|
<option v-for="version in allVersions" :value="version">{{version}}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<more-options-tbody @change="changeOptionsVisible"></more-options-tbody>
|
||||||
|
<tbody v-show="moreOptionsVisible">
|
||||||
|
<!-- 加密套件 -->
|
||||||
|
<tr>
|
||||||
|
<td>加密算法套件<em>(CipherSuites)</em></td>
|
||||||
|
<td>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" value="1" v-model="policy.cipherSuitesIsOn" />
|
||||||
|
<label>是否要自定义</label>
|
||||||
|
</div>
|
||||||
|
<div v-show="policy.cipherSuitesIsOn">
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="cipher-suites-box">
|
||||||
|
已添加套件({{policy.cipherSuites.length}}):
|
||||||
|
<div v-for="cipherSuite in policy.cipherSuites" class="ui label tiny" style="margin-bottom: 0.5em">
|
||||||
|
<input type="hidden" name="cipherSuites" :value="cipherSuite"/>
|
||||||
|
<span v-html="formatCipherSuite(cipherSuite)"></span> <a href="" title="删除套件" @click.prevent="removeCipherSuite(cipherSuite)"><i class="icon remove"></i></a>
|
||||||
|
<a href="" title="拖动改变顺序"><i class="icon bars handle"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<span v-if="policy.cipherSuites.length > 0"><a href="" @click.prevent="clearCipherSuites()">[清除所有已选套件]</a> </span>
|
||||||
|
<a href="" @click.prevent="addBatchCipherSuites(modernCipherSuites)">[添加推荐套件]</a>
|
||||||
|
<a href="" @click.prevent="addBatchCipherSuites(intermediateCipherSuites)">[添加兼容套件]</a>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cipher-all-suites-box">
|
||||||
|
<a href="" @click.prevent="showAllCipherSuites()"><span v-if="policy.cipherSuites.length == 0">所有</span>可选套件({{allCipherSuites.length}}) <i class="icon angle" :class="{down:!cipherSuitesVisible, up:cipherSuitesVisible}"></i></a>
|
||||||
|
<a href="" v-if="cipherSuitesVisible" v-for="cipherSuite in allCipherSuites" class="ui label tiny" title="点击添加到自定义套件中" @click.prevent="addCipherSuite(cipherSuite)" v-html="formatCipherSuite(cipherSuite)" style="margin-bottom:0.5em"></a>
|
||||||
|
</div>
|
||||||
|
<p class="comment" v-if="cipherSuitesVisible">点击可选套件添加。</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- HSTS -->
|
||||||
|
<tr v-show="vProtocol == 'https'">
|
||||||
|
<td :class="{'color-border':hsts.isOn}">是否开启HSTS</td>
|
||||||
|
<td>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="hstsOn" v-model="hsts.isOn" value="1"/>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
<p class="comment">
|
||||||
|
开启后,会自动在响应Header中加入
|
||||||
|
<span class="ui label small">Strict-Transport-Security:
|
||||||
|
<var v-if="!hsts.isOn">...</var>
|
||||||
|
<var v-if="hsts.isOn"><span>max-age=</span>{{hsts.maxAge}}</var>
|
||||||
|
<var v-if="hsts.isOn && hsts.includeSubDomains">; includeSubDomains</var>
|
||||||
|
<var v-if="hsts.isOn && hsts.preload">; preload</var>
|
||||||
|
</span>
|
||||||
|
<span v-if="hsts.isOn">
|
||||||
|
<a href="" @click.prevent="showMoreHSTS()">修改<i class="icon angle" :class="{down:!hstsOptionsVisible, up:hstsOptionsVisible}"></i> </a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-show="hsts.isOn && hstsOptionsVisible">
|
||||||
|
<td class="color-border">HSTS包含子域名<em>(includeSubDomains)</em></td>
|
||||||
|
<td>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="hstsIncludeSubDomains" value="1" v-model="hsts.includeSubDomains"/>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-show="hsts.isOn && hstsOptionsVisible">
|
||||||
|
<td class="color-border">HSTS预加载<em>(preload)</em></td>
|
||||||
|
<td>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="hstsPreload" value="1" v-model="hsts.preload"/>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-show="hsts.isOn && hstsOptionsVisible">
|
||||||
|
<td class="color-border">HSTS生效的域名</td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div class="names-box">
|
||||||
|
<span class="ui label tiny" v-for="(domain, arrayIndex) in hsts.domains" :class="{blue:hstsDomainEditingIndex == arrayIndex}">{{domain}}
|
||||||
|
<input type="hidden" name="hstsDomains" :value="domain"/>
|
||||||
|
<a href="" @click.prevent="editHstsDomain(arrayIndex)" title="修改"><i class="icon pencil"></i></a>
|
||||||
|
<a href="" @click.prevent="removeHstsDomain(arrayIndex)" title="删除"><i class="icon remove"></i></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="ui fields inline" v-if="hstsDomainAdding" style="margin-top:0.8em">
|
||||||
|
<div class="ui field">
|
||||||
|
<input type="text" name="addingHstsDomain" ref="addingHstsDomain" style="width:16em" maxlength="100" placeholder="域名,比如example.com" @keyup.enter="confirmAddHstsDomain()" @keypress.enter.prevent="1" v-model="addingHstsDomain" />
|
||||||
|
</div>
|
||||||
|
<div class="ui field">
|
||||||
|
<button class="ui button tiny" type="button" @click="confirmAddHstsDomain()">确定</button>
|
||||||
|
<a href="" @click.prevent="cancelHstsDomainAdding()">取消</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui field" style="margin-top: 1em">
|
||||||
|
<button class="ui button tiny" type="button" @click="addHstsDomain()">+</button>
|
||||||
|
</div>
|
||||||
|
<p class="comment">如果没有设置域名的话,则默认支持所有的域名。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 客户端认证 -->
|
||||||
|
<tr>
|
||||||
|
<td>客户端认证方式</td>
|
||||||
|
<td>
|
||||||
|
<select name="clientAuthType" v-model="policy.clientAuthType" class="ui dropdown auto-width">
|
||||||
|
<option v-for="authType in allClientAuthTypes" :value="authType.type">{{authType.name}}</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>客户端认证CA证书</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="policy.clientCACerts != null && policy.clientCACerts.length > 0">
|
||||||
|
<div class="ui label small" v-for="(cert, index) in policy.clientCACerts">
|
||||||
|
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} <a href="" title="删除" @click.prevent="removeClientCACert()"><i class="icon remove"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
</div>
|
||||||
|
<button class="ui button tiny" type="button" @click.prevent="selectClientCACert()">选择已有证书</button>
|
||||||
|
<button class="ui button tiny" type="button" @click.prevent="uploadClientCACert()">上传新证书</button>
|
||||||
|
<p class="comment">用来校验客户端证书以增强安全性,通常不需要设置。</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="ui margin"></div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
322
web/public/js/date.tea.js
Normal file
322
web/public/js/date.tea.js
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
/**
|
||||||
|
* Tea.Date 对象
|
||||||
|
*
|
||||||
|
* @class Tea.Date
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tea.Date构造器。使用方法如:<br/>
|
||||||
|
* var date = new Tea.Date();<br/>
|
||||||
|
* var date = new Tea.Date("Y-m-d H:i:s");<br/>
|
||||||
|
* var date = new Tea.Date("Y-m-d H:i:s", 1169226085);
|
||||||
|
*
|
||||||
|
* @constructor Tea.Date
|
||||||
|
* @param String format 时间格式,为可选参数,目前支持O,r,Y,y,L,M,m,n,F,t,w,D,l,d,z,H,i,s,j,h,G,g,a,A等字符。
|
||||||
|
* @param int time 时间戳,为可选参数
|
||||||
|
*/
|
||||||
|
Tea.Date = function (format, time) {
|
||||||
|
var date = new Date();
|
||||||
|
|
||||||
|
if (typeof(format) == "undefined") {
|
||||||
|
format = "r";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(time) != "undefined") {
|
||||||
|
time = parseInt(time, 10);
|
||||||
|
date.setTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
//parse char
|
||||||
|
this.get = function (chr) {
|
||||||
|
if ((chr >= "a" && chr <= "z") || (chr >= "A" && chr <= "Z")) {
|
||||||
|
var func = "_parse_" + chr;
|
||||||
|
if (this[func]) {
|
||||||
|
return this[func]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据提供的格式取得对应的时间格式
|
||||||
|
*
|
||||||
|
* @method parse
|
||||||
|
* @param String format
|
||||||
|
*/
|
||||||
|
this.parse = function (format) {
|
||||||
|
var result = "";
|
||||||
|
if (format.length > 0) {
|
||||||
|
for (var i=0; i<format.length; i++) {
|
||||||
|
var chr = format.charAt(i);
|
||||||
|
result += this.get(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置某一时间为某值
|
||||||
|
*
|
||||||
|
* @method set
|
||||||
|
* @param String type 时间选项,如 d 表示天,Y 表示年,H 表示小时,等等。
|
||||||
|
* @param int value 新的值
|
||||||
|
*/
|
||||||
|
this.set = function (type, value) {
|
||||||
|
value = parseInt(value, 10);
|
||||||
|
switch (type) {
|
||||||
|
case "d":
|
||||||
|
date.setDate(value);
|
||||||
|
break;
|
||||||
|
case "Y":
|
||||||
|
date.setFullYear(value);
|
||||||
|
break;
|
||||||
|
case "H":
|
||||||
|
case "G":
|
||||||
|
date.setHours(value);
|
||||||
|
break;
|
||||||
|
case "i":
|
||||||
|
date.setMinutes(value);
|
||||||
|
break;
|
||||||
|
case "s":
|
||||||
|
date.setSeconds(value);
|
||||||
|
break;
|
||||||
|
case "m":
|
||||||
|
case "n":
|
||||||
|
date.setMonth(value - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//timezone
|
||||||
|
this._parse_O = function () {
|
||||||
|
var hours = (Math.abs(date.getTimezoneOffset()/60)).toString();
|
||||||
|
if (hours.length == 1) {
|
||||||
|
hours = "0" + hours;
|
||||||
|
}
|
||||||
|
return "+" + hours + "00";
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_r = function () {
|
||||||
|
return this.parse("D, d M Y H:i:s O");
|
||||||
|
};
|
||||||
|
|
||||||
|
//parse year
|
||||||
|
this._parse_Y = function () {
|
||||||
|
return date.getFullYear().toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_y = function () {
|
||||||
|
var y = this._parse_Y();
|
||||||
|
return y.substr(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_L = function () {
|
||||||
|
var y = parseInt(this.parse("Y"));
|
||||||
|
if (y%4 ==0 && (y%100 > 0 || y%400 == 0)) {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
return "0";
|
||||||
|
};
|
||||||
|
|
||||||
|
//month
|
||||||
|
this._parse_m = function () {
|
||||||
|
var n = this._parse_n();
|
||||||
|
if (n.length < 2) {
|
||||||
|
n = "0" + n;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_n = function () {
|
||||||
|
return (date.getMonth() + 1).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_t = function () {
|
||||||
|
var t = 32 - new Date(this.get("Y"), this.get("m") - 1 , 32).getDate();
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_F = function () {
|
||||||
|
var n = parseInt(this.parse("n"));
|
||||||
|
var months = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||||
|
return months[n];
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_M = function () {
|
||||||
|
var n = parseInt(this.parse("n"));
|
||||||
|
var months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
return months[n];
|
||||||
|
};
|
||||||
|
|
||||||
|
//week
|
||||||
|
this._parse_w = function () {
|
||||||
|
return date.getDay().toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_D = function () {
|
||||||
|
var w = parseInt(this._parse_w());
|
||||||
|
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
return days[w];
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_l = function () {
|
||||||
|
var w = parseInt(this._parse_w());
|
||||||
|
var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||||
|
return days[w];
|
||||||
|
};
|
||||||
|
|
||||||
|
//day
|
||||||
|
this._parse_d = function () {
|
||||||
|
var j = this._parse_j();
|
||||||
|
if (j.length < 2) {
|
||||||
|
j = "0" + j;
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_j = function () {
|
||||||
|
return date.getDate().toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_W = function () {
|
||||||
|
var _date = new Tea.Date();
|
||||||
|
_date.set("m", 1);
|
||||||
|
_date.set("d", 1);
|
||||||
|
var w = parseInt(_date.parse("w"));
|
||||||
|
var m = parseInt(this.parse("m"), 10);
|
||||||
|
var total = 0;
|
||||||
|
for (var i=1; i<m; i++) {
|
||||||
|
var date2 = new Tea.Date();
|
||||||
|
date2.set("m", i);
|
||||||
|
var t = parseInt(date2.parse("t"));
|
||||||
|
total += t;
|
||||||
|
}
|
||||||
|
total += parseInt(this.parse("d"), 10);
|
||||||
|
var w2 = parseInt(this.parse("w"));
|
||||||
|
total = total - w2 + (w - 1);
|
||||||
|
var weeks = 0;
|
||||||
|
if (w2 != 0) {
|
||||||
|
weeks = (total/7 + 1).toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
weeks = (total/7).toString();
|
||||||
|
}
|
||||||
|
if (weeks.length == 1) {
|
||||||
|
weeks = "0" + weeks;
|
||||||
|
}
|
||||||
|
return weeks;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_z = function () {
|
||||||
|
var m = parseInt(this.parse("m"), 10);
|
||||||
|
var total = 0;
|
||||||
|
for (var i=1; i<m; i++) {
|
||||||
|
var date2 = new Tea.Date();
|
||||||
|
date2.set("m", i);
|
||||||
|
var t = parseInt(date2.parse("t"));
|
||||||
|
total += t;
|
||||||
|
}
|
||||||
|
total += parseInt(this.parse("d"), 10) - 1;
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
|
//minute
|
||||||
|
this._parse_i = function () {
|
||||||
|
var i = date.getMinutes().toString();
|
||||||
|
if (i.length < 2) {
|
||||||
|
i = "0" + i;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
//second
|
||||||
|
this._parse_s = function () {
|
||||||
|
var s = date.getSeconds().toString();
|
||||||
|
if (s.length < 2) {
|
||||||
|
s = "0" + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
//hour
|
||||||
|
this._parse_H = function () {
|
||||||
|
var H = this._parse_G();
|
||||||
|
if (H.length < 2) {
|
||||||
|
H = "0" + H;
|
||||||
|
}
|
||||||
|
return H;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_G = function () {
|
||||||
|
return date.getHours().toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_h = function () {
|
||||||
|
var h = this._parse_g();
|
||||||
|
if (h.length < 2) {
|
||||||
|
h = "0" + h;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_g = function () {
|
||||||
|
var g = parseInt(this._parse_G(), 10);
|
||||||
|
if (g > 12) {
|
||||||
|
g = g - 12;
|
||||||
|
}
|
||||||
|
return g.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
//time
|
||||||
|
this._parse_U = function () {
|
||||||
|
return this.time().toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
//am/pm
|
||||||
|
this._parse_a = function () {
|
||||||
|
var hour = this.parse("H");
|
||||||
|
return (hour<12)?"am":"pm";
|
||||||
|
};
|
||||||
|
|
||||||
|
this._parse_A = function () {
|
||||||
|
return this.parse("a").toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得当前时间对应的时间戳,代表了从 1970 年 1 月 1 日开始计算到 Date 对象中的时间之间的秒数
|
||||||
|
*
|
||||||
|
* @method time
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
this.time = function () {
|
||||||
|
return Math.round(date.getTime()/1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 将该对象转换成字符串格式
|
||||||
|
*
|
||||||
|
* @method toString
|
||||||
|
* @return String 该对象的字符串表示形式
|
||||||
|
*/
|
||||||
|
this.toString = function () {
|
||||||
|
return this.parse(format);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Tea.Date.toTime = function (dateStr) {
|
||||||
|
if (arguments.length == 1) {
|
||||||
|
return Date.parse(dateStr);
|
||||||
|
} else if (arguments.length == 3) {
|
||||||
|
arguments[1] = parseInt(arguments[1], 10) - 1;
|
||||||
|
return (new Date(arguments[0], arguments[1], arguments[2])).time();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Number.prototype.dateFormat = function (format) {
|
||||||
|
var date = new Tea.Date(format, this * 1000);
|
||||||
|
return date.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
Date.prototype.format = function (format) {
|
||||||
|
return new Tea.Date(format, this.getTime()).toString();
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
<script type="text/javascript" src="/ui/components.js?v=1.0.0"></script>
|
<script type="text/javascript" src="/ui/components.js?v=1.0.0"></script>
|
||||||
<script type="text/javascript" src="/js/utils.js"></script>
|
<script type="text/javascript" src="/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/date.tea.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tr v-for="(cert, index) in certs">
|
<tr v-for="(cert, index) in certs">
|
||||||
<td>{{cert.name}}</td>
|
<td>{{cert.name}}
|
||||||
|
<div v-if="cert.isCA" style="margin-top:0.5em">
|
||||||
|
<span class="ui label olive tiny">CA</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
|
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
43
web/views/@default/servers/components/ssl/selectPopup.html
Normal file
43
web/views/@default/servers/components/ssl/selectPopup.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{$layout "layout_popup"}
|
||||||
|
|
||||||
|
<h3>选择证书</h3>
|
||||||
|
<p class="comment" v-if="certs.length == 0">暂时还没有相关的证书。</p>
|
||||||
|
<table class="ui table selectable" v-if="certs.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>证书说明</th>
|
||||||
|
<th>顶级发行组织</th>
|
||||||
|
<th>域名</th>
|
||||||
|
<th>过期日期</th>
|
||||||
|
<th>引用服务</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th class="one op">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="(cert, index) in certs">
|
||||||
|
<td>{{cert.name}}
|
||||||
|
<div v-if="cert.isCA" style="margin-top:0.5em">
|
||||||
|
<span class="ui label olive tiny">CA</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div v-for="dnsName in cert.dnsNames" style="margin-bottom:0.4em">
|
||||||
|
<span class="ui label tiny">{{dnsName}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{certInfos[index].endDay}}</td>
|
||||||
|
<td>{{certInfos[index].countServers}}</td>
|
||||||
|
<td nowrap="">
|
||||||
|
<span class="ui label red tiny basic" v-if="certInfos[index].isExpired">已过期</span>
|
||||||
|
<span class="ui label green tiny basic" v-else>有效中</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="" @click.prevent="selectCert(cert)">选择</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="page" v-html="page"></div>
|
||||||
14
web/views/@default/servers/components/ssl/selectPopup.js
Normal file
14
web/views/@default/servers/components/ssl/selectPopup.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.selectCert = function (cert) {
|
||||||
|
NotifyPopup({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
cert: cert,
|
||||||
|
certRef: {
|
||||||
|
isOn: true,
|
||||||
|
certId: cert.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
{$layout}
|
{$layout}
|
||||||
|
|
||||||
{$template "/left_menu"}
|
{$template "/left_menu"}
|
||||||
|
|
||||||
|
{$var "header"}
|
||||||
|
<script src="/servers/components/ssl/datajs" type="text/javascript"></script>
|
||||||
|
<script src="/js/sortable.min.js" type="text/javascript"></script>
|
||||||
|
{$end}
|
||||||
|
|
||||||
<div class="right-box">
|
<div class="right-box">
|
||||||
<p class="comment">提醒:HTTP2、证书等信息修改后,可能需要清空浏览器缓存后才能浏览效果。</p>
|
<p class="comment">提醒:HTTP2、证书等信息修改后,可能需要清空浏览器缓存后才能浏览效果。</p>
|
||||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||||
@@ -26,6 +30,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<!-- SSL配置 -->
|
||||||
|
<ssl-config-box :v-ssl-policy="httpsConfig.sslPolicy" :v-protocol="'https'" v-show="httpsConfig.isOn"></ssl-config-box>
|
||||||
|
|
||||||
<submit-btn></submit-btn>
|
<submit-btn></submit-btn>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user