换成单集群模式
This commit is contained in:
BIN
EdgeAdmin/edge-admin
Normal file
BIN
EdgeAdmin/edge-admin
Normal file
Binary file not shown.
@@ -1,15 +1,14 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type AppSettingsAction struct {
|
||||
@@ -60,55 +59,70 @@ func (this *AppSettingsAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
clusters := make([]maps.Map, 0, len(clusterResp.GetClusters()))
|
||||
clusterDomainMap := map[int64]string{}
|
||||
clusterApiAddressMap := map[int64]string{}
|
||||
clusterNameMap := map[int64]string{}
|
||||
defaultPrimaryClusterId := int64(0)
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusterId := cluster.GetId()
|
||||
clusterName := cluster.GetName()
|
||||
|
||||
port := "443"
|
||||
if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 {
|
||||
var tlsConfig map[string]interface{}
|
||||
if err := json.Unmarshal(rawTLS, &tlsConfig); err == nil {
|
||||
if listenRaw, ok := tlsConfig["listen"]; ok && listenRaw != nil {
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
var listenAddresses []map[string]interface{}
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 {
|
||||
if portRange, ok := listenAddresses[0]["portRange"].(string); ok && len(portRange) > 0 {
|
||||
port = portRange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apiAddress := "https://" + cluster.GetServiceDomain() + ":" + port
|
||||
|
||||
clusters = append(clusters, maps.Map{
|
||||
"id": clusterId,
|
||||
"name": clusterName,
|
||||
"serviceDomain": cluster.GetServiceDomain(),
|
||||
"isDefault": cluster.GetIsDefault(),
|
||||
"id": clusterId,
|
||||
"name": clusterName,
|
||||
})
|
||||
clusterDomainMap[clusterId] = cluster.GetServiceDomain()
|
||||
clusterApiAddressMap[clusterId] = apiAddress
|
||||
clusterNameMap[clusterId] = clusterName
|
||||
if defaultPrimaryClusterId <= 0 && cluster.GetIsDefault() {
|
||||
defaultPrimaryClusterId = clusterId
|
||||
}
|
||||
|
||||
// 读取应用绑定的集群列表,取第一个作为当前选中。
|
||||
var selectedClusterId int64
|
||||
if raw := app.Get("clusterIds"); raw != nil {
|
||||
if ids, ok := raw.([]int64); ok && len(ids) > 0 {
|
||||
selectedClusterId = ids[0]
|
||||
}
|
||||
}
|
||||
|
||||
defaultBackupClusterId := int64(0)
|
||||
defaultBackupResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
// 构建服务地址列表。
|
||||
serviceAddresses := make([]maps.Map, 0)
|
||||
if selectedClusterId > 0 {
|
||||
addr := clusterApiAddressMap[selectedClusterId]
|
||||
name := clusterNameMap[selectedClusterId]
|
||||
if len(addr) > 0 {
|
||||
serviceAddresses = append(serviceAddresses, maps.Map{
|
||||
"address": addr,
|
||||
"clusterName": name,
|
||||
})
|
||||
}
|
||||
}
|
||||
if defaultBackupResp != nil && len(defaultBackupResp.GetValueJSON()) > 0 {
|
||||
defaultBackupClusterId = types.Int64(string(defaultBackupResp.GetValueJSON()))
|
||||
}
|
||||
|
||||
primaryClusterId := app.GetInt64("primaryClusterId")
|
||||
backupClusterId := app.GetInt64("backupClusterId")
|
||||
|
||||
settings := maps.Map{
|
||||
"appId": app.GetString("appId"),
|
||||
"appStatus": app.GetBool("isOn"),
|
||||
"primaryClusterId": primaryClusterId,
|
||||
"backupClusterId": backupClusterId,
|
||||
"defaultPrimaryClusterId": defaultPrimaryClusterId,
|
||||
"defaultPrimaryClusterName": clusterNameMap[defaultPrimaryClusterId],
|
||||
"defaultBackupClusterId": defaultBackupClusterId,
|
||||
"defaultBackupClusterName": clusterNameMap[defaultBackupClusterId],
|
||||
"primaryServiceDomain": clusterDomainMap[primaryClusterId],
|
||||
"backupServiceDomain": clusterDomainMap[backupClusterId],
|
||||
"signEnabled": app.GetBool("signEnabled"),
|
||||
"signSecretPlain": app.GetString("signSecretPlain"),
|
||||
"signSecretMasked": app.GetString("signSecretMasked"),
|
||||
"signSecretUpdatedAt": app.GetString("signSecretUpdated"),
|
||||
"appId": app.GetString("appId"),
|
||||
"appStatus": app.GetBool("isOn"),
|
||||
"selectedClusterId": selectedClusterId,
|
||||
"serviceAddresses": serviceAddresses,
|
||||
"signEnabled": app.GetBool("signEnabled"),
|
||||
"signSecretPlain": app.GetString("signSecretPlain"),
|
||||
"signSecretMasked": app.GetString("signSecretMasked"),
|
||||
"signSecretUpdatedAt": app.GetString("signSecretUpdated"),
|
||||
}
|
||||
this.Data["app"] = app
|
||||
this.Data["settings"] = settings
|
||||
@@ -119,18 +133,13 @@ func (this *AppSettingsAction) RunGet(params struct {
|
||||
func (this *AppSettingsAction) RunPost(params struct {
|
||||
AppId int64
|
||||
|
||||
AppStatus bool
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId int64
|
||||
AppStatus bool
|
||||
ClusterId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
if params.PrimaryClusterId > 0 && params.BackupClusterId > 0 && params.PrimaryClusterId == params.BackupClusterId {
|
||||
this.FailField("backupClusterId", "备用集群不能与主集群相同")
|
||||
return
|
||||
}
|
||||
|
||||
appResp, err := this.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(this.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||
AppDbId: params.AppId,
|
||||
@@ -144,13 +153,18 @@ func (this *AppSettingsAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
var clusterIds []int64
|
||||
if params.ClusterId > 0 {
|
||||
clusterIds = []int64{params.ClusterId}
|
||||
}
|
||||
clusterIdsJSON, _ := json.Marshal(clusterIds)
|
||||
|
||||
_, err = this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(this.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||
AppDbId: params.AppId,
|
||||
Name: appResp.GetApp().GetName(),
|
||||
PrimaryClusterId: params.PrimaryClusterId,
|
||||
BackupClusterId: params.BackupClusterId,
|
||||
IsOn: params.AppStatus,
|
||||
UserId: appResp.GetApp().GetUserId(),
|
||||
AppDbId: params.AppId,
|
||||
Name: appResp.GetApp().GetName(),
|
||||
ClusterIdsJSON: clusterIdsJSON,
|
||||
IsOn: params.AppStatus,
|
||||
UserId: appResp.GetApp().GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -33,28 +34,6 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
|
||||
defaultPrimaryClusterId := int64(0)
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
if cluster.GetIsDefault() {
|
||||
defaultPrimaryClusterId = cluster.GetId()
|
||||
break
|
||||
}
|
||||
}
|
||||
if defaultPrimaryClusterId <= 0 && len(clusters) > 0 {
|
||||
defaultPrimaryClusterId = clusters[0].GetInt64("id")
|
||||
}
|
||||
this.Data["defaultPrimaryClusterId"] = defaultPrimaryClusterId
|
||||
|
||||
defaultBackupClusterId := int64(0)
|
||||
for _, cluster := range clusters {
|
||||
clusterId := cluster.GetInt64("id")
|
||||
if clusterId > 0 && clusterId != defaultPrimaryClusterId {
|
||||
defaultBackupClusterId = clusterId
|
||||
break
|
||||
}
|
||||
}
|
||||
this.Data["defaultBackupClusterId"] = defaultBackupClusterId
|
||||
|
||||
usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
@@ -77,28 +56,28 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId int64
|
||||
UserId int64
|
||||
Name string
|
||||
ClusterId int64
|
||||
UserId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("name", params.Name).Require("请输入应用名称")
|
||||
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "请输入主服务集群")
|
||||
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
||||
this.FailField("backupClusterId", "备用服务集群必须和主服务集群不一致")
|
||||
if params.ClusterId <= 0 {
|
||||
this.FailField("clusterId", "请选择集群")
|
||||
return
|
||||
}
|
||||
|
||||
clusterIdsJSON, _ := json.Marshal([]int64{params.ClusterId})
|
||||
|
||||
createResp, err := this.RPC().HTTPDNSAppRPC().CreateHTTPDNSApp(this.AdminContext(), &pb.CreateHTTPDNSAppRequest{
|
||||
Name: params.Name,
|
||||
AppId: "app" + strconv.FormatInt(time.Now().UnixNano()%1_000_000_000_000, 36),
|
||||
PrimaryClusterId: params.PrimaryClusterId,
|
||||
BackupClusterId: params.BackupClusterId,
|
||||
IsOn: true,
|
||||
SignEnabled: true,
|
||||
UserId: params.UserId,
|
||||
Name: params.Name,
|
||||
AppId: "app" + strconv.FormatInt(time.Now().UnixNano()%1_000_000_000_000, 36),
|
||||
ClusterIdsJSON: clusterIdsJSON,
|
||||
IsOn: true,
|
||||
SignEnabled: true,
|
||||
UserId: params.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -24,5 +24,6 @@ func (this *IndexAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
this.Data["apps"] = apps
|
||||
this.Data["page"] = ""
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -87,15 +88,14 @@ func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, erro
|
||||
return apps[0], nil
|
||||
}
|
||||
|
||||
func createApp(parent *actionutils.ParentAction, name string, primaryClusterId int64, backupClusterId int64) (int64, error) {
|
||||
func createApp(parent *actionutils.ParentAction, name string, clusterIdsJSON []byte) (int64, error) {
|
||||
newAppId := "app" + strconv.FormatInt(time.Now().UnixNano()%1_000_000_000_000, 36)
|
||||
resp, err := parent.RPC().HTTPDNSAppRPC().CreateHTTPDNSApp(parent.AdminContext(), &pb.CreateHTTPDNSAppRequest{
|
||||
Name: strings.TrimSpace(name),
|
||||
AppId: newAppId,
|
||||
PrimaryClusterId: primaryClusterId,
|
||||
BackupClusterId: backupClusterId,
|
||||
IsOn: true,
|
||||
SignEnabled: true,
|
||||
Name: strings.TrimSpace(name),
|
||||
AppId: newAppId,
|
||||
ClusterIdsJSON: clusterIdsJSON,
|
||||
IsOn: true,
|
||||
SignEnabled: true,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -110,14 +110,13 @@ func deleteAppByID(parent *actionutils.ParentAction, appDbId int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func updateAppSettings(parent *actionutils.ParentAction, appDbId int64, name string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) error {
|
||||
func updateAppSettings(parent *actionutils.ParentAction, appDbId int64, name string, clusterIdsJSON []byte, isOn bool, userId int64) error {
|
||||
_, err := parent.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(parent.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||
AppDbId: appDbId,
|
||||
Name: strings.TrimSpace(name),
|
||||
PrimaryClusterId: primaryClusterId,
|
||||
BackupClusterId: backupClusterId,
|
||||
IsOn: isOn,
|
||||
UserId: userId,
|
||||
AppDbId: appDbId,
|
||||
Name: strings.TrimSpace(name),
|
||||
ClusterIdsJSON: clusterIdsJSON,
|
||||
IsOn: isOn,
|
||||
UserId: userId,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -277,13 +276,24 @@ func toggleCustomRule(parent *actionutils.ParentAction, ruleId int64, isOn bool)
|
||||
return err
|
||||
}
|
||||
|
||||
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterNameMap map[int64]string, userMapByID map[int64]maps.Map) maps.Map {
|
||||
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterMapByID map[int64]maps.Map, userMapByID map[int64]maps.Map) maps.Map {
|
||||
signSecret := app.GetSignSecret()
|
||||
|
||||
primaryClusterID := app.GetPrimaryClusterId()
|
||||
backupClusterID := app.GetBackupClusterId()
|
||||
primaryClusterMap := maps.Map{"id": primaryClusterID, "name": clusterNameMap[primaryClusterID]}
|
||||
backupClusterMap := maps.Map{"id": backupClusterID, "name": clusterNameMap[backupClusterID]}
|
||||
// 读取集群 ID 列表
|
||||
var clusterIds []int64
|
||||
if len(app.GetClusterIdsJSON()) > 0 {
|
||||
_ = json.Unmarshal(app.GetClusterIdsJSON(), &clusterIds)
|
||||
}
|
||||
|
||||
// 构建集群映射列表
|
||||
var clusterMaps []maps.Map
|
||||
for _, cid := range clusterIds {
|
||||
cm := clusterMapByID[cid]
|
||||
if cm == nil {
|
||||
cm = maps.Map{"id": cid, "name": "", "apiAddress": ""}
|
||||
}
|
||||
clusterMaps = append(clusterMaps, cm)
|
||||
}
|
||||
|
||||
var userMap maps.Map
|
||||
if app.GetUserId() > 0 {
|
||||
@@ -301,11 +311,8 @@ func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterNameMap map[int64]
|
||||
"id": app.GetId(),
|
||||
"name": app.GetName(),
|
||||
"appId": app.GetAppId(),
|
||||
"clusterId": primaryClusterID,
|
||||
"primaryClusterId": primaryClusterID,
|
||||
"backupClusterId": backupClusterID,
|
||||
"primaryCluster": primaryClusterMap,
|
||||
"backupCluster": backupClusterMap,
|
||||
"clusterIds": clusterIds,
|
||||
"clusters": clusterMaps,
|
||||
"userId": app.GetUserId(),
|
||||
"user": userMap,
|
||||
"isOn": app.GetIsOn(),
|
||||
@@ -318,15 +325,37 @@ func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterNameMap map[int64]
|
||||
}
|
||||
}
|
||||
|
||||
func loadHTTPDNSClusterNameMap(parent *actionutils.ParentAction) (map[int64]string, error) {
|
||||
func loadHTTPDNSClusterNameMap(parent *actionutils.ParentAction) (map[int64]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(parent.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := map[int64]string{}
|
||||
result := map[int64]maps.Map{}
|
||||
for _, cluster := range resp.GetClusters() {
|
||||
result[cluster.GetId()] = cluster.GetName()
|
||||
port := "443"
|
||||
if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 {
|
||||
tlsConfig := maps.Map{}
|
||||
if err := json.Unmarshal(rawTLS, &tlsConfig); err == nil {
|
||||
if listenRaw := tlsConfig.Get("listen"); listenRaw != nil {
|
||||
var listenAddresses []maps.Map
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 && len(listenAddresses[0].GetString("portRange")) > 0 {
|
||||
port = listenAddresses[0].GetString("portRange")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apiAddress := "https://" + cluster.GetServiceDomain() + ":" + port
|
||||
|
||||
result[cluster.GetId()] = maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"apiAddress": apiAddress,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type InstallAction struct {
|
||||
@@ -55,7 +58,22 @@ func (this *InstallAction) RunGet(params struct {
|
||||
apiEndpoints = []string{"http://127.0.0.1:7788"}
|
||||
}
|
||||
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
||||
// 从 NodeLogin 中提取 SSH 地址
|
||||
this.Data["sshAddr"] = ""
|
||||
loginMap, _ := node.Get("login").(maps.Map)
|
||||
if loginMap != nil {
|
||||
paramsMap, _ := loginMap.Get("params").(maps.Map)
|
||||
if paramsMap != nil {
|
||||
host := paramsMap.GetString("host")
|
||||
if len(host) > 0 {
|
||||
port := paramsMap.GetInt("port")
|
||||
if port <= 0 {
|
||||
port = 22
|
||||
}
|
||||
this.Data["sshAddr"] = fmt.Sprintf("%s:%d", configutils.QuoteIP(host), port)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,21 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma
|
||||
if installStatusMap.Has("ipAddresses") {
|
||||
for _, addr := range installStatusMap.GetSlice("ipAddresses") {
|
||||
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||
ipAddresses = append(ipAddresses, maps.Map(addrMap))
|
||||
m := maps.Map(addrMap)
|
||||
// 确保必要字段存在,防止前端组件报错
|
||||
if !m.Has("name") {
|
||||
m["name"] = ""
|
||||
}
|
||||
if !m.Has("canAccess") {
|
||||
m["canAccess"] = true
|
||||
}
|
||||
if !m.Has("isOn") {
|
||||
m["isOn"] = true
|
||||
}
|
||||
if !m.Has("isUp") {
|
||||
m["isUp"] = true
|
||||
}
|
||||
ipAddresses = append(ipAddresses, m)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -87,11 +101,16 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构造 node.login 用于 index.html 展示 SSH 信息
|
||||
// 构造 node.login 用于 index.html 展示 SSH 信息(从 NodeLogin 读取)
|
||||
var loginMap maps.Map = nil
|
||||
sshInfo := installStatusMap.GetMap("ssh")
|
||||
if sshInfo != nil {
|
||||
grantId := sshInfo.GetInt64("grantId")
|
||||
if node.GetNodeLogin() != nil {
|
||||
nodeLogin := node.GetNodeLogin()
|
||||
sshLoginParams := maps.Map{}
|
||||
if len(nodeLogin.Params) > 0 {
|
||||
_ = json.Unmarshal(nodeLogin.Params, &sshLoginParams)
|
||||
}
|
||||
|
||||
grantId := sshLoginParams.GetInt64("grantId")
|
||||
var grantMap maps.Map = nil
|
||||
if grantId > 0 {
|
||||
grantResp, grantErr := parent.RPC().NodeGrantRPC().FindEnabledNodeGrant(parent.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||
@@ -110,8 +129,8 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma
|
||||
|
||||
loginMap = maps.Map{
|
||||
"params": maps.Map{
|
||||
"host": sshInfo.GetString("host"),
|
||||
"port": sshInfo.GetInt("port"),
|
||||
"host": sshLoginParams.GetString("host"),
|
||||
"port": sshLoginParams.GetInt("port"),
|
||||
},
|
||||
"grant": grantMap,
|
||||
}
|
||||
@@ -169,8 +188,8 @@ func decodeNodeStatus(raw []byte) maps.Map {
|
||||
func decodeInstallStatus(raw []byte) maps.Map {
|
||||
result := maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"isFinished": false,
|
||||
"isOk": false,
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
|
||||
@@ -47,39 +47,62 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
sshPort := 22
|
||||
ipAddresses := []maps.Map{}
|
||||
var grantId int64
|
||||
var loginId int64
|
||||
this.Data["grant"] = nil
|
||||
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err == nil && resp.GetNode() != nil {
|
||||
// 从 NodeLogin 读取 SSH 信息
|
||||
if resp.GetNode().GetNodeLogin() != nil {
|
||||
nodeLogin := resp.GetNode().GetNodeLogin()
|
||||
loginId = nodeLogin.Id
|
||||
if len(nodeLogin.Params) > 0 {
|
||||
sshLoginParams := maps.Map{}
|
||||
_ = json.Unmarshal(nodeLogin.Params, &sshLoginParams)
|
||||
if h := strings.TrimSpace(sshLoginParams.GetString("host")); len(h) > 0 {
|
||||
sshHost = h
|
||||
}
|
||||
if p := sshLoginParams.GetInt("port"); p > 0 {
|
||||
sshPort = p
|
||||
}
|
||||
grantId = sshLoginParams.GetInt64("grantId")
|
||||
}
|
||||
}
|
||||
|
||||
// IP 地址仍从 installStatus 读取
|
||||
if len(resp.GetNode().GetInstallStatusJSON()) > 0 {
|
||||
installStatus := maps.Map{}
|
||||
_ = json.Unmarshal(resp.GetNode().GetInstallStatusJSON(), &installStatus)
|
||||
sshInfo := installStatus.GetMap("ssh")
|
||||
if sshInfo != nil {
|
||||
if h := strings.TrimSpace(sshInfo.GetString("host")); len(h) > 0 {
|
||||
sshHost = h
|
||||
}
|
||||
if p := sshInfo.GetInt("port"); p > 0 {
|
||||
sshPort = p
|
||||
}
|
||||
grantId = sshInfo.GetInt64("grantId")
|
||||
}
|
||||
|
||||
|
||||
if installStatus.Has("ipAddresses") {
|
||||
for _, addr := range installStatus.GetSlice("ipAddresses") {
|
||||
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||
ipAddresses = append(ipAddresses, maps.Map(addrMap))
|
||||
m := maps.Map(addrMap)
|
||||
// 确保必要字段存在,防止前端组件报错
|
||||
if !m.Has("name") {
|
||||
m["name"] = ""
|
||||
}
|
||||
if !m.Has("canAccess") {
|
||||
m["canAccess"] = true
|
||||
}
|
||||
if !m.Has("isOn") {
|
||||
m["isOn"] = true
|
||||
}
|
||||
if !m.Has("isUp") {
|
||||
m["isUp"] = true
|
||||
}
|
||||
ipAddresses = append(ipAddresses, m)
|
||||
}
|
||||
}
|
||||
} else if ip := strings.TrimSpace(installStatus.GetString("ipAddr")); len(ip) > 0 {
|
||||
ipAddresses = append(ipAddresses, maps.Map{
|
||||
"ip": ip,
|
||||
"name": "",
|
||||
"ip": ip,
|
||||
"name": "",
|
||||
"canAccess": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -87,6 +110,7 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
|
||||
this.Data["sshHost"] = sshHost
|
||||
this.Data["sshPort"] = sshPort
|
||||
this.Data["loginId"] = loginId
|
||||
this.Data["ipAddresses"] = ipAddresses
|
||||
|
||||
if grantId > 0 {
|
||||
@@ -107,11 +131,12 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
Name string
|
||||
ClusterId int64
|
||||
IsOn bool
|
||||
SshHost string
|
||||
NodeId int64
|
||||
Name string
|
||||
ClusterId int64
|
||||
IsOn bool
|
||||
LoginId int64
|
||||
SshHost string
|
||||
SshPort int
|
||||
GrantId int64
|
||||
IpAddressesJSON []byte
|
||||
@@ -155,8 +180,6 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
needUpdateInstallStatus := hasSSHUpdate || len(ipAddresses) > 0
|
||||
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
@@ -186,7 +209,31 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
if needUpdateInstallStatus {
|
||||
// SSH 保存到 NodeLogin
|
||||
if hasSSHUpdate {
|
||||
login := &pb.NodeLogin{
|
||||
Id: params.LoginId,
|
||||
Name: "SSH",
|
||||
Type: "ssh",
|
||||
Params: maps.Map{
|
||||
"grantId": params.GrantId,
|
||||
"host": params.SshHost,
|
||||
"port": params.SshPort,
|
||||
}.AsJSON(),
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeLogin(this.AdminContext(), &pb.UpdateHTTPDNSNodeLoginRequest{
|
||||
NodeId: params.NodeId,
|
||||
NodeLogin: login,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// IP 地址仍保存在 installStatus 中
|
||||
if len(ipAddresses) > 0 {
|
||||
installStatus := maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
@@ -197,19 +244,10 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
if len(node.GetInstallStatusJSON()) > 0 {
|
||||
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||
}
|
||||
if hasSSHUpdate {
|
||||
installStatus["ssh"] = maps.Map{
|
||||
"host": params.SshHost,
|
||||
"port": params.SshPort,
|
||||
"grantId": params.GrantId,
|
||||
}
|
||||
}
|
||||
if len(ipAddresses) > 0 {
|
||||
installStatus["ipAddresses"] = ipAddresses
|
||||
} else {
|
||||
delete(installStatus, "ipAddresses")
|
||||
delete(installStatus, "ipAddr") // Cleanup legacy
|
||||
}
|
||||
installStatus["ipAddresses"] = ipAddresses
|
||||
// 清理旧的 ssh 字段
|
||||
delete(installStatus, "ssh")
|
||||
delete(installStatus, "ipAddr")
|
||||
|
||||
installStatusJSON, _ := json.Marshal(installStatus)
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
|
||||
@@ -34,7 +34,7 @@ func (this *UpdateInstallStatusAction) RunPost(params struct {
|
||||
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||
}
|
||||
installStatus["isRunning"] = false
|
||||
installStatus["isFinished"] = true
|
||||
installStatus["isFinished"] = params.IsInstalled // 标记为未安装时重置为"未开始"状态
|
||||
installStatus["isOk"] = params.IsInstalled
|
||||
installStatus["error"] = ""
|
||||
installStatus["errorCode"] = ""
|
||||
|
||||
@@ -10,10 +10,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type ClusterSettingsAction struct {
|
||||
@@ -49,11 +47,9 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
||||
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
|
||||
"installDir": cluster.GetString("installDir"),
|
||||
"isOn": cluster.GetBool("isOn"),
|
||||
"isDefaultCluster": cluster.GetBool("isDefault"),
|
||||
"isDefaultBackupCluster": false,
|
||||
}
|
||||
if settings.GetInt("cacheTtl") <= 0 {
|
||||
settings["cacheTtl"] = 30
|
||||
settings["cacheTtl"] = 60
|
||||
}
|
||||
if settings.GetInt("fallbackTimeout") <= 0 {
|
||||
settings["fallbackTimeout"] = 300
|
||||
@@ -62,22 +58,9 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
||||
settings["installDir"] = "/opt/edge-httpdns"
|
||||
}
|
||||
|
||||
defaultBackupResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
defaultBackupClusterId := int64(0)
|
||||
if defaultBackupResp != nil && len(defaultBackupResp.GetValueJSON()) > 0 {
|
||||
defaultBackupClusterId = types.Int64(string(defaultBackupResp.GetValueJSON()))
|
||||
}
|
||||
settings["isDefaultBackupCluster"] = defaultBackupClusterId == params.ClusterId
|
||||
|
||||
listenAddresses := []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolHTTPS,
|
||||
Protocol: serverconfigs.ProtocolTLS,
|
||||
Host: "",
|
||||
PortRange: "443",
|
||||
},
|
||||
@@ -126,9 +109,7 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefaultCluster bool
|
||||
IsDefaultBackupCluster bool
|
||||
IsOn bool
|
||||
|
||||
Addresses []byte
|
||||
SslPolicyJSON []byte
|
||||
@@ -145,7 +126,7 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||
|
||||
if params.CacheTtl <= 0 {
|
||||
params.CacheTtl = 30
|
||||
params.CacheTtl = 60
|
||||
}
|
||||
if params.FallbackTimeout <= 0 {
|
||||
params.FallbackTimeout = 300
|
||||
@@ -153,18 +134,6 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
if len(params.InstallDir) == 0 {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
if params.IsDefaultCluster && !params.IsOn {
|
||||
this.Fail("默认主集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultBackupCluster && !params.IsOn {
|
||||
this.Fail("默认备用集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultCluster && params.IsDefaultBackupCluster {
|
||||
this.Fail("默认主集群和默认备用集群不能是同一个集群")
|
||||
return
|
||||
}
|
||||
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
@@ -213,35 +182,7 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
InstallDir: params.InstallDir,
|
||||
TlsPolicyJSON: tlsPolicyJSON,
|
||||
IsOn: params.IsOn,
|
||||
IsDefault: params.IsDefaultCluster,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
backupClusterValue := int64(0)
|
||||
if params.IsDefaultBackupCluster {
|
||||
backupClusterValue = params.ClusterId
|
||||
} else {
|
||||
readResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if readResp != nil && len(readResp.GetValueJSON()) > 0 {
|
||||
oldBackupClusterId := types.Int64(string(readResp.GetValueJSON()))
|
||||
if oldBackupClusterId != params.ClusterId {
|
||||
backupClusterValue = oldBackupClusterId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
ValueJSON: []byte(strconv.FormatInt(backupClusterValue, 10)),
|
||||
IsDefault: false,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
@@ -25,14 +23,12 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefaultPrimary bool
|
||||
IsDefaultBackup bool
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -43,7 +39,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
if params.CacheTtl <= 0 {
|
||||
params.CacheTtl = 30
|
||||
params.CacheTtl = 60
|
||||
}
|
||||
if params.FallbackTimeout <= 0 {
|
||||
params.FallbackTimeout = 300
|
||||
@@ -52,19 +48,6 @@ func (this *CreateAction) RunPost(params struct {
|
||||
params.Must.Field("name", params.Name).Require("请输入集群名称")
|
||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||
|
||||
if params.IsDefaultPrimary && !params.IsOn {
|
||||
this.Fail("默认主集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultBackup && !params.IsOn {
|
||||
this.Fail("默认备用集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultPrimary && params.IsDefaultBackup {
|
||||
this.Fail("默认主集群和默认备用集群不能是同一个集群")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().HTTPDNSClusterRPC().CreateHTTPDNSCluster(this.AdminContext(), &pb.CreateHTTPDNSClusterRequest{
|
||||
Name: params.Name,
|
||||
ServiceDomain: params.GatewayDomain,
|
||||
@@ -72,24 +55,13 @@ func (this *CreateAction) RunPost(params struct {
|
||||
FallbackTimeoutMs: params.FallbackTimeout,
|
||||
InstallDir: params.InstallDir,
|
||||
IsOn: params.IsOn,
|
||||
IsDefault: params.IsDefaultPrimary,
|
||||
IsDefault: false,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if params.IsDefaultBackup {
|
||||
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
ValueJSON: []byte(strconv.FormatInt(resp.GetClusterId(), 10)),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = resp.GetClusterId()
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -37,10 +37,29 @@ func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.M
|
||||
}
|
||||
}
|
||||
|
||||
port := "443"
|
||||
if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 {
|
||||
tlsConfig := maps.Map{}
|
||||
if err := json.Unmarshal(rawTLS, &tlsConfig); err == nil {
|
||||
if listenRaw := tlsConfig.Get("listen"); listenRaw != nil {
|
||||
var listenAddresses []maps.Map
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 && len(listenAddresses[0].GetString("portRange")) > 0 {
|
||||
port = listenAddresses[0].GetString("portRange")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apiAddress := "https://" + cluster.GetServiceDomain() + ":" + port
|
||||
|
||||
result = append(result, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"gatewayDomain": cluster.GetServiceDomain(),
|
||||
"apiAddress": apiAddress,
|
||||
"defaultTTL": cluster.GetDefaultTTL(),
|
||||
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
|
||||
"installDir": cluster.GetInstallDir(),
|
||||
@@ -86,7 +105,7 @@ func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map
|
||||
"id": int64(0),
|
||||
"name": "",
|
||||
"gatewayDomain": "",
|
||||
"defaultTTL": 30,
|
||||
"defaultTTL": 60,
|
||||
"fallbackTimeout": 300,
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
}, nil
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -47,25 +47,36 @@ func (this *UpdateNodeSSHAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["loginId"] = 0
|
||||
|
||||
if resp.GetNode() != nil && len(resp.GetNode().GetInstallStatusJSON()) > 0 {
|
||||
installStatus := maps.Map{}
|
||||
_ = json.Unmarshal(resp.GetNode().GetInstallStatusJSON(), &installStatus)
|
||||
sshInfo := installStatus.GetMap("ssh")
|
||||
if sshInfo != nil {
|
||||
if host := strings.TrimSpace(sshInfo.GetString("host")); len(host) > 0 {
|
||||
loginParams["host"] = host
|
||||
}
|
||||
if port := sshInfo.GetInt("port"); port > 0 {
|
||||
loginParams["port"] = port
|
||||
}
|
||||
if grantID := sshInfo.GetInt64("grantId"); grantID > 0 {
|
||||
loginParams["grantId"] = grantID
|
||||
}
|
||||
// 从 NodeLogin 读取 SSH 信息
|
||||
if resp.GetNode() != nil && resp.GetNode().GetNodeLogin() != nil {
|
||||
nodeLogin := resp.GetNode().GetNodeLogin()
|
||||
this.Data["loginId"] = nodeLogin.Id
|
||||
if len(nodeLogin.Params) > 0 {
|
||||
_ = json.Unmarshal(nodeLogin.Params, &loginParams)
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["params"] = loginParams
|
||||
this.Data["grant"] = nil
|
||||
|
||||
// 认证信息
|
||||
grantId := loginParams.GetInt64("grantId")
|
||||
var grantMap maps.Map = nil
|
||||
if grantId > 0 {
|
||||
grantResp, grantErr := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||
NodeGrantId: grantId,
|
||||
})
|
||||
if grantErr == nil && grantResp.GetNodeGrant() != nil {
|
||||
g := grantResp.GetNodeGrant()
|
||||
grantMap = maps.Map{
|
||||
"id": g.Id,
|
||||
"name": g.Name,
|
||||
"method": g.Method,
|
||||
"methodName": g.Method,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["grant"] = grantMap
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -93,46 +104,23 @@ func (this *UpdateNodeSSHAction) RunPost(params struct {
|
||||
this.Fail("SSH 主机地址 IP 格式错误")
|
||||
}
|
||||
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
node := resp.GetNode()
|
||||
if node == nil {
|
||||
this.Fail("节点不存在")
|
||||
return
|
||||
login := &pb.NodeLogin{
|
||||
Id: params.LoginId,
|
||||
Name: "SSH",
|
||||
Type: "ssh",
|
||||
Params: maps.Map{
|
||||
"grantId": params.GrantId,
|
||||
"host": params.SshHost,
|
||||
"port": params.SshPort,
|
||||
}.AsJSON(),
|
||||
}
|
||||
|
||||
installStatus := maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": node.GetIsInstalled(),
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
if len(node.GetInstallStatusJSON()) > 0 {
|
||||
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||
}
|
||||
installStatus["ssh"] = maps.Map{
|
||||
"host": params.SshHost,
|
||||
"port": params.SshPort,
|
||||
"grantId": params.GrantId,
|
||||
}
|
||||
|
||||
installStatusJSON, _ := json.Marshal(installStatus)
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: node.GetIsUp(),
|
||||
IsInstalled: node.GetIsInstalled(),
|
||||
IsActive: node.GetIsActive(),
|
||||
StatusJSON: node.GetStatusJSON(),
|
||||
InstallStatusJSON: installStatusJSON,
|
||||
_, err := this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeLogin(this.AdminContext(), &pb.UpdateHTTPDNSNodeLoginRequest{
|
||||
NodeId: params.NodeId,
|
||||
NodeLogin: login,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
this.Fail("保存SSH登录信息失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package guide
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("httpdns", "app", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.RedirectURL("/httpdns/apps")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package guide
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeHttpDNS)).
|
||||
Data("teaMenu", "httpdns").
|
||||
Data("teaSubMenu", "guide").
|
||||
Prefix("/httpdns/guide").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package policies
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("httpdns", "policy", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.RedirectURL("/httpdns/clusters")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct{}) {
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeHttpDNS)).
|
||||
Data("teaMenu", "httpdns").
|
||||
Data("teaSubMenu", "policy").
|
||||
Prefix("/httpdns/policies").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
@@ -25,9 +28,37 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
}
|
||||
clusters := make([]maps.Map, 0, len(clusterResp.GetClusters()))
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
serviceDomain := strings.TrimSpace(cluster.GetServiceDomain())
|
||||
|
||||
port := "443"
|
||||
if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 {
|
||||
var tlsConfig map[string]interface{}
|
||||
if err := json.Unmarshal(rawTLS, &tlsConfig); err == nil {
|
||||
if listenRaw, ok := tlsConfig["listen"]; ok && listenRaw != nil {
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
var listenAddresses []map[string]interface{}
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 {
|
||||
if portRange, ok := listenAddresses[0]["portRange"].(string); ok && len(portRange) > 0 {
|
||||
port = portRange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apiAddress := "https://" + serviceDomain + ":" + port
|
||||
|
||||
displayName := apiAddress
|
||||
if len(serviceDomain) == 0 {
|
||||
displayName = cluster.GetName()
|
||||
}
|
||||
clusters = append(clusters, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"serviceDomain": serviceDomain,
|
||||
"displayName": displayName,
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
@@ -50,14 +81,18 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
for _, domain := range domainResp.GetDomains() {
|
||||
domains = append(domains, domain.GetDomain())
|
||||
}
|
||||
// 解析集群ID列表
|
||||
var clusterIds []int64
|
||||
if len(app.GetClusterIdsJSON()) > 0 {
|
||||
_ = json.Unmarshal(app.GetClusterIdsJSON(), &clusterIds)
|
||||
}
|
||||
|
||||
apps = append(apps, maps.Map{
|
||||
"id": app.GetId(),
|
||||
"name": app.GetName(),
|
||||
"appId": app.GetAppId(),
|
||||
"clusterId": app.GetPrimaryClusterId(),
|
||||
"primaryClusterId": app.GetPrimaryClusterId(),
|
||||
"backupClusterId": app.GetBackupClusterId(),
|
||||
"domains": domains,
|
||||
"id": app.GetId(),
|
||||
"name": app.GetName(),
|
||||
"appId": app.GetAppId(),
|
||||
"clusterIds": clusterIds,
|
||||
"domains": domains,
|
||||
})
|
||||
}
|
||||
this.Data["apps"] = apps
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -52,12 +53,30 @@ func (this *TestAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
clusterDomain := ""
|
||||
port := "443"
|
||||
if params.ClusterId > 0 {
|
||||
clusterResp, findErr := this.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(this.AdminContext(), &pb.FindHTTPDNSClusterRequest{
|
||||
ClusterId: params.ClusterId,
|
||||
})
|
||||
if findErr == nil && clusterResp.GetCluster() != nil {
|
||||
clusterDomain = strings.TrimSpace(clusterResp.GetCluster().GetServiceDomain())
|
||||
if rawTLS := clusterResp.GetCluster().GetTlsPolicyJSON(); len(rawTLS) > 0 {
|
||||
var tlsConfig map[string]interface{}
|
||||
if err := json.Unmarshal(rawTLS, &tlsConfig); err == nil {
|
||||
if listenRaw, ok := tlsConfig["listen"]; ok && listenRaw != nil {
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
var listenAddresses []map[string]interface{}
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 {
|
||||
if portRange, ok := listenAddresses[0]["portRange"].(string); ok && len(portRange) > 0 {
|
||||
port = portRange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(clusterDomain) == 0 {
|
||||
@@ -80,7 +99,7 @@ func (this *TestAction) RunPost(params struct {
|
||||
query.Set("nonce", nonce)
|
||||
query.Set("sign", sign)
|
||||
}
|
||||
requestURL := "https://" + clusterDomain + "/resolve?" + query.Encode()
|
||||
requestURL := "https://" + clusterDomain + ":" + port + "/resolve?" + query.Encode()
|
||||
|
||||
resultCode := 1
|
||||
if strings.EqualFold(resp.GetCode(), "SUCCESS") {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
@@ -23,6 +24,38 @@ func (this *CreatePopupAction) Init() {
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
// 检查是否启用了 HTTPDNS 功能(全局用户注册设置中)
|
||||
var hasHTTPDNSFeature = false
|
||||
var defaultHttpdnsClusterId int64 = 0
|
||||
|
||||
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserRegisterConfig})
|
||||
if err == nil && len(resp.ValueJSON) > 0 {
|
||||
var config = userconfigs.DefaultUserRegisterConfig()
|
||||
if json.Unmarshal(resp.ValueJSON, config) == nil {
|
||||
hasHTTPDNSFeature = config.HTTPDNSIsOn
|
||||
if len(config.HTTPDNSDefaultClusterIds) > 0 {
|
||||
defaultHttpdnsClusterId = config.HTTPDNSDefaultClusterIds[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["hasHTTPDNSFeature"] = hasHTTPDNSFeature
|
||||
this.Data["httpdnsClusterId"] = defaultHttpdnsClusterId
|
||||
|
||||
// 加载所有 HTTPDNS 集群
|
||||
var httpdnsClusters = []maps.Map{}
|
||||
if hasHTTPDNSFeature {
|
||||
httpdnsClusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err == nil {
|
||||
for _, c := range httpdnsClusterResp.GetClusters() {
|
||||
httpdnsClusters = append(httpdnsClusters, maps.Map{
|
||||
"id": c.GetId(),
|
||||
"name": c.GetName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Data["httpdnsClusters"] = httpdnsClusters
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -35,8 +68,9 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Tel string
|
||||
Email string
|
||||
Remark string
|
||||
ClusterId int64
|
||||
FeaturesType string
|
||||
ClusterId int64
|
||||
HttpdnsClusterId int64
|
||||
FeaturesType string
|
||||
|
||||
// OTP
|
||||
OtpOn bool
|
||||
@@ -111,6 +145,28 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
|
||||
userId = createResp.UserId
|
||||
|
||||
// 如果表单选择了 HTTPDNS 关联集群,在这里予以保存
|
||||
if params.HttpdnsClusterId > 0 {
|
||||
httpdnsJSON, _ := json.Marshal([]int64{params.HttpdnsClusterId})
|
||||
_, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{
|
||||
UserId: userId,
|
||||
Username: params.Username,
|
||||
Password: params.Pass1,
|
||||
Fullname: params.Fullname,
|
||||
Mobile: params.Mobile,
|
||||
Tel: params.Tel,
|
||||
Email: params.Email,
|
||||
Remark: params.Remark,
|
||||
IsOn: true,
|
||||
NodeClusterId: params.ClusterId,
|
||||
HttpdnsClusterIdsJSON: httpdnsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 功能
|
||||
if teaconst.IsPlus {
|
||||
if params.FeaturesType == "default" {
|
||||
@@ -127,14 +183,41 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var featureCodes = config.Features
|
||||
if config.HTTPDNSIsOn && !lists.ContainsString(featureCodes, userconfigs.UserFeatureCodeHTTPDNS) {
|
||||
featureCodes = append(featureCodes, userconfigs.UserFeatureCodeHTTPDNS)
|
||||
}
|
||||
|
||||
_, err = this.RPC().UserRPC().UpdateUserFeatures(this.AdminContext(), &pb.UpdateUserFeaturesRequest{
|
||||
UserId: userId,
|
||||
FeatureCodes: config.Features,
|
||||
FeatureCodes: featureCodes,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 自动关联默认 HTTPDNS 集群 (如果没有在表单中选择的话)
|
||||
if config.HTTPDNSIsOn && params.HttpdnsClusterId <= 0 && len(config.HTTPDNSDefaultClusterIds) > 0 {
|
||||
httpdnsJSON, _ := json.Marshal(config.HTTPDNSDefaultClusterIds)
|
||||
_, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{
|
||||
UserId: userId,
|
||||
Username: params.Username,
|
||||
Password: params.Pass1,
|
||||
Fullname: params.Fullname,
|
||||
Mobile: params.Mobile,
|
||||
Tel: params.Tel,
|
||||
Email: params.Email,
|
||||
Remark: params.Remark,
|
||||
IsOn: true,
|
||||
NodeClusterId: params.ClusterId,
|
||||
HttpdnsClusterIdsJSON: httpdnsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if params.FeaturesType == "all" {
|
||||
featuresResp, err := this.RPC().UserRPC().FindAllUserFeatureDefinitions(this.AdminContext(), &pb.FindAllUserFeatureDefinitionsRequest{})
|
||||
|
||||
@@ -66,6 +66,28 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
// 当前默认的智能DNS设置
|
||||
this.Data["nsIsVisible"] = plus.AllowComponent(plus.ComponentCodeNS)
|
||||
|
||||
// HTTPDNS 集群列表(用于默认集群多选)
|
||||
httpdnsClusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpdnsClusters := make([]maps.Map, 0, len(httpdnsClusterResp.GetClusters()))
|
||||
for _, cluster := range httpdnsClusterResp.GetClusters() {
|
||||
httpdnsClusters = append(httpdnsClusters, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
})
|
||||
}
|
||||
this.Data["httpdnsClusters"] = httpdnsClusters
|
||||
|
||||
// 当前选中的默认集群(取数组第一个元素)
|
||||
var httpdnsDefaultClusterId int64
|
||||
if len(config.HTTPDNSDefaultClusterIds) > 0 {
|
||||
httpdnsDefaultClusterId = config.HTTPDNSDefaultClusterIds[0]
|
||||
}
|
||||
this.Data["httpdnsDefaultClusterId"] = httpdnsDefaultClusterId
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -102,7 +124,8 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
NsIsOn bool
|
||||
|
||||
HttpdnsIsOn bool
|
||||
HttpdnsIsOn bool
|
||||
HttpdnsDefaultClusterId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
@@ -154,6 +177,11 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
config.NSIsOn = params.NsIsOn
|
||||
config.HTTPDNSIsOn = params.HttpdnsIsOn
|
||||
if params.HttpdnsDefaultClusterId > 0 {
|
||||
config.HTTPDNSDefaultClusterIds = []int64{params.HttpdnsDefaultClusterId}
|
||||
} else {
|
||||
config.HTTPDNSDefaultClusterIds = []int64{}
|
||||
}
|
||||
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
@@ -85,6 +87,46 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
this.Data["clusterId"] = user.NodeCluster.Id
|
||||
}
|
||||
|
||||
// 检查用户是否开通了 HTTPDNS 功能
|
||||
var hasHTTPDNSFeature = false
|
||||
userFeaturesResp, err := this.RPC().UserRPC().FindUserFeatures(this.AdminContext(), &pb.FindUserFeaturesRequest{UserId: params.UserId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, f := range userFeaturesResp.Features {
|
||||
if f.Code == "httpdns" {
|
||||
hasHTTPDNSFeature = true
|
||||
break
|
||||
}
|
||||
}
|
||||
this.Data["hasHTTPDNSFeature"] = hasHTTPDNSFeature
|
||||
|
||||
// 读取用户已关联的 HTTPDNS 集群(取第一个作为下拉默认值)
|
||||
var httpdnsClusterId int64
|
||||
if len(user.HttpdnsClusterIdsJSON) > 0 {
|
||||
var userHTTPDNSClusterIds []int64
|
||||
if json.Unmarshal(user.HttpdnsClusterIdsJSON, &userHTTPDNSClusterIds) == nil && len(userHTTPDNSClusterIds) > 0 {
|
||||
httpdnsClusterId = userHTTPDNSClusterIds[0]
|
||||
}
|
||||
}
|
||||
this.Data["httpdnsClusterId"] = httpdnsClusterId
|
||||
|
||||
// 加载所有 HTTPDNS 集群
|
||||
httpdnsClusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpdnsClusters := make([]maps.Map, 0, len(httpdnsClusterResp.GetClusters()))
|
||||
for _, c := range httpdnsClusterResp.GetClusters() {
|
||||
httpdnsClusters = append(httpdnsClusters, maps.Map{
|
||||
"id": c.GetId(),
|
||||
"name": c.GetName(),
|
||||
})
|
||||
}
|
||||
this.Data["httpdnsClusters"] = httpdnsClusters
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -99,8 +141,9 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
Email string
|
||||
Remark string
|
||||
IsOn bool
|
||||
ClusterId int64
|
||||
BandwidthAlgo string
|
||||
ClusterId int64
|
||||
HttpdnsClusterId int64
|
||||
BandwidthAlgo string
|
||||
|
||||
// OTP
|
||||
OtpOn bool
|
||||
@@ -151,18 +194,25 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
Email("请输入正确的电子邮箱")
|
||||
}
|
||||
|
||||
var httpdnsClusterIds []int64
|
||||
if params.HttpdnsClusterId > 0 {
|
||||
httpdnsClusterIds = []int64{params.HttpdnsClusterId}
|
||||
}
|
||||
httpdnsClusterIdsJSON, _ := json.Marshal(httpdnsClusterIds)
|
||||
|
||||
_, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{
|
||||
UserId: params.UserId,
|
||||
Username: params.Username,
|
||||
Password: params.Pass1,
|
||||
Fullname: params.Fullname,
|
||||
Mobile: params.Mobile,
|
||||
Tel: params.Tel,
|
||||
Email: params.Email,
|
||||
Remark: params.Remark,
|
||||
IsOn: params.IsOn,
|
||||
NodeClusterId: params.ClusterId,
|
||||
BandwidthAlgo: params.BandwidthAlgo,
|
||||
UserId: params.UserId,
|
||||
Username: params.Username,
|
||||
Password: params.Pass1,
|
||||
Fullname: params.Fullname,
|
||||
Mobile: params.Mobile,
|
||||
Tel: params.Tel,
|
||||
Email: params.Email,
|
||||
Remark: params.Remark,
|
||||
IsOn: params.IsOn,
|
||||
NodeClusterId: params.ClusterId,
|
||||
BandwidthAlgo: params.BandwidthAlgo,
|
||||
HttpdnsClusterIdsJSON: httpdnsClusterIdsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -89,6 +91,37 @@ func (this *UserAction) RunGet(params struct {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查全局是否启用了 HTTPDNS 功能
|
||||
var hasHTTPDNSFeature = false
|
||||
sysResp, sysErr := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserRegisterConfig})
|
||||
if sysErr == nil && len(sysResp.ValueJSON) > 0 {
|
||||
var config = userconfigs.DefaultUserRegisterConfig()
|
||||
if json.Unmarshal(sysResp.ValueJSON, config) == nil {
|
||||
hasHTTPDNSFeature = config.HTTPDNSIsOn
|
||||
}
|
||||
}
|
||||
this.Data["hasHTTPDNSFeature"] = hasHTTPDNSFeature
|
||||
|
||||
// 读取用户关联的 HTTPDNS 集群
|
||||
var httpdnsClusterMap maps.Map = nil
|
||||
if hasHTTPDNSFeature && len(user.HttpdnsClusterIdsJSON) > 0 {
|
||||
var httpdnsClusterIds []int64
|
||||
if json.Unmarshal(user.HttpdnsClusterIdsJSON, &httpdnsClusterIds) == nil && len(httpdnsClusterIds) > 0 {
|
||||
httpdnsClusterResp, httpdnsErr := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if httpdnsErr == nil {
|
||||
for _, c := range httpdnsClusterResp.GetClusters() {
|
||||
if c.GetId() == httpdnsClusterIds[0] {
|
||||
httpdnsClusterMap = maps.Map{
|
||||
"id": c.GetId(),
|
||||
"name": c.GetName(),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["user"] = maps.Map{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
@@ -100,6 +133,7 @@ func (this *UserAction) RunGet(params struct {
|
||||
"mobile": user.Mobile,
|
||||
"isOn": user.IsOn,
|
||||
"cluster": clusterMap,
|
||||
"httpdnsCluster": httpdnsClusterMap,
|
||||
"countAccessKeys": countAccessKeys,
|
||||
"isRejected": user.IsRejected,
|
||||
"rejectReason": user.RejectReason,
|
||||
|
||||
@@ -531,6 +531,57 @@ Vue.component("ddos-protection-ports-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
Vue.component("node-cluster-combo-box", {
|
||||
props: ["v-cluster-id"],
|
||||
data: function () {
|
||||
|
||||
@@ -531,6 +531,57 @@ Vue.component("ddos-protection-ports-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
Vue.component("node-cluster-combo-box", {
|
||||
props: ["v-cluster-id"],
|
||||
data: function () {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{$layout}
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/left_menu_with_menu"}
|
||||
|
||||
@@ -67,23 +67,13 @@
|
||||
|
||||
<table class="ui table selectable definition" v-show="activeSection == 'basic'">
|
||||
<tr>
|
||||
<td class="title">主集群</td>
|
||||
<td class="title">所属集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="primaryClusterId" v-model="settings.primaryClusterId">
|
||||
<option :value="0">[不设置]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment httpdns-note">未设置时,按默认主集群处理(当前默认主集群:{{settings.defaultPrimaryClusterName || '-' }})。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">备集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="backupClusterId" v-model="settings.backupClusterId">
|
||||
<option :value="0">[不设置]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment httpdns-note">未设置时,按默认备用集群处理(当前默认备用集群:{{settings.defaultBackupClusterName || '-' }})。</p>
|
||||
<combo-box name="clusterId" placeholder="选择集群" :v-items="clusters"
|
||||
:v-value="settings.selectedClusterId > 0 ? settings.selectedClusterId : ((settings.clusterIds != null && settings.clusterIds.length > 0) ? settings.clusterIds[0] : 0)"
|
||||
width="14em">
|
||||
</combo-box>
|
||||
<p class="comment httpdns-note">选择该应用绑定的集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -106,30 +96,29 @@
|
||||
<td class="title">App ID</td>
|
||||
<td>
|
||||
<code>{{settings.appId}}</code>
|
||||
<a href="" class="httpdns-mini-icon" title="复制 App ID" @click.prevent="copySecret(settings.appId, 'App ID')"><i class="copy outline icon"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" title="复制 App ID"
|
||||
@click.prevent="copySecret(settings.appId, 'App ID')"><i class="copy outline icon"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">主服务域名</td>
|
||||
<td class="title">服务地址</td>
|
||||
<td>
|
||||
<code v-if="settings.primaryServiceDomain && settings.primaryServiceDomain.length > 0">{{settings.primaryServiceDomain}}</code>
|
||||
<span class="grey" v-else>未配置</span>
|
||||
<a v-if="settings.primaryServiceDomain && settings.primaryServiceDomain.length > 0" href="" class="httpdns-mini-icon" title="复制主服务域名" @click.prevent="copySecret(settings.primaryServiceDomain, '主服务域名')"><i class="copy outline icon"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">备用服务域名</td>
|
||||
<td>
|
||||
<code v-if="settings.backupServiceDomain && settings.backupServiceDomain.length > 0">{{settings.backupServiceDomain}}</code>
|
||||
<span class="grey" v-else>未配置</span>
|
||||
<a v-if="settings.backupServiceDomain && settings.backupServiceDomain.length > 0" href="" class="httpdns-mini-icon" title="复制备用服务域名" @click.prevent="copySecret(settings.backupServiceDomain, '备用服务域名')"><i class="copy outline icon"></i></a>
|
||||
<div v-if="settings.serviceAddresses && settings.serviceAddresses.length > 0">
|
||||
<div v-for="sa in settings.serviceAddresses" style="margin-bottom: 0.4em;">
|
||||
<code>{{sa.address}}</code>
|
||||
</div>
|
||||
</div>
|
||||
<span class="grey" v-else>未配置集群</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">请求验签</td>
|
||||
<td>
|
||||
<span :class="settings.signEnabled ? 'httpdns-state-on' : 'httpdns-state-off'">{{settings.signEnabled ? "已开启" : "已关闭"}}</span>
|
||||
<a href="" class="ui mini button basic" style="margin-left: .8em;" @click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
|
||||
<span
|
||||
:class="settings.signEnabled ? 'httpdns-state-on' : 'httpdns-state-off'">{{settings.signEnabled
|
||||
? "已开启" : "已关闭"}}</span>
|
||||
<a href="" class="ui mini button basic" style="margin-left: .8em;"
|
||||
@click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
|
||||
<p class="comment httpdns-note">打开后,服务端会对请求进行签名校验。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -138,9 +127,14 @@
|
||||
<td>
|
||||
<div class="httpdns-secret-line">
|
||||
<code>{{signSecretVisible ? settings.signSecretPlain : settings.signSecretMasked}}</code>
|
||||
<a href="" class="httpdns-mini-icon" @click.prevent="signSecretVisible = !signSecretVisible" :title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon" :class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" title="复制加签 Secret" @click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i class="copy outline icon"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" title="重置加签 Secret" @click.prevent="resetSignSecret"><i class="redo icon"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" @click.prevent="signSecretVisible = !signSecretVisible"
|
||||
:title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon"
|
||||
:class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" title="复制加签 Secret"
|
||||
@click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i
|
||||
class="copy outline icon"></i></a>
|
||||
<a href="" class="httpdns-mini-icon" title="重置加签 Secret" @click.prevent="resetSignSecret"><i
|
||||
class="redo icon"></i></a>
|
||||
</div>
|
||||
<p class="comment httpdns-note">最近更新:{{settings.signSecretUpdatedAt}}</p>
|
||||
<p class="comment httpdns-note" v-if="!settings.signEnabled">请求验签已关闭,当前不使用加签 Secret。</p>
|
||||
@@ -151,4 +145,4 @@
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
{$layout}
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
@@ -12,35 +12,20 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>主集群 *</td>
|
||||
<td>所属集群 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown" name="primaryClusterId" v-model="defaultPrimaryClusterId">
|
||||
<option value="">[请选择集群]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment">主集群用于优先处理应用解析请求。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>备集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown" name="backupClusterId" v-model="defaultBackupClusterId">
|
||||
<option :value="0">[不设置]</option>
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment">当主集群不可用时,可切换到备集群。</p>
|
||||
<combo-box name="clusterId" placeholder="选择集群" :v-items="clusters" width="11em">
|
||||
</combo-box>
|
||||
<p class="comment">选择该应用绑定的集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属用户</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="userId">
|
||||
<option value="0">[不设置]</option>
|
||||
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
|
||||
</select>
|
||||
<user-selector></user-selector>
|
||||
<p class="comment">可以选择当前应用所属的平台用户。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
@@ -18,15 +18,18 @@
|
||||
"西南": ["默认", "重庆", "四川", "贵州", "云南", "西藏"]
|
||||
};
|
||||
|
||||
vm.continents = ["默认", "非洲", "南极洲", "亚洲", "欧洲", "北美洲", "南美洲", "大洋洲"];
|
||||
vm.continents = ["默认", "亚洲", "欧洲", "北美洲", "南美洲", "非洲", "大洋洲"];
|
||||
vm.continentCountries = {
|
||||
"默认": ["默认"],
|
||||
"非洲": ["默认", "南非", "埃及", "尼日利亚", "肯尼亚", "摩洛哥"],
|
||||
"南极洲": ["默认"],
|
||||
"亚洲": ["默认", "中国香港", "中国澳门", "中国台湾", "日本", "韩国", "新加坡", "印度", "泰国", "越南"],
|
||||
"欧洲": ["默认", "德国", "英国", "法国", "荷兰", "西班牙", "意大利", "俄罗斯"],
|
||||
"北美洲": ["默认", "美国", "加拿大", "墨西哥"],
|
||||
"南美洲": ["默认", "巴西", "阿根廷", "智利", "哥伦比亚"],
|
||||
"亚洲": ["默认", "中国香港", "中国澳门", "中国台湾", "日本", "韩国", "新加坡", "印度", "泰国", "越南",
|
||||
"印度尼西亚", "马来西亚", "菲律宾", "柬埔寨", "缅甸", "老挝", "斯里兰卡", "孟加拉国", "巴基斯坦", "尼泊尔",
|
||||
"阿联酋", "沙特阿拉伯", "土耳其", "以色列", "伊朗", "伊拉克", "卡塔尔", "科威特", "蒙古"],
|
||||
"欧洲": ["默认", "德国", "英国", "法国", "荷兰", "西班牙", "意大利", "俄罗斯",
|
||||
"波兰", "瑞典", "瑞士", "挪威", "芬兰", "丹麦", "葡萄牙", "爱尔兰", "比利时", "奥地利",
|
||||
"乌克兰", "捷克", "罗马尼亚", "匈牙利", "希腊"],
|
||||
"北美洲": ["默认", "美国", "加拿大", "墨西哥", "巴拿马", "哥斯达黎加", "古巴"],
|
||||
"南美洲": ["默认", "巴西", "阿根廷", "智利", "哥伦比亚", "秘鲁", "委内瑞拉", "厄瓜多尔"],
|
||||
"非洲": ["默认", "南非", "埃及", "尼日利亚", "肯尼亚", "摩洛哥", "阿尔及利亚", "坦桑尼亚", "埃塞俄比亚", "加纳", "突尼斯"],
|
||||
"大洋洲": ["默认", "澳大利亚", "新西兰"]
|
||||
};
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
this.bindDomain = function () {
|
||||
teaweb.popup("/httpdns/apps/domains/createPopup?appId=" + this.app.id, {
|
||||
height: "24em",
|
||||
width: "46em",
|
||||
height: "12em",
|
||||
width: "36em",
|
||||
title: "添加域名",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
<colgroup>
|
||||
<col style="width:20%;" />
|
||||
<col style="width:12%;" />
|
||||
<col style="width:11%;" />
|
||||
<col style="width:11%;" />
|
||||
<col style="width:16%;" />
|
||||
<col style="width:12%;" />
|
||||
<col style="width:8%;" />
|
||||
<col style="width:6%;" />
|
||||
@@ -39,8 +38,7 @@
|
||||
<tr>
|
||||
<th>应用名称</th>
|
||||
<th>AppID</th>
|
||||
<th>主集群</th>
|
||||
<th>备用集群</th>
|
||||
<th>集群</th>
|
||||
<th>用户</th>
|
||||
<th class="center">绑定域名数</th>
|
||||
<th class="center">状态</th>
|
||||
@@ -61,16 +59,10 @@
|
||||
<copy-icon :text="app.appId"></copy-icon>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="app.primaryCluster != null && app.primaryCluster.id > 0">
|
||||
{{app.primaryCluster.name || ('#' + app.primaryCluster.id)}}
|
||||
<link-icon :href="'/httpdns/clusters/cluster?clusterId=' + app.primaryCluster.id"></link-icon>
|
||||
</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="app.backupCluster != null && app.backupCluster.id > 0">
|
||||
{{app.backupCluster.name || ('#' + app.backupCluster.id)}}
|
||||
<link-icon :href="'/httpdns/clusters/cluster?clusterId=' + app.backupCluster.id"></link-icon>
|
||||
<span v-if="app.clusters != null && app.clusters.length > 0">
|
||||
<span v-for="(cluster, idx) in app.clusters">
|
||||
<a :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id">{{cluster.name || ('#' + cluster.id)}}</a><span v-if="idx < app.clusters.length - 1">、</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="ui margin"></div>
|
||||
|
||||
<h3 class="ui header">HTTPDNS 鍏ㄥ眬绛栫暐</h3>
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">鍏ㄥ眬榛樿瑙f瀽 TTL</td>
|
||||
<td><input type="text" name="defaultTTL" maxlength="8" v-model="policies.defaultTTL" /> 绉?/td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">鍏ㄥ眬榛樿 SNI 绛夌骇</td>
|
||||
<td>
|
||||
<select name="defaultSniPolicy" class="ui dropdown auto-width" v-model="policies.defaultSniPolicy">
|
||||
<option value="level1">level1锛堝浐瀹?SNI锛?/option>
|
||||
<option value="level2">level2锛堥殣鍖?SNI锛?/option>
|
||||
<option value="level3">level3锛圗CH锛?/option>
|
||||
</select>
|
||||
<div class="grey small" v-if="policies.defaultSniPolicy == 'level1'" style="margin-top:.5em;">
|
||||
level1 浠呬娇鐢ㄥ浐瀹?SNI锛屼笉鍚敤 ECS 鍜岃瘉涔︽牎楠岀瓥鐣ャ€? </div>
|
||||
<div class="grey small" v-else-if="policies.defaultSniPolicy == 'level2'" style="margin-top:.5em;">
|
||||
level2 浠呭惎鐢ㄩ殣鍖?SNI锛屼笉瑕佹眰閰嶇疆 ECS 涓庤瘉涔︾瓥鐣ャ€? </div>
|
||||
<div class="grey small" v-else style="margin-top:.5em;">
|
||||
level3 鍚敤 ECH锛屽缓璁悓鏃堕厤缃?ECS 涓庤瘉涔︽牎楠岀瓥鐣ャ€? </div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">鍏ㄥ眬闄嶇骇瓒呮椂</td>
|
||||
<td><input type="text" name="defaultFallbackMs" maxlength="8" v-model="policies.defaultFallbackMs" /> 姣</td>
|
||||
</tr>
|
||||
|
||||
<tr v-show="policies.defaultSniPolicy == 'level3'">
|
||||
<td class="title">全局 ECS 掩码策略</td>
|
||||
<td>
|
||||
<select name="ecsMode" class="ui dropdown auto-width" v-model="policies.ecsMode">
|
||||
<option value="off">鍏抽棴</option>
|
||||
<option value="auto">鑷姩</option>
|
||||
<option value="custom">鑷畾涔?/option>
|
||||
</select>
|
||||
<span v-if="policies.ecsMode == 'custom'">
|
||||
<span class="grey" style="margin-left:.8em;">IPv4 /</span>
|
||||
<input type="text" name="ecsIPv4Prefix" maxlength="3" v-model="policies.ecsIPv4Prefix" style="width:4.5em;" />
|
||||
<span class="grey" style="margin-left:.8em;">IPv6 /</span>
|
||||
<input type="text" name="ecsIPv6Prefix" maxlength="3" v-model="policies.ecsIPv6Prefix" style="width:4.5em;" />
|
||||
</span>
|
||||
<span v-else class="grey" style="margin-left:.8em;">仅在“自定义”模式下配置掩码。</span>
|
||||
|
||||
<input type="hidden" name="ecsIPv4Prefix" v-model="policies.ecsIPv4Prefix" v-if="policies.ecsMode != 'custom'" />
|
||||
<input type="hidden" name="ecsIPv6Prefix" v-model="policies.ecsIPv6Prefix" v-if="policies.ecsMode != 'custom'" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-show="policies.defaultSniPolicy == 'level3'">
|
||||
<td class="title">鍏ㄥ眬璇佷功鏍¢獙绛栫暐</td>
|
||||
<td>
|
||||
<span class="grey">证书指纹校验(Pinning)</span>
|
||||
<select name="pinningMode" class="ui dropdown auto-width" v-model="policies.pinningMode" style="margin-left:.4em;">
|
||||
<option value="off">鍏抽棴</option>
|
||||
<option value="report">瑙傚療妯″紡</option>
|
||||
<option value="enforce">寮哄埗鏍¢獙</option>
|
||||
</select>
|
||||
<span class="grey" style="margin-left:1.2em;">证书 SAN 域名校验</span>
|
||||
<select name="sanMode" class="ui dropdown auto-width" v-model="policies.sanMode" style="margin-left:.4em;">
|
||||
<option value="off">鍏抽棴</option>
|
||||
<option value="report">瑙傚療妯″紡</option>
|
||||
<option value="strict">涓ユ牸鏍¢獙</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功");
|
||||
|
||||
this.$delay(function () {
|
||||
this.$watch("policies.defaultSniPolicy", function (level) {
|
||||
if (level == "level1" || level == "level2") {
|
||||
this.policies.ecsMode = "off";
|
||||
this.policies.pinningMode = "off";
|
||||
this.policies.sanMode = "off";
|
||||
return;
|
||||
}
|
||||
|
||||
if (level == "level3") {
|
||||
if (this.policies.ecsMode == "off") {
|
||||
this.policies.ecsMode = "auto";
|
||||
}
|
||||
if (this.policies.pinningMode == "off") {
|
||||
this.policies.pinningMode = "report";
|
||||
}
|
||||
if (this.policies.sanMode == "off") {
|
||||
this.policies.sanMode = "strict";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.$watch("policies.ecsMode", function (mode) {
|
||||
if (this.policies.defaultSniPolicy != "level3") {
|
||||
return;
|
||||
}
|
||||
if (mode == "custom") {
|
||||
if (!this.policies.ecsIPv4Prefix || this.policies.ecsIPv4Prefix <= 0) {
|
||||
this.policies.ecsIPv4Prefix = 24;
|
||||
}
|
||||
if (!this.policies.ecsIPv6Prefix || this.policies.ecsIPv6Prefix <= 0) {
|
||||
this.policies.ecsIPv6Prefix = 56;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,16 +2,17 @@
|
||||
{$template "node_menu"}
|
||||
{$template "/code_editor"}
|
||||
|
||||
<!-- 已安装 -->
|
||||
<div v-if="node.isInstalled">
|
||||
<div class="ui message green">当前节点为已安装状态。</div>
|
||||
<a href="" @click.prevent="updateNodeIsInstalled(false)">[修改为未安装]</a>
|
||||
<a href="" @click.prevent="updateNodeIsInstalled(false)">[重新安装]</a>
|
||||
|
||||
<h4>配置文件</h4>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">配置文件</td>
|
||||
<td>
|
||||
configs/api_httpdns.yaml
|
||||
configs/api_httpdns.yaml
|
||||
<download-link :v-element="'rpc-code'" :v-file="'api_httpdns.yaml'">[下载]</download-link>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -19,27 +20,45 @@
|
||||
<td>配置内容</td>
|
||||
<td>
|
||||
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
|
||||
nodeId: "{{node.uniqueId}}"
|
||||
secret: "{{node.secret}}"</source-code-box>
|
||||
nodeId: "{{node.uniqueId}}"
|
||||
secret: "{{node.secret}}"</source-code-box>
|
||||
<p class="comment">每个节点的配置文件内容均不相同,不能混用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">安装目录</td>
|
||||
<td>
|
||||
<div v-if="node.installDir.length == 0">使用集群设置<span v-if="node.cluster != null && node.cluster.installDir.length > 0">({{node.cluster.installDir}})</span></div>
|
||||
<div v-if="node.installDir.length == 0">使用集群设置<span
|
||||
v-if="node.cluster != null && node.cluster.installDir.length > 0">({{node.cluster.installDir}})</span>
|
||||
</div>
|
||||
<span v-else>{{node.installDir}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 未安装 -->
|
||||
<div v-if="!node.isInstalled">
|
||||
<h4>通过控制台标记安装</h4>
|
||||
<div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)" class="ui segment installing-box">
|
||||
<h4>方法1:通过SSH自动安装</h4>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">SSH地址</td>
|
||||
<td>
|
||||
<span v-if="sshAddr.length > 0">{{sshAddr}} <a href=""
|
||||
@click.prevent="showSSHPopup(nodeId)">[修改]</a></span>
|
||||
<span v-else><span class="red">尚未设置</span> <a href=""
|
||||
@click.prevent="showSSHPopup(nodeId)">[设置]</a></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)"
|
||||
class="ui segment installing-box">
|
||||
<div v-if="installStatus.isRunning" class="blue">安装中...</div>
|
||||
<div v-if="installStatus.isFinished">
|
||||
<span v-if="installStatus.isOk" class="green">安装成功</span>
|
||||
<span v-else class="red">安装失败:{{installStatus.error}}</span>
|
||||
<span v-if="installStatus.isOk" class="green">已安装成功</span>
|
||||
<span v-if="!installStatus.isOk" class="red">安装过程中发生错误:{{installStatus.error}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="installStatus != null && installStatus.isFinished">
|
||||
@@ -49,12 +68,12 @@
|
||||
<button class="ui button small primary" type="button" @click.prevent="install()">开始安装</button>
|
||||
</div>
|
||||
|
||||
<h4>配置文件</h4>
|
||||
<h4>方法2:手动安装</h4>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">配置文件</td>
|
||||
<td>
|
||||
configs/api_httpdns.yaml
|
||||
configs/api_httpdns.yaml
|
||||
<download-link :v-element="'rpc-code'" :v-file="'api_httpdns.yaml'">[下载]</download-link>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -62,18 +81,21 @@
|
||||
<td>配置内容</td>
|
||||
<td>
|
||||
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
|
||||
nodeId: "{{node.uniqueId}}"
|
||||
secret: "{{node.secret}}"</source-code-box>
|
||||
nodeId: "{{node.uniqueId}}"
|
||||
secret: "{{node.secret}}"</source-code-box>
|
||||
<p class="comment">每个节点的配置文件内容均不相同,不能混用。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">安装目录</td>
|
||||
<td>
|
||||
<div v-if="node.installDir.length == 0">使用集群设置<span v-if="node.cluster != null && node.cluster.installDir.length > 0">({{node.cluster.installDir}})</span></div>
|
||||
<div v-if="node.installDir.length == 0">使用集群设置<span
|
||||
v-if="node.cluster != null && node.cluster.installDir.length > 0">({{node.cluster.installDir}})</span>
|
||||
</div>
|
||||
<span v-else>{{node.installDir}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="" @click.prevent="updateNodeIsInstalled(true)">[修改为已安装]</a>
|
||||
</div>
|
||||
<a href="" @click.prevent="updateNodeIsInstalled(true)">[修改为已安装状态]</a>
|
||||
</div>
|
||||
@@ -28,6 +28,7 @@ Tea.context(function () {
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新状态
|
||||
this.reloadStatus = function (nodeId) {
|
||||
let that = this
|
||||
|
||||
@@ -44,10 +45,50 @@ Tea.context(function () {
|
||||
}
|
||||
|
||||
let installStatus = this.installStatus || {}
|
||||
let errorCode = installStatus.errorCode || ""
|
||||
if (errorCode.length > 0) {
|
||||
let errMsg = installStatus.error || ""
|
||||
|
||||
if (installStatus.errorCode != null && installStatus.errorCode.length > 0) {
|
||||
isInstalling = false
|
||||
teaweb.warn("安装失败:" + (installStatus.error || "未知错误"))
|
||||
}
|
||||
|
||||
switch (installStatus.errorCode) {
|
||||
case "EMPTY_LOGIN":
|
||||
case "EMPTY_SSH_HOST":
|
||||
case "EMPTY_SSH_PORT":
|
||||
case "EMPTY_GRANT":
|
||||
teaweb.warn("需要填写SSH登录信息", function () {
|
||||
teaweb.popup("/httpdns/clusters/updateNodeSSH?nodeId=" + nodeId, {
|
||||
height: "20em",
|
||||
callback: function () {
|
||||
that.install()
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
case "SSH_LOGIN_FAILED":
|
||||
teaweb.warn("SSH登录失败,请检查设置", function () {
|
||||
teaweb.popup("/httpdns/clusters/updateNodeSSH?nodeId=" + nodeId, {
|
||||
height: "20em",
|
||||
callback: function () {
|
||||
that.install()
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
case "CREATE_ROOT_DIRECTORY_FAILED":
|
||||
teaweb.warn("创建根目录失败,请检查目录权限或者手工创建:" + errMsg)
|
||||
return
|
||||
case "INSTALL_HELPER_FAILED":
|
||||
teaweb.warn("安装助手失败:" + errMsg)
|
||||
return
|
||||
case "TEST_FAILED":
|
||||
teaweb.warn("环境测试失败:" + errMsg)
|
||||
return
|
||||
case "RPC_TEST_FAILED":
|
||||
teaweb.confirm("html:要安装的节点到API服务之间的RPC通讯测试失败,具体错误:" + errMsg + ",<br/>现在修改API信息?", function () {
|
||||
window.location = "/settings/api"
|
||||
})
|
||||
return
|
||||
}
|
||||
})
|
||||
.done(function () {
|
||||
@@ -59,7 +100,7 @@ Tea.context(function () {
|
||||
|
||||
this.showSSHPopup = function (nodeId) {
|
||||
teaweb.popup("/httpdns/clusters/updateNodeSSH?nodeId=" + nodeId, {
|
||||
height: "30em",
|
||||
height: "20em",
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
<h3>修改节点</h3>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="nodeId" :value="node.id" />
|
||||
<input type="hidden" name="loginId" :value="loginId" />
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">节点名称 *</td>
|
||||
|
||||
@@ -65,40 +65,12 @@
|
||||
<td>启用当前集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn" @change="syncDefaultCluster" />
|
||||
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn" />
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">取消启用后,该集群不会参与 HTTPDNS 服务。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="settings.defaultClusterEnabled" @change="syncDefaultClusterSelection" />
|
||||
<label>设为默认集群</label>
|
||||
</div>
|
||||
<div class="ui form" style="margin-top: .8em;" v-if="settings.defaultClusterEnabled">
|
||||
<div class="grouped fields" style="margin:0;">
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" v-model="settings.defaultClusterRole" value="primary" @change="syncDefaultClusterSelection" />
|
||||
<label>主集群</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" v-model="settings.defaultClusterRole" value="backup" @change="syncDefaultClusterSelection" />
|
||||
<label>备用集群</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="isDefaultCluster" value="1" v-if="settings.defaultClusterEnabled && settings.defaultClusterRole == 'primary'" />
|
||||
<input type="hidden" name="isDefaultBackupCluster" value="1" v-if="settings.defaultClusterEnabled && settings.defaultClusterRole == 'backup'" />
|
||||
<p class="comment">同一时刻最多 1 个默认集群角色,新设置会自动取消旧设置。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="ui table selectable definition" v-show="activeSection == 'tls'">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Tea.context(function () {
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
|
||||
this.activeSection = this.activeSection || "basic"
|
||||
@@ -7,39 +7,4 @@
|
||||
if (!this.settings) {
|
||||
this.settings = {}
|
||||
}
|
||||
|
||||
// 兼容旧字段,转换成统一“默认集群 + 角色”表现
|
||||
let isDefaultPrimary = !!this.settings.isDefaultCluster
|
||||
let isDefaultBackup = !!this.settings.isDefaultBackupCluster
|
||||
this.settings.defaultClusterEnabled = isDefaultPrimary || isDefaultBackup
|
||||
this.settings.defaultClusterRole = isDefaultBackup ? "backup" : "primary"
|
||||
|
||||
this.syncDefaultCluster = function () {
|
||||
if (!this.settings.isOn) {
|
||||
this.settings.defaultClusterEnabled = false
|
||||
this.settings.defaultClusterRole = "primary"
|
||||
this.settings.isDefaultCluster = false
|
||||
this.settings.isDefaultBackupCluster = false
|
||||
return
|
||||
}
|
||||
this.syncDefaultClusterSelection()
|
||||
}
|
||||
|
||||
this.syncDefaultClusterSelection = function () {
|
||||
if (!this.settings.defaultClusterEnabled) {
|
||||
this.settings.isDefaultCluster = false
|
||||
this.settings.isDefaultBackupCluster = false
|
||||
return
|
||||
}
|
||||
|
||||
if (this.settings.defaultClusterRole === "backup") {
|
||||
this.settings.isDefaultCluster = false
|
||||
this.settings.isDefaultBackupCluster = true
|
||||
} else {
|
||||
this.settings.defaultClusterRole = "primary"
|
||||
this.settings.isDefaultCluster = true
|
||||
this.settings.isDefaultBackupCluster = false
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<td>默认解析 TTL</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="cacheTtl" maxlength="5" value="30" style="width: 6em" />
|
||||
<input type="text" name="cacheTtl" maxlength="5" value="60" style="width: 6em" />
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">SDK 通过 HTTPDNS 解析域名时返回的默认 TTL。</p>
|
||||
@@ -51,38 +51,12 @@
|
||||
<td>启用当前集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" v-model="isOn" @change="syncDefaultClusterEnabled" checked />
|
||||
<input type="checkbox" name="isOn" value="1" v-model="isOn" @change="syncDefaultClusterEnabled"
|
||||
checked />
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" value="1" v-model="defaultClusterEnabled" @change="syncDefaultClusterEnabled" />
|
||||
<label>设为默认集群</label>
|
||||
</div>
|
||||
<div class="ui form" style="margin-top: .8em;" v-if="defaultClusterEnabled">
|
||||
<div class="grouped fields" style="margin:0;">
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" v-model="defaultClusterRole" value="primary" />
|
||||
<label>主集群</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" v-model="defaultClusterRole" value="backup" />
|
||||
<label>备用集群</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="isDefaultPrimary" value="1" v-if="defaultClusterEnabled && defaultClusterRole == 'primary'" />
|
||||
<input type="hidden" name="isDefaultBackup" value="1" v-if="defaultClusterEnabled && defaultClusterRole == 'backup'" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
@@ -1,14 +1,5 @@
|
||||
Tea.context(function () {
|
||||
Tea.context(function () {
|
||||
this.isOn = true
|
||||
this.defaultClusterEnabled = false
|
||||
this.defaultClusterRole = "primary"
|
||||
|
||||
this.syncDefaultClusterEnabled = function () {
|
||||
if (!this.isOn) {
|
||||
this.defaultClusterEnabled = false
|
||||
this.defaultClusterRole = "primary"
|
||||
}
|
||||
}
|
||||
|
||||
this.success = function (resp) {
|
||||
let clusterId = 0
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>集群名称</th>
|
||||
<th>服务域名</th>
|
||||
<th>API服务地址</th>
|
||||
<th class="center width10">节点数</th>
|
||||
<th class="center width10">在线节点数</th>
|
||||
<th class="width5">状态</th>
|
||||
@@ -43,7 +43,7 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<code>{{cluster.gatewayDomain}}</code>
|
||||
<code>{{cluster.apiAddress}}</code>
|
||||
</td>
|
||||
<td class="center">
|
||||
<a :href="'/httpdns/clusters/cluster?clusterId=' + cluster.id" v-if="cluster.countAllNodes > 0">
|
||||
@@ -67,5 +67,4 @@
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -3,7 +3,6 @@
|
||||
<h3>修改节点"{{node.name}}"的SSH登录信息</h3>
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="nodeId" :value="node.id" />
|
||||
<input type="hidden" name="loginId" :value="loginId" />
|
||||
<table class="ui table definition">
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<!-- 左侧菜单由 Go Backend 自动生成注入,此处加首行子菜单使其符合标准平台样式 -->
|
||||
<first-menu>
|
||||
<menu-item href="/httpdns/guide" code="index">SDK接入向导</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,146 +0,0 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<style>
|
||||
.httpdns-mini-action {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-left: .55em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.httpdns-mini-action:hover {
|
||||
color: #1e70bf;
|
||||
}
|
||||
.httpdns-mini-action .icon {
|
||||
margin-right: 0 !important;
|
||||
font-size: .92em !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<div class="ui form" style="margin-bottom: 1.5em;">
|
||||
<div class="ui fields inline">
|
||||
<div class="field">
|
||||
<label style="font-weight: 600;">鐩爣搴旂敤</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select class="ui dropdown auto-width" v-model="selectedAppId" @change="onAppChange">
|
||||
<option value="">[璇烽€夋嫨搴旂敤]</option>
|
||||
<option v-for="app in apps" :value="app.appId">{{app.name}} ({{app.appId}})</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="selectedAppId.length == 0" class="ui segment center aligned" style="padding:4em 1em; color:#999;">
|
||||
<i class="icon cogs" style="font-size:3em;"></i>
|
||||
<p style="margin-top:1em; font-size:1.1em;">璇峰厛閫夋嫨搴旂敤锛岀劧鍚庢煡鐪嬮厤缃苟瀹屾垚 SDK 鎺ュ叆銆?/p>
|
||||
</div>
|
||||
|
||||
<div v-show="selectedAppId.length > 0">
|
||||
<div class="ui two steps mini" style="margin-bottom: 0;">
|
||||
<a class="step" :class="{active: currentStep == 1}" @click.prevent="currentStep=1">
|
||||
<i class="icon file alternate outline"></i>
|
||||
<div class="content">
|
||||
<div class="title">01 鏌ョ湅閰嶇疆</div>
|
||||
<div class="description">鏌ョ湅 SDK 鍒濆鍖栧弬鏁?/div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="step" :class="{active: currentStep == 2}" @click.prevent="currentStep=2">
|
||||
<i class="icon code"></i>
|
||||
<div class="content">
|
||||
<div class="title">02 寮€鍙戞帴鍏?/div>
|
||||
<div class="description">涓嬭浇 SDK 骞堕泦鎴愰」鐩?/div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ui segment" v-show="currentStep == 1"
|
||||
style="border-top:none; margin-top:0; border-top-left-radius:0; border-top-right-radius:0;">
|
||||
<h4 class="ui header">鏌ョ湅閰嶇疆</h4>
|
||||
<table class="ui table definition">
|
||||
<tr>
|
||||
<td class="four wide">App ID</td>
|
||||
<td>
|
||||
<code>{{selectedApp.appId}}</code>
|
||||
<a href="" class="httpdns-mini-action" title="澶嶅埗 App ID" @click.prevent="copyText(selectedApp.appId, 'App ID')"><i class="copy outline icon"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>搴旂敤鍚嶇О</td>
|
||||
<td><strong>{{selectedApp.name}}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>闆嗙兢鏈嶅姟鍦板潃</td>
|
||||
<td>
|
||||
<code>{{selectedApp.gatewayDomainDisplay}}</code>
|
||||
<a href="" class="httpdns-mini-action" title="澶嶅埗鏈嶅姟鍦板潃" @click.prevent="copyText(selectedApp.gatewayDomainDisplay, '鏈嶅姟鍦板潃')"><i class="copy outline icon"></i></a>
|
||||
<p class="comment" v-if="selectedApp.gatewayDomains && selectedApp.gatewayDomains.length > 1">宸插惎鐢ㄤ富澶囷細绗竴涓负涓婚泦缇わ紝鍚庣画涓哄闆嗙兢銆?/p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>鍔犵 Secret</td>
|
||||
<td>
|
||||
<code>{{signSecretVisible ? selectedApp.signSecret : selectedApp.signSecretMasked}}</code>
|
||||
<a href="" class="httpdns-mini-action" @click.prevent="signSecretVisible = !signSecretVisible" :title="signSecretVisible ? '闅愯棌鏄庢枃' : '鏌ョ湅鏄庢枃'"><i class="icon" :class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
|
||||
<a href="" class="httpdns-mini-action" title="澶嶅埗鍔犵 Secret" @click.prevent="copyText(selectedApp.signSecret, '鍔犵 Secret')"><i class="copy outline icon"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>璇锋眰楠岀</td>
|
||||
<td>
|
||||
<span class="green" v-if="selectedApp.signEnabled">宸插紑鍚?/span>
|
||||
<span class="grey" v-else>宸插叧闂?/span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a href="" class="ui button small" @click.prevent="currentStep=2">
|
||||
涓嬩竴姝?<i class="icon arrow right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ui segment" v-show="currentStep == 2"
|
||||
style="border-top:none; margin-top:0; border-top-left-radius:0; border-top-right-radius:0;">
|
||||
<h4 class="ui header">寮€鍙戞帴鍏?/h4>
|
||||
<p class="grey">閫夋嫨瀵瑰簲骞冲彴 SDK 涓嬭浇骞舵煡闃呴泦鎴愭枃妗c€?/p>
|
||||
|
||||
<div class="ui three cards" style="margin-top: 1.5em;">
|
||||
<div class="card">
|
||||
<div class="content">
|
||||
<div class="header"><i class="icon android green"></i> Android SDK</div>
|
||||
<div class="description" style="margin-top:.5em;">
|
||||
閫傜敤浜?Android 5.0+ 鐨勫師鐢?SDK锛屾敮鎸?Java / Kotlin銆? </div>
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<a class="ui button primary mini" href="https://github.com/aliyun/alibabacloud-httpdns-android-sdk" target="_blank" rel="noopener noreferrer"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button basic mini" href="https://github.com/aliyun/alibabacloud-httpdns-android-sdk/blob/master/README.md" target="_blank" rel="noopener noreferrer"><i class="icon book"></i> 集成帮助文档</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="content">
|
||||
<div class="header"><i class="icon apple grey"></i> iOS SDK</div>
|
||||
<div class="description" style="margin-top:.5em;">
|
||||
閫傜敤浜?iOS 12+ 鐨勫師鐢?SDK锛屾敮鎸?Swift / Objective-C銆? </div>
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<a class="ui button primary mini" href="https://github.com/aliyun/alibabacloud-httpdns-ios-sdk" target="_blank" rel="noopener noreferrer"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button basic mini" href="https://github.com/aliyun/alibabacloud-httpdns-ios-sdk/blob/master/README.md" target="_blank" rel="noopener noreferrer"><i class="icon book"></i> 集成帮助文档</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="content">
|
||||
<div class="header"><i class="icon mobile alternate blue"></i> Flutter SDK</div>
|
||||
<div class="description" style="margin-top:.5em;">
|
||||
璺ㄥ钩鍙?Flutter 鎻掍欢锛屽悓鏃舵敮鎸?Android 鍜?iOS銆? </div>
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<a class="ui button primary mini" href="https://pub.dev/packages/aliyun_httpdns" target="_blank" rel="noopener noreferrer"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button basic mini" href="https://pub.dev/packages/aliyun_httpdns" target="_blank" rel="noopener noreferrer"><i class="icon book"></i> 集成帮助文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.selectedAppId = ""
|
||||
this.selectedApp = {}
|
||||
this.currentStep = 1
|
||||
this.signSecretVisible = false
|
||||
|
||||
if (typeof this.apps == "undefined") {
|
||||
this.apps = []
|
||||
}
|
||||
|
||||
this.onAppChange = function () {
|
||||
if (this.selectedAppId.length == 0) {
|
||||
this.selectedApp = {}
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.apps.length; i++) {
|
||||
if (this.apps[i].appId == this.selectedAppId) {
|
||||
this.selectedApp = this.apps[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!this.selectedApp.gatewayDomain || this.selectedApp.gatewayDomain.length == 0) {
|
||||
this.selectedApp.gatewayDomain = "gw.httpdns.example.com"
|
||||
}
|
||||
if (!this.selectedApp.gatewayDomainDisplay || this.selectedApp.gatewayDomainDisplay.length == 0) {
|
||||
this.selectedApp.gatewayDomainDisplay = this.selectedApp.gatewayDomain
|
||||
}
|
||||
|
||||
this.signSecretVisible = false
|
||||
this.currentStep = 1
|
||||
}
|
||||
|
||||
this.copyText = function (text, name) {
|
||||
if (typeof text != "string" || text.length == 0) {
|
||||
teaweb.warn("没有可复制的内容")
|
||||
return
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
teaweb.success(name + "已复制")
|
||||
}).catch(function () {
|
||||
this.copyByTextarea(text, name)
|
||||
}.bind(this))
|
||||
return
|
||||
}
|
||||
|
||||
this.copyByTextarea(text, name)
|
||||
}
|
||||
|
||||
this.copyByTextarea = function (text, name) {
|
||||
var input = document.createElement("textarea")
|
||||
input.value = text
|
||||
input.setAttribute("readonly", "readonly")
|
||||
input.style.position = "fixed"
|
||||
input.style.left = "-10000px"
|
||||
input.style.top = "-10000px"
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
|
||||
var ok = false
|
||||
try {
|
||||
ok = document.execCommand("copy")
|
||||
} catch (e) {
|
||||
ok = false
|
||||
}
|
||||
document.body.removeChild(input)
|
||||
|
||||
if (ok) {
|
||||
teaweb.success(name + "已复制")
|
||||
} else {
|
||||
teaweb.warn("复制失败,请手动复制")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,3 +0,0 @@
|
||||
<first-menu>
|
||||
<menu-item href="/httpdns/policies" code="index">全局策略</menu-item>
|
||||
</first-menu>
|
||||
@@ -1,114 +0,0 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<style>
|
||||
.httpdns-policy-grid {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.httpdns-policy-grid .left-nav-column {
|
||||
max-width: 220px;
|
||||
}
|
||||
.httpdns-policy-grid .right-form-column {
|
||||
padding-left: .3em !important;
|
||||
}
|
||||
.httpdns-policy-menu .item {
|
||||
padding-top: .8em !important;
|
||||
padding-bottom: .8em !important;
|
||||
}
|
||||
.httpdns-policy-note.comment {
|
||||
color: #8f9aa6 !important;
|
||||
font-size: 12px;
|
||||
margin-top: .45em !important;
|
||||
}
|
||||
.httpdns-inline-field {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .45em;
|
||||
}
|
||||
.httpdns-inline-field input {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.httpdns-inline-field .httpdns-unit {
|
||||
color: #333;
|
||||
min-width: 2em;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
.httpdns-inline-hint {
|
||||
color: #8f9aa6;
|
||||
margin-left: .35em;
|
||||
}
|
||||
.httpdns-unit-input {
|
||||
display: inline-flex !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.httpdns-unit-input .label {
|
||||
min-width: 3em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="ui margin"></div>
|
||||
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<div class="ui stackable grid httpdns-policy-grid">
|
||||
<div class="three wide computer four wide tablet sixteen wide mobile column left-nav-column">
|
||||
<div class="ui fluid vertical pointing menu httpdns-policy-menu">
|
||||
<a href="" class="item" :class="{active: activeSection == 'user'}" @click.prevent="activeSection='user'">用户设置</a>
|
||||
<a href="" class="item" :class="{active: activeSection == 'basic'}" @click.prevent="activeSection='basic'">基础默认</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thirteen wide computer twelve wide tablet sixteen wide mobile column right-form-column">
|
||||
<table class="ui table definition selectable" v-show="activeSection == 'user'">
|
||||
<tr>
|
||||
<td class="title">默认部署集群</td>
|
||||
<td>
|
||||
<select name="defaultClusterId" class="ui dropdown auto-width" v-model="policies.defaultClusterId">
|
||||
<option v-for="cluster in availableClusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment httpdns-policy-note">用户新建应用时默认落到此集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="ui table definition selectable" v-show="activeSection == 'basic'">
|
||||
<tr>
|
||||
<td class="title">SNI 防护配置</td>
|
||||
<td>
|
||||
<span class="green">隐匿 SNI</span>
|
||||
<p class="comment httpdns-policy-note">当前统一采用隐匿 SNI 策略,避免在握手阶段暴露业务域名。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">全局默认解析 TTL</td>
|
||||
<td>
|
||||
<div class="httpdns-inline-field">
|
||||
<div class="ui right labeled input httpdns-unit-input" style="width:11em;">
|
||||
<input type="text" name="defaultTTL" maxlength="8" v-model="policies.defaultTTL" />
|
||||
<div class="ui basic label">秒</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment httpdns-policy-note">建议 30~120 秒,兼顾缓存命中与切换速度。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">全局降级超时</td>
|
||||
<td>
|
||||
<div class="httpdns-inline-field">
|
||||
<div class="ui right labeled input httpdns-unit-input" style="width:11em;">
|
||||
<input type="text" name="defaultFallbackMs" maxlength="8" v-model="policies.defaultFallbackMs" />
|
||||
<div class="ui basic label">毫秒</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="comment httpdns-policy-note">超时后可走降级解析流程,建议 200~800ms。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,19 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功");
|
||||
this.activeSection = "user";
|
||||
|
||||
if (!Array.isArray(this.availableClusters)) {
|
||||
this.availableClusters = [];
|
||||
}
|
||||
if (!this.policies.defaultClusterId || this.policies.defaultClusterId <= 0) {
|
||||
if (this.availableClusters.length > 0) {
|
||||
this.policies.defaultClusterId = this.availableClusters[0].id;
|
||||
}
|
||||
}
|
||||
if (typeof this.policies.defaultTTL == "undefined" || this.policies.defaultTTL <= 0) {
|
||||
this.policies.defaultTTL = 30;
|
||||
}
|
||||
if (typeof this.policies.defaultFallbackMs == "undefined" || this.policies.defaultFallbackMs <= 0) {
|
||||
this.policies.defaultFallbackMs = 300;
|
||||
}
|
||||
});
|
||||
@@ -17,10 +17,10 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>所属集群 *</label>
|
||||
<label>API服务地址 *</label>
|
||||
<select class="ui dropdown" name="clusterId" v-model="request.clusterId">
|
||||
<option value="">[请选择所属集群]</option>
|
||||
<option v-for="cluster in currentClusters" :value="String(cluster.id)">{{cluster.name}}</option>
|
||||
<option value="">[请选择API服务地址]</option>
|
||||
<option v-for="cluster in currentClusters" :value="String(cluster.id)">{{cluster.displayName || cluster.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
||||
@@ -58,8 +58,7 @@ Tea.context(function () {
|
||||
this.request.domain = ""
|
||||
}
|
||||
|
||||
let primaryClusterId = (typeof selectedApp.primaryClusterId !== "undefined" && selectedApp.primaryClusterId !== null) ? Number(selectedApp.primaryClusterId) : 0
|
||||
let backupClusterId = (typeof selectedApp.backupClusterId !== "undefined" && selectedApp.backupClusterId !== null) ? Number(selectedApp.backupClusterId) : 0
|
||||
let appClusterIds = Array.isArray(selectedApp.clusterIds) ? selectedApp.clusterIds.map(Number) : []
|
||||
|
||||
let allowed = []
|
||||
for (let i = 0; i < this.clusters.length; i++) {
|
||||
@@ -68,7 +67,7 @@ Tea.context(function () {
|
||||
if (clusterId <= 0) {
|
||||
continue
|
||||
}
|
||||
if (clusterId === primaryClusterId || clusterId === backupClusterId) {
|
||||
if (appClusterIds.indexOf(clusterId) >= 0) {
|
||||
allowed.push(cluster)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +119,7 @@ Tea.context(function () {
|
||||
return
|
||||
}
|
||||
if (this.request.clusterId.length === 0) {
|
||||
teaweb.warn("当前应用未绑定可用集群,请先在应用设置中配置主集群/备集群")
|
||||
teaweb.warn("当前应用未绑定可用集群,请先在应用设置中配置所属集群")
|
||||
return
|
||||
}
|
||||
if (this.request.domain.length === 0) {
|
||||
|
||||
@@ -3,41 +3,51 @@
|
||||
<h3>创建用户</h3>
|
||||
|
||||
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<csrf-token></csrf-token>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">用户名 *</td>
|
||||
<td>
|
||||
<input type="text" name="username" maxlength="100" ref="focus"/>
|
||||
<input type="text" name="username" maxlength="100" ref="focus" />
|
||||
<p class="comment">用户名只能是英文、数字、下划线的组合。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>密码 *</td>
|
||||
<td>
|
||||
<input type="password" name="pass1" maxlength="100"/>
|
||||
<input type="password" name="pass1" maxlength="100" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>确认密码 *</td>
|
||||
<td>
|
||||
<input type="password" name="pass2" maxlength="100"/>
|
||||
<input type="password" name="pass2" maxlength="100" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>全名 *</td>
|
||||
<td>
|
||||
<input type="text" name="fullname" maxlength="100"/>
|
||||
<input type="text" name="fullname" maxlength="100" />
|
||||
<p class="comment">用户姓名或者公司名称等等。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>关联集群 *</td>
|
||||
<td>CDN关联集群 *</td>
|
||||
<td>
|
||||
<cluster-selector></cluster-selector>
|
||||
<p class="comment">用户发布的网站会自动部署到此集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="hasHTTPDNSFeature">
|
||||
<td>HTTPDNS关联集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="httpdnsClusterId" v-model="httpdnsClusterId">
|
||||
<option value="0">[未选择]</option>
|
||||
<option v-for="cluster in httpdnsClusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment">用户发布的 HTTPDNS 应用会自动关联到此集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="teaIsPlus">
|
||||
<td>开通功能</td>
|
||||
<td>
|
||||
@@ -54,19 +64,19 @@
|
||||
<tr>
|
||||
<td>手机号</td>
|
||||
<td>
|
||||
<input type="text" name="mobile" maxlength="11"/>
|
||||
<input type="text" name="mobile" maxlength="11" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>联系电话</td>
|
||||
<td>
|
||||
<input type="text" name="tel" maxlength="100"/>
|
||||
<input type="text" name="tel" maxlength="100" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>电子邮箱</td>
|
||||
<td>
|
||||
<input type="text" name="email" maxlength="100"/>
|
||||
<input type="text" name="email" maxlength="100" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -83,6 +93,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
@@ -43,7 +43,9 @@
|
||||
<td class="title">检查客户端区域</td>
|
||||
<td>
|
||||
<checkbox name="checkClientRegion" v-model="config.checkClientRegion"></checkbox>
|
||||
<p class="comment">选中后,表示每次用户访问时都检查客户端所在地理区域是否和登录时一致,以提升安全性;如果当前系统下游有反向代理设置,请在<a href="/settings/user-ui#client-ip-header-names" target="_blank">[用户界面设置]</a>中设置“自定义客户端IP报头”选项。</p>
|
||||
<p class="comment">选中后,表示每次用户访问时都检查客户端所在地理区域是否和登录时一致,以提升安全性;如果当前系统下游有反向代理设置,请在<a
|
||||
href="/settings/user-ui#client-ip-header-names" target="_blank">[用户界面设置]</a>中设置“自定义客户端IP报头”选项。
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -54,14 +56,16 @@
|
||||
<td class="title">启用电子邮箱绑定功能</td>
|
||||
<td>
|
||||
<checkbox name="emailVerificationIsOn" v-model="config.emailVerification.isOn"></checkbox>
|
||||
<p class="comment">选中后,电子邮箱需要激活之后可以使用邮箱登录、找回密码等。此功能需要事先设置 <a href="/users/setting/email" target="_blank">[激活邮件设置]</a>。</p>
|
||||
<p class="comment">选中后,电子邮箱需要激活之后可以使用邮箱登录、找回密码等。此功能需要事先设置 <a href="/users/setting/email"
|
||||
target="_blank">[激活邮件设置]</a>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-show="config.emailVerification.isOn">
|
||||
<tr>
|
||||
<td>提示用户未绑定</td>
|
||||
<td>
|
||||
<checkbox name="emailVerificationShowNotice" v-model="config.emailVerification.showNotice"></checkbox>
|
||||
<checkbox name="emailVerificationShowNotice" v-model="config.emailVerification.showNotice">
|
||||
</checkbox>
|
||||
<p class="comment">选中后,将在页面上提示用户尚未绑定电子邮箱.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -73,20 +77,25 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator v-model="emailVerificationMoreOptions"></more-options-indicator></td>
|
||||
<td colspan="2"><more-options-indicator v-model="emailVerificationMoreOptions"></more-options-indicator>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="emailVerificationMoreOptions">
|
||||
<td>激活邮件标题</td>
|
||||
<td>
|
||||
<input type="text" name="emailVerificationSubject" v-model="config.emailVerification.subject"/>
|
||||
<p class="comment">其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui" target="_blank">产品名称</a>。</p>
|
||||
<input type="text" name="emailVerificationSubject" v-model="config.emailVerification.subject" />
|
||||
<p class="comment">其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui"
|
||||
target="_blank">产品名称</a>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="emailVerificationMoreOptions">
|
||||
<td>激活邮件内容</td>
|
||||
<td>
|
||||
<textarea name="emailVerificationBody" rows="8" v-model="config.emailVerification.body"></textarea>
|
||||
<p class="comment">可以使用简单的HTML,其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui" target="_blank">产品名称</a>,<code-label>${url.verify}</code-label>为生成的激活地址,<code-label>${url.home}</code-label>为用户平台主页地址,由<a href="/settings/userNodes" target="_blank">用户节点</a>访问地址组合而成。</p>
|
||||
<p class="comment">可以使用简单的HTML,其中<code-label>${product.name}</code-label>为当前设置的<a
|
||||
href="/settings/ui"
|
||||
target="_blank">产品名称</a>,<code-label>${url.verify}</code-label>为生成的激活地址,<code-label>${url.home}</code-label>为用户平台主页地址,由<a
|
||||
href="/settings/userNodes" target="_blank">用户节点</a>访问地址组合而成。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -106,20 +115,27 @@
|
||||
</tr>
|
||||
<tbody v-show="config.emailResetPassword.isOn">
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator v-model="emailResetPasswordMoreOptions"></more-options-indicator></td>
|
||||
<td colspan="2"><more-options-indicator
|
||||
v-model="emailResetPasswordMoreOptions"></more-options-indicator></td>
|
||||
</tr>
|
||||
<tr v-show="emailResetPasswordMoreOptions">
|
||||
<td>找回密码邮件标题</td>
|
||||
<td>
|
||||
<input type="text" name="emailResetPasswordSubject" v-model="config.emailResetPassword.subject" maxlength="100"/>
|
||||
<p class="comment">其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui" target="_blank">产品名称</a>。</p>
|
||||
<input type="text" name="emailResetPasswordSubject" v-model="config.emailResetPassword.subject"
|
||||
maxlength="100" />
|
||||
<p class="comment">其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui"
|
||||
target="_blank">产品名称</a>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="emailResetPasswordMoreOptions">
|
||||
<td>找回密码邮件内容</td>
|
||||
<td>
|
||||
<textarea name="emailResetPasswordBody" v-model="config.emailResetPassword.body" rows="5"></textarea>
|
||||
<p class="comment">可以使用简单的HTML,其中<code-label>${product.name}</code-label>为当前设置的<a href="/settings/ui" target="_blank">产品名称</a>,<code-label>${code}</code-label>为找回密码时用到的验证码,<code-label>${url.home}</code-label>为用户平台主页地址,由<a href="/settings/userNodes" target="_blank">用户节点</a>访问地址组合而成。</p>
|
||||
<textarea name="emailResetPasswordBody" v-model="config.emailResetPassword.body"
|
||||
rows="5"></textarea>
|
||||
<p class="comment">可以使用简单的HTML,其中<code-label>${product.name}</code-label>为当前设置的<a
|
||||
href="/settings/ui"
|
||||
target="_blank">产品名称</a>,<code-label>${code}</code-label>为找回密码时用到的验证码,<code-label>${url.home}</code-label>为用户平台主页地址,由<a
|
||||
href="/settings/userNodes" target="_blank">用户节点</a>访问地址组合而成。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -133,14 +149,16 @@
|
||||
<td class="title">启用手机号码绑定功能</td>
|
||||
<td>
|
||||
<checkbox name="mobileVerificationIsOn" v-model="config.mobileVerification.isOn"></checkbox>
|
||||
<p class="comment">选中后,手机号码需要激活之后可以使用手机号码、找回密码等。此功能需要事先设置 <a href="/users/setting/sms" target="_blank">[激活短信设置]</a>。</p>
|
||||
<p class="comment">选中后,手机号码需要激活之后可以使用手机号码、找回密码等。此功能需要事先设置 <a href="/users/setting/sms"
|
||||
target="_blank">[激活短信设置]</a>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody v-show="config.mobileVerification.isOn">
|
||||
<tr>
|
||||
<td>提示用户未绑定</td>
|
||||
<td>
|
||||
<checkbox name="mobileVerificationShowNotice" v-model="config.mobileVerification.showNotice"></checkbox>
|
||||
<checkbox name="mobileVerificationShowNotice" v-model="config.mobileVerification.showNotice">
|
||||
</checkbox>
|
||||
<p class="comment">选中后,将在页面上提示用户尚未绑定手机号码.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -159,12 +177,14 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator v-model="mobileVerificationMoreOptions"></more-options-indicator></td>
|
||||
<td colspan="2"><more-options-indicator
|
||||
v-model="mobileVerificationMoreOptions"></more-options-indicator></td>
|
||||
</tr>
|
||||
<tr v-show="mobileVerificationMoreOptions">
|
||||
<td>激活短信内容</td>
|
||||
<td>
|
||||
<textarea name="mobileVerificationBody" rows="8" v-model="config.mobileVerification.body"></textarea>
|
||||
<textarea name="mobileVerificationBody" rows="8"
|
||||
v-model="config.mobileVerification.body"></textarea>
|
||||
<p class="comment">其中使用<code-label>${code}</code-label>表示验证码。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -200,12 +220,14 @@
|
||||
</div>
|
||||
<span v-else class="grey">暂时还没有开通任何功能。</span>
|
||||
<div style="margin-top: 0.1em">
|
||||
<a href="" @click.prevent="showFeatures">修改<i class="icon angle" :class="{up: featuresVisible, down: !featuresVisible}"></i></a>
|
||||
<a href="" @click.prevent="showFeatures">修改<i class="icon angle"
|
||||
:class="{up: featuresVisible, down: !featuresVisible}"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-boxes" v-show="featuresVisible" style="margin-top: 1em">
|
||||
<div class="feature-box" v-for="feature in features">
|
||||
<checkbox name="features" :v-value="feature.code" v-model="feature.isChecked">{{feature.name}}</checkbox>
|
||||
<checkbox name="features" :v-value="feature.code" v-model="feature.isChecked">
|
||||
{{feature.name}}</checkbox>
|
||||
<p class="comment">{{feature.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,7 +255,8 @@
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">开通DDoS高防管理</td>
|
||||
<td><checkbox name="adIsOn" v-model="config.adIsOn"></checkbox>
|
||||
<td>
|
||||
<checkbox name="adIsOn" v-model="config.adIsOn"></checkbox>
|
||||
<p class="comment">选中表示自动为用户开通DDoS高防IP使用服务。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -247,8 +270,9 @@
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">开通智能DNS服务</td>
|
||||
<td><checkbox name="nsIsOn" v-model="config.nsIsOn"></checkbox>
|
||||
<p class="comment">选中表示自动为用户开通智能DNS服务。</p>
|
||||
<td>
|
||||
<checkbox name="nsIsOn" v-model="config.nsIsOn"></checkbox>
|
||||
<p class="comment">选中表示自动为用户开通智能DNS服务。使用默认集群资源,如需使用其他集群,请到用户新增和业务设置页面中进行选择。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -261,12 +285,23 @@
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">开通HTTPDNS服务</td>
|
||||
<td><checkbox name="httpdnsIsOn" v-model="config.httpdnsIsOn"></checkbox>
|
||||
<p class="comment">选中表示自动为用户开通HTTPDNS服务。</p>
|
||||
<td>
|
||||
<checkbox name="httpdnsIsOn" v-model="config.httpdnsIsOn"></checkbox>
|
||||
<p class="comment">选中表示自动为用户开通HTTPDNS服务。使用默认集群资源,如需使用其他集群,请到用户新增和业务设置页面中进行选择。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="config.httpdnsIsOn">
|
||||
<td>默认分配集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="httpdnsDefaultClusterId" v-model="httpdnsDefaultClusterId">
|
||||
<option value="0">[未选择]</option>
|
||||
<option v-for="cluster in httpdnsClusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment">用户新建 HTTPDNS 应用时默认分配的集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
@@ -39,12 +39,22 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>关联集群 *</td>
|
||||
<td>CDN关联集群 *</td>
|
||||
<td>
|
||||
<cluster-selector :v-cluster-id="clusterId"></cluster-selector>
|
||||
<p class="comment">用户发布的网站会自动部署到此集群,修改此选项会同步修改当前用户下的所有网站,但不影响和套餐绑定的服务。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="hasHTTPDNSFeature">
|
||||
<td>HTTPDNS关联集群</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="httpdnsClusterId" v-model="httpdnsClusterId">
|
||||
<option value="0">[未选择]</option>
|
||||
<option v-for="cluster in httpdnsClusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<p class="comment">用户新建 HTTPDNS 应用时默认分配的集群。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
信息未审核 <a href="" @click.prevent="verify">[审核]</a>
|
||||
</span>
|
||||
<span v-else-if="user.isRejected" class="red">已拒绝
|
||||
<a href="" @click.prevent="verify">[重新审核]</a>
|
||||
<a href="" @click.prevent="verify">[重新审核]</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<label-on :v-is-on="user.isOn"></label-on>
|
||||
</span>
|
||||
<p class="comment" v-if="user.isVerified && user.isRejected && user.rejectReason.length > 0">拒绝原因:{{user.rejectReason}}</p>
|
||||
<p class="comment" v-if="user.isVerified && user.isRejected && user.rejectReason.length > 0">
|
||||
拒绝原因:{{user.rejectReason}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -30,9 +31,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>关联集群</td>
|
||||
<td>CDN关联集群</td>
|
||||
<td>
|
||||
<span v-if="user.cluster != null">{{user.cluster.name}} <link-icon :href="'/clusters/cluster?clusterId=' + user.cluster.id"></link-icon></span>
|
||||
<span v-if="user.cluster != null">{{user.cluster.name}} <link-icon
|
||||
:href="'/clusters/cluster?clusterId=' + user.cluster.id"></link-icon></span>
|
||||
<span v-else class="disabled">没有设置。</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="hasHTTPDNSFeature">
|
||||
<td>HTTPDNS关联集群</td>
|
||||
<td>
|
||||
<span v-if="user.httpdnsCluster != null">{{user.httpdnsCluster.name}} <link-icon
|
||||
:href="'/httpdns/clusters/cluster?clusterId=' + user.httpdnsCluster.id"></link-icon></span>
|
||||
<span v-else class="disabled">没有设置。</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -106,8 +116,9 @@
|
||||
<tr v-if="otp != null && otp.isOn && moreOptionsVisible">
|
||||
<td>认证二维码</td>
|
||||
<td>
|
||||
<img alt="qrcode" :src="'./otpQrcode?userId=' + user.id"/>
|
||||
<p class="comment"><a :href="'./otpQrcode?userId=' + user.id + '&download=true'">[下载]</a> 可以通过二维码快速添加OTP认证信息到认证App中。</p>
|
||||
<img alt="qrcode" :src="'./otpQrcode?userId=' + user.id" />
|
||||
<p class="comment"><a :href="'./otpQrcode?userId=' + user.id + '&download=true'">[下载]</a>
|
||||
可以通过二维码快速添加OTP认证信息到认证App中。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="otp != null && otp.isOn && moreOptionsVisible">
|
||||
|
||||
Reference in New Issue
Block a user