带阿里标识的版本
This commit is contained in:
@@ -6,8 +6,10 @@ import (
|
||||
"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 {
|
||||
@@ -52,30 +54,83 @@ func (this *AppSettingsAction) RunGet(params struct {
|
||||
},
|
||||
}
|
||||
|
||||
clusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
clusters := make([]maps.Map, 0, len(clusterResp.GetClusters()))
|
||||
clusterDomainMap := map[int64]string{}
|
||||
clusterNameMap := map[int64]string{}
|
||||
defaultPrimaryClusterId := int64(0)
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusterId := cluster.GetId()
|
||||
clusterName := cluster.GetName()
|
||||
clusters = append(clusters, maps.Map{
|
||||
"id": clusterId,
|
||||
"name": clusterName,
|
||||
"serviceDomain": cluster.GetServiceDomain(),
|
||||
"isDefault": cluster.GetIsDefault(),
|
||||
})
|
||||
clusterDomainMap[clusterId] = cluster.GetServiceDomain()
|
||||
clusterNameMap[clusterId] = clusterName
|
||||
if defaultPrimaryClusterId <= 0 && cluster.GetIsDefault() {
|
||||
defaultPrimaryClusterId = clusterId
|
||||
}
|
||||
}
|
||||
|
||||
defaultBackupClusterId := int64(0)
|
||||
defaultBackupResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
|
||||
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
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": app.GetInt64("primaryClusterId"),
|
||||
"backupClusterId": app.GetInt64("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"),
|
||||
"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"),
|
||||
}
|
||||
this.Data["app"] = app
|
||||
this.Data["settings"] = settings
|
||||
this.Data["clusters"] = clusters
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *AppSettingsAction) RunPost(params struct {
|
||||
AppId int64
|
||||
|
||||
AppStatus bool
|
||||
AppStatus bool
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId 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,
|
||||
@@ -92,8 +147,8 @@ func (this *AppSettingsAction) RunPost(params struct {
|
||||
_, err = this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(this.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||
AppDbId: params.AppId,
|
||||
Name: appResp.GetApp().GetName(),
|
||||
PrimaryClusterId: appResp.GetApp().GetPrimaryClusterId(),
|
||||
BackupClusterId: appResp.GetApp().GetBackupClusterId(),
|
||||
PrimaryClusterId: params.PrimaryClusterId,
|
||||
BackupClusterId: params.BackupClusterId,
|
||||
IsOn: params.AppStatus,
|
||||
UserId: appResp.GetApp().GetUserId(),
|
||||
})
|
||||
|
||||
@@ -55,6 +55,24 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
this.Data["defaultBackupClusterId"] = defaultBackupClusterId
|
||||
|
||||
usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
users := make([]maps.Map, 0, len(usersResp.GetUsers()))
|
||||
for _, user := range usersResp.GetUsers() {
|
||||
users = append(users, maps.Map{
|
||||
"id": user.GetId(),
|
||||
"fullname": user.GetFullname(),
|
||||
"username": user.GetUsername(),
|
||||
})
|
||||
}
|
||||
this.Data["users"] = users
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ func init() {
|
||||
Get("/sdk", new(SdkAction)).
|
||||
GetPost("/sdk/upload", new(SdkUploadAction)).
|
||||
Post("/sdk/upload/delete", new(SdkUploadDeleteAction)).
|
||||
Get("/sdk/check", new(SdkCheckAction)).
|
||||
Get("/sdk/download", new(SdkDownloadAction)).
|
||||
Get("/sdk/doc", new(SdkDocAction)).
|
||||
GetPost("/app/settings", new(AppSettingsAction)).
|
||||
|
||||
@@ -7,11 +7,20 @@ import (
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
|
||||
clusterNameMap, err := loadHTTPDNSClusterNameMap(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userMapByID, err := loadHTTPDNSUserMap(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := parent.RPC().HTTPDNSAppRPC().ListHTTPDNSApps(parent.AdminContext(), &pb.ListHTTPDNSAppsRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
@@ -30,13 +39,22 @@ func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains()))))
|
||||
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains())), clusterNameMap, userMapByID))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, error) {
|
||||
clusterNameMap, err := loadHTTPDNSClusterNameMap(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userMapByID, err := loadHTTPDNSUserMap(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if appDbId > 0 {
|
||||
resp, err := parent.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(parent.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||
AppDbId: appDbId,
|
||||
@@ -51,7 +69,7 @@ func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains()))), nil
|
||||
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains())), clusterNameMap, userMapByID), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,16 +277,37 @@ func toggleCustomRule(parent *actionutils.ParentAction, ruleId int64, isOn bool)
|
||||
return err
|
||||
}
|
||||
|
||||
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
|
||||
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterNameMap map[int64]string, 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]}
|
||||
|
||||
var userMap maps.Map
|
||||
if app.GetUserId() > 0 {
|
||||
userMap = userMapByID[app.GetUserId()]
|
||||
if userMap == nil {
|
||||
userMap = maps.Map{
|
||||
"id": app.GetUserId(),
|
||||
"fullname": "用户#" + strconv.FormatInt(app.GetUserId(), 10),
|
||||
"username": "-",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maps.Map{
|
||||
"id": app.GetId(),
|
||||
"name": app.GetName(),
|
||||
"appId": app.GetAppId(),
|
||||
"clusterId": app.GetPrimaryClusterId(),
|
||||
"primaryClusterId": app.GetPrimaryClusterId(),
|
||||
"backupClusterId": app.GetBackupClusterId(),
|
||||
"clusterId": primaryClusterID,
|
||||
"primaryClusterId": primaryClusterID,
|
||||
"backupClusterId": backupClusterID,
|
||||
"primaryCluster": primaryClusterMap,
|
||||
"backupCluster": backupClusterMap,
|
||||
"userId": app.GetUserId(),
|
||||
"user": userMap,
|
||||
"isOn": app.GetIsOn(),
|
||||
"domainCount": domainCount,
|
||||
"sniPolicyText": "隐匿 SNI",
|
||||
@@ -279,6 +318,39 @@ func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
|
||||
}
|
||||
}
|
||||
|
||||
func loadHTTPDNSClusterNameMap(parent *actionutils.ParentAction) (map[int64]string, error) {
|
||||
resp, err := parent.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(parent.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := map[int64]string{}
|
||||
for _, cluster := range resp.GetClusters() {
|
||||
result[cluster.GetId()] = cluster.GetName()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadHTTPDNSUserMap(parent *actionutils.ParentAction) (map[int64]maps.Map, error) {
|
||||
resp, err := parent.RPC().UserRPC().ListEnabledUsers(parent.AdminContext(), &pb.ListEnabledUsersRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := map[int64]maps.Map{}
|
||||
for _, user := range resp.GetUsers() {
|
||||
result[user.GetId()] = maps.Map{
|
||||
"id": user.GetId(),
|
||||
"fullname": user.GetFullname(),
|
||||
"username": user.GetUsername(),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func defaultLineField(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SdkCheckAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SdkCheckAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *SdkCheckAction) RunGet(params struct {
|
||||
Platform string
|
||||
Version string
|
||||
Type string
|
||||
}) {
|
||||
platform, _, _, filename, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = err.Error()
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
t := strings.ToLower(strings.TrimSpace(params.Type))
|
||||
if t == "doc" {
|
||||
docPath := findUploadedSDKDocPath(platform, params.Version)
|
||||
if len(docPath) == 0 {
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = "Documentation is unavailable, please upload first"
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
downloadURL := "/httpdns/apps/sdk/doc?platform=" + url.QueryEscape(platform)
|
||||
if len(strings.TrimSpace(params.Version)) > 0 {
|
||||
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
|
||||
}
|
||||
this.Data["exists"] = true
|
||||
this.Data["url"] = downloadURL
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
archivePath := findSDKArchivePath(filename, params.Version)
|
||||
if len(archivePath) == 0 {
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = "SDK package is unavailable, please upload first"
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
downloadURL := "/httpdns/apps/sdk/download?platform=" + url.QueryEscape(platform)
|
||||
if len(strings.TrimSpace(params.Version)) > 0 {
|
||||
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
|
||||
}
|
||||
downloadURL += "&raw=1"
|
||||
this.Data["exists"] = true
|
||||
this.Data["url"] = downloadURL
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SdkDocAction struct {
|
||||
@@ -17,15 +18,18 @@ func (this *SdkDocAction) Init() {
|
||||
|
||||
func (this *SdkDocAction) RunGet(params struct {
|
||||
Platform string
|
||||
Version string
|
||||
}) {
|
||||
platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = err.Error()
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
var data []byte
|
||||
uploadedDocPath := findUploadedSDKDocPath(platform)
|
||||
uploadedDocPath := findUploadedSDKDocPath(platform, params.Version)
|
||||
if len(uploadedDocPath) > 0 {
|
||||
data, err = os.ReadFile(uploadedDocPath)
|
||||
}
|
||||
@@ -44,11 +48,18 @@ func (this *SdkDocAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
if len(data) == 0 || err != nil {
|
||||
this.Fail("当前服务器未找到 SDK 集成文档,请先在“SDK 集成”页面上传对应平台文档")
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = "SDK documentation is not found on server, please upload first"
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
downloadName := filepath.Base(uploadedDocPath)
|
||||
if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) {
|
||||
downloadName = "httpdns-sdk-" + strings.ToLower(platform) + ".md"
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Type", "text/markdown; charset=utf-8")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\"httpdns-sdk-"+strings.ToLower(platform)+"-README.md\";")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\""+downloadName+"\";")
|
||||
_, _ = this.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SdkDownloadAction struct {
|
||||
@@ -16,30 +18,48 @@ func (this *SdkDownloadAction) Init() {
|
||||
|
||||
func (this *SdkDownloadAction) RunGet(params struct {
|
||||
Platform string
|
||||
Version string
|
||||
Raw int
|
||||
}) {
|
||||
_, _, _, filename, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = err.Error()
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
archivePath := findSDKArchivePath(filename)
|
||||
archivePath := findSDKArchivePath(filename, params.Version)
|
||||
if len(archivePath) == 0 {
|
||||
this.Fail("当前服务器未找到 SDK 包,请先在“SDK 集成”页面上传对应平台包: " + filename)
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = "SDK archive not found on server, please upload first: " + filename
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
fp, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
this.Fail("打开 SDK 包失败: " + err.Error())
|
||||
this.Data["exists"] = false
|
||||
this.Data["message"] = "failed to open SDK archive: " + err.Error()
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
this.AddHeader("Content-Type", "application/zip")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
|
||||
downloadName := filepath.Base(archivePath)
|
||||
if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) {
|
||||
downloadName = filename
|
||||
}
|
||||
|
||||
if params.Raw == 1 {
|
||||
this.AddHeader("Content-Type", "application/octet-stream")
|
||||
this.AddHeader("X-SDK-Filename", downloadName)
|
||||
} else {
|
||||
this.AddHeader("Content-Type", "application/zip")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\""+downloadName+"\";")
|
||||
}
|
||||
this.AddHeader("X-Accel-Buffering", "no")
|
||||
_, _ = io.Copy(this.ResponseWriter, fp)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,58 @@ package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
)
|
||||
|
||||
func sdkUploadDir() string {
|
||||
dirs := sdkUploadDirs()
|
||||
if len(dirs) > 0 {
|
||||
return dirs[0]
|
||||
}
|
||||
return filepath.Clean(Tea.Root + "/data/httpdns/sdk")
|
||||
}
|
||||
|
||||
func sdkUploadDirs() []string {
|
||||
candidates := []string{
|
||||
filepath.Clean(Tea.Root + "/../data/httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/data/httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../edge-admin/data/httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../edge-user/data/httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../../data/httpdns/sdk"),
|
||||
}
|
||||
results := make([]string, 0, len(candidates))
|
||||
seen := map[string]bool{}
|
||||
for _, dir := range candidates {
|
||||
if len(dir) == 0 || seen[dir] {
|
||||
continue
|
||||
}
|
||||
seen[dir] = true
|
||||
results = append(results, dir)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func sdkUploadSearchDirs() []string {
|
||||
dirs := sdkUploadDirs()
|
||||
results := make([]string, 0, len(dirs))
|
||||
for _, dir := range dirs {
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
results = append(results, dir)
|
||||
}
|
||||
}
|
||||
if len(results) == 0 {
|
||||
results = append(results, sdkUploadDir())
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func findFirstExistingDir(paths []string) string {
|
||||
for _, path := range paths {
|
||||
stat, err := os.Stat(path)
|
||||
@@ -27,7 +67,7 @@ func findFirstExistingDir(paths []string) string {
|
||||
func findFirstExistingFile(paths []string) string {
|
||||
for _, path := range paths {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil && !stat.IsDir() {
|
||||
if err == nil && !stat.IsDir() && stat.Size() > 0 {
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -45,6 +85,9 @@ func findNewestExistingFile(paths []string) string {
|
||||
if err != nil || stat.IsDir() {
|
||||
continue
|
||||
}
|
||||
if stat.Size() <= 0 {
|
||||
continue
|
||||
}
|
||||
if len(result.path) == 0 || stat.ModTime().After(result.modTime) || (stat.ModTime().Equal(result.modTime) && path > result.path) {
|
||||
result.path = path
|
||||
result.modTime = stat.ModTime()
|
||||
@@ -85,22 +128,23 @@ func resolveSDKPlatform(platform string) (key string, relativeDir string, readme
|
||||
}
|
||||
}
|
||||
|
||||
func findSDKArchivePath(downloadFilename string) string {
|
||||
searchDirs := []string{sdkUploadDir()}
|
||||
func findSDKArchivePath(downloadFilename string, version string) string {
|
||||
searchDirs := sdkUploadSearchDirs()
|
||||
|
||||
// 1) Exact filename first.
|
||||
exactFiles := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
|
||||
}
|
||||
path := findFirstExistingFile(exactFiles)
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
|
||||
// 2) Version-suffixed archives, e.g. httpdns-sdk-android-v1.4.8.zip
|
||||
normalizedVersion := strings.TrimSpace(version)
|
||||
base := strings.TrimSuffix(downloadFilename, ".zip")
|
||||
patternName := base + "-*.zip"
|
||||
if len(normalizedVersion) > 0 {
|
||||
versionFiles := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
versionFiles = append(versionFiles, filepath.Join(dir, base+"-v"+normalizedVersion+".zip"))
|
||||
}
|
||||
if path := findFirstExistingFile(versionFiles); len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
patternName := base + "-v*.zip"
|
||||
matches := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
found, _ := filepath.Glob(filepath.Join(dir, patternName))
|
||||
@@ -115,28 +159,52 @@ func findSDKArchivePath(downloadFilename string) string {
|
||||
return findNewestExistingFile(matches)
|
||||
}
|
||||
|
||||
exactFiles := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
|
||||
}
|
||||
if path := findFirstExistingFile(exactFiles); len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func findUploadedSDKDocPath(platform string) string {
|
||||
func findUploadedSDKDocPath(platform string, version string) string {
|
||||
platform = strings.ToLower(strings.TrimSpace(platform))
|
||||
if len(platform) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
searchDir := sdkUploadDir()
|
||||
exact := filepath.Join(searchDir, "httpdns-sdk-"+platform+".md")
|
||||
if file := findFirstExistingFile([]string{exact}); len(file) > 0 {
|
||||
return file
|
||||
}
|
||||
|
||||
pattern := filepath.Join(searchDir, "httpdns-sdk-"+platform+"-*.md")
|
||||
matches, _ := filepath.Glob(pattern)
|
||||
if len(matches) == 0 {
|
||||
searchDirs := sdkUploadSearchDirs()
|
||||
normalizedVersion := strings.TrimSpace(version)
|
||||
if len(normalizedVersion) > 0 {
|
||||
exactVersion := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
exactVersion = append(exactVersion, filepath.Join(dir, "httpdns-sdk-"+platform+"-v"+normalizedVersion+".md"))
|
||||
}
|
||||
if file := findFirstExistingFile(exactVersion); len(file) > 0 {
|
||||
return file
|
||||
}
|
||||
return ""
|
||||
}
|
||||
sort.Strings(matches)
|
||||
return findNewestExistingFile(matches)
|
||||
|
||||
matches := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
pattern := filepath.Join(dir, "httpdns-sdk-"+platform+"-v*.md")
|
||||
found, _ := filepath.Glob(pattern)
|
||||
matches = append(matches, found...)
|
||||
}
|
||||
if len(matches) > 0 {
|
||||
sort.Strings(matches)
|
||||
return findNewestExistingFile(matches)
|
||||
}
|
||||
|
||||
exact := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
exact = append(exact, filepath.Join(dir, "httpdns-sdk-"+platform+".md"))
|
||||
}
|
||||
return findFirstExistingFile(exact)
|
||||
}
|
||||
|
||||
func findLocalSDKDocPath(platform string) string {
|
||||
|
||||
@@ -2,15 +2,16 @@ package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type SdkUploadAction struct {
|
||||
@@ -44,7 +45,7 @@ func (this *SdkUploadAction) RunPost(params struct {
|
||||
AppId int64
|
||||
Platform string
|
||||
Version string
|
||||
SDKFile *actions.File
|
||||
SdkFile *actions.File
|
||||
DocFile *actions.File
|
||||
|
||||
Must *actions.Must
|
||||
@@ -63,7 +64,7 @@ func (this *SdkUploadAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
if params.SDKFile == nil && params.DocFile == nil {
|
||||
if params.SdkFile == nil && params.DocFile == nil {
|
||||
this.Fail("请至少上传一个文件")
|
||||
return
|
||||
}
|
||||
@@ -75,24 +76,25 @@ func (this *SdkUploadAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
if params.SDKFile != nil {
|
||||
filename := strings.ToLower(strings.TrimSpace(params.SDKFile.Filename))
|
||||
if params.SdkFile != nil {
|
||||
filename := strings.ToLower(strings.TrimSpace(params.SdkFile.Filename))
|
||||
if !strings.HasSuffix(filename, ".zip") {
|
||||
this.Fail("SDK 包仅支持 .zip 文件")
|
||||
return
|
||||
}
|
||||
|
||||
sdkData, readErr := params.SDKFile.Read()
|
||||
sdkData, readErr := params.SdkFile.Read()
|
||||
if readErr != nil {
|
||||
this.Fail("读取 SDK 包失败: " + readErr.Error())
|
||||
return
|
||||
}
|
||||
if len(sdkData) == 0 {
|
||||
this.Fail("SDK 包文件为空,请重新上传")
|
||||
return
|
||||
}
|
||||
|
||||
baseName := strings.TrimSuffix(downloadFilename, ".zip")
|
||||
err = saveSDKUploadFile(uploadDir, downloadFilename, sdkData)
|
||||
if err == nil {
|
||||
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
|
||||
}
|
||||
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
|
||||
if err != nil {
|
||||
this.Fail("保存 SDK 包失败: " + err.Error())
|
||||
return
|
||||
@@ -111,12 +113,12 @@ func (this *SdkUploadAction) RunPost(params struct {
|
||||
this.Fail("读取集成文档失败: " + readErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
filename := "httpdns-sdk-" + platform + ".md"
|
||||
err = saveSDKUploadFile(uploadDir, filename, docData)
|
||||
if err == nil {
|
||||
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
|
||||
if len(docData) == 0 {
|
||||
this.Fail("集成文档文件为空,请重新上传")
|
||||
return
|
||||
}
|
||||
|
||||
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
|
||||
if err != nil {
|
||||
this.Fail("保存集成文档失败: " + err.Error())
|
||||
return
|
||||
@@ -151,12 +153,6 @@ func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
|
||||
}
|
||||
|
||||
func listUploadedSDKFiles() []map[string]interface{} {
|
||||
dir := sdkUploadDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
|
||||
type item struct {
|
||||
Name string
|
||||
Platform string
|
||||
@@ -166,30 +162,45 @@ func listUploadedSDKFiles() []map[string]interface{} {
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
items := make([]item, 0)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
platform, version, fileType, ok := parseSDKUploadFilename(name)
|
||||
if !ok {
|
||||
byName := map[string]item{}
|
||||
for _, dir := range sdkUploadSearchDirs() {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
platform, version, fileType, ok := parseSDKUploadFilename(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
info, statErr := entry.Info()
|
||||
if statErr != nil {
|
||||
continue
|
||||
}
|
||||
info, statErr := entry.Info()
|
||||
if statErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, item{
|
||||
Name: name,
|
||||
Platform: platform,
|
||||
FileType: fileType,
|
||||
Version: version,
|
||||
SizeBytes: info.Size(),
|
||||
UpdatedAt: info.ModTime().Unix(),
|
||||
})
|
||||
current := item{
|
||||
Name: name,
|
||||
Platform: platform,
|
||||
FileType: fileType,
|
||||
Version: version,
|
||||
SizeBytes: info.Size(),
|
||||
UpdatedAt: info.ModTime().Unix(),
|
||||
}
|
||||
old, exists := byName[name]
|
||||
if !exists || current.UpdatedAt >= old.UpdatedAt {
|
||||
byName[name] = current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]item, 0, len(byName))
|
||||
for _, it := range byName {
|
||||
items = append(items, it)
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
@@ -200,14 +211,14 @@ func listUploadedSDKFiles() []map[string]interface{} {
|
||||
})
|
||||
|
||||
result := make([]map[string]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
for _, it := range items {
|
||||
result = append(result, map[string]interface{}{
|
||||
"name": item.Name,
|
||||
"platform": item.Platform,
|
||||
"fileType": item.FileType,
|
||||
"version": item.Version,
|
||||
"sizeText": formatSDKFileSize(item.SizeBytes),
|
||||
"updatedAt": time.Unix(item.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
"name": it.Name,
|
||||
"platform": it.Platform,
|
||||
"fileType": it.FileType,
|
||||
"version": it.Version,
|
||||
"sizeText": formatSDKFileSize(it.SizeBytes),
|
||||
"updatedAt": time.Unix(it.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
return result
|
||||
@@ -231,7 +242,7 @@ func parseSDKUploadFilename(filename string) (platform string, version string, f
|
||||
}
|
||||
|
||||
main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext)
|
||||
version = "latest"
|
||||
version = ""
|
||||
if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) {
|
||||
version = main[idx+2:]
|
||||
main = main[:idx]
|
||||
@@ -241,6 +252,9 @@ func parseSDKUploadFilename(filename string) (platform string, version string, f
|
||||
switch main {
|
||||
case "android", "ios", "flutter":
|
||||
platform = main
|
||||
if len(version) == 0 {
|
||||
version = "-"
|
||||
}
|
||||
return platform, version, fileType, true
|
||||
default:
|
||||
return "", "", "", false
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SdkUploadDeleteAction struct {
|
||||
@@ -42,15 +43,16 @@ func (this *SdkUploadDeleteAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(sdkUploadDir(), filename)
|
||||
_, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
if err = os.Remove(fullPath); err != nil {
|
||||
this.Fail("删除失败: " + err.Error())
|
||||
return
|
||||
for _, dir := range sdkUploadDirs() {
|
||||
fullPath := filepath.Join(dir, filename)
|
||||
_, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err = os.Remove(fullPath); err != nil {
|
||||
this.Fail("删除失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
|
||||
@@ -10,8 +10,10 @@ 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 {
|
||||
@@ -41,13 +43,14 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
settings := maps.Map{
|
||||
"name": cluster.GetString("name"),
|
||||
"gatewayDomain": cluster.GetString("gatewayDomain"),
|
||||
"cacheTtl": cluster.GetInt("defaultTTL"),
|
||||
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
|
||||
"installDir": cluster.GetString("installDir"),
|
||||
"isOn": cluster.GetBool("isOn"),
|
||||
"isDefaultCluster": cluster.GetBool("isDefault"),
|
||||
"name": cluster.GetString("name"),
|
||||
"gatewayDomain": cluster.GetString("gatewayDomain"),
|
||||
"cacheTtl": cluster.GetInt("defaultTTL"),
|
||||
"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
|
||||
@@ -59,6 +62,19 @@ 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,
|
||||
@@ -104,14 +120,15 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
||||
}
|
||||
|
||||
func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefaultCluster bool
|
||||
ClusterId int64
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefaultCluster bool
|
||||
IsDefaultBackupCluster bool
|
||||
|
||||
Addresses []byte
|
||||
SslPolicyJSON []byte
|
||||
@@ -137,7 +154,15 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
if params.IsDefaultCluster && !params.IsOn {
|
||||
this.Fail("默认集群必须保持启用状态")
|
||||
this.Fail("默认主集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultBackupCluster && !params.IsOn {
|
||||
this.Fail("默认备用集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
if params.IsDefaultCluster && params.IsDefaultBackupCluster {
|
||||
this.Fail("默认主集群和默认备用集群不能是同一个集群")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -195,5 +220,33 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
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)),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -23,13 +25,14 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefault bool
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefaultPrimary bool
|
||||
IsDefaultBackup bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -49,6 +52,19 @@ 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,
|
||||
@@ -56,13 +72,24 @@ func (this *CreateAction) RunPost(params struct {
|
||||
FallbackTimeoutMs: params.FallbackTimeout,
|
||||
InstallDir: params.InstallDir,
|
||||
IsOn: params.IsOn,
|
||||
IsDefault: params.IsDefault,
|
||||
IsDefault: params.IsDefaultPrimary,
|
||||
})
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -102,6 +102,8 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
NsIsOn bool
|
||||
|
||||
HttpdnsIsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -112,6 +114,15 @@ func (this *IndexAction) RunPost(params struct {
|
||||
Gt(0, "请选择一个集群")
|
||||
|
||||
var config = userconfigs.DefaultUserRegisterConfig()
|
||||
{
|
||||
// 先读取现有配置,避免保存时把未出现在当前表单里的字段重置为默认值
|
||||
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserRegisterConfig,
|
||||
})
|
||||
if err == nil && len(resp.ValueJSON) > 0 {
|
||||
_ = json.Unmarshal(resp.ValueJSON, config)
|
||||
}
|
||||
}
|
||||
config.IsOn = params.IsOn
|
||||
config.ComplexPassword = params.ComplexPassword
|
||||
config.RequireVerification = params.RequireVerification
|
||||
@@ -142,6 +153,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.ADIsOn = params.AdIsOn
|
||||
|
||||
config.NSIsOn = params.NsIsOn
|
||||
config.HTTPDNSIsOn = params.HttpdnsIsOn
|
||||
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
@@ -157,10 +169,15 @@ func (this *IndexAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
if params.FeatureOp != "keep" {
|
||||
featureOp := params.FeatureOp
|
||||
if featureOp != "overwrite" && featureOp != "append" && featureOp != "keep" {
|
||||
featureOp = "keep"
|
||||
}
|
||||
|
||||
if featureOp != "keep" {
|
||||
_, err = this.RPC().UserRPC().UpdateAllUsersFeatures(this.AdminContext(), &pb.UpdateAllUsersFeaturesRequest{
|
||||
FeatureCodes: params.Features,
|
||||
Overwrite: params.FeatureOp == "overwrite",
|
||||
Overwrite: featureOp == "overwrite",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
28
EdgeAdmin/web/views/@default/httpdns/addPortPopup.html
Normal file
28
EdgeAdmin/web/views/@default/httpdns/addPortPopup.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3 v-if="!isUpdating">添加端口绑定</h3>
|
||||
<h3 v-if="isUpdating">修改端口绑定</h3>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<input type="hidden" name="supportRange" :value="supportRange ? 1 : 0"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td>网络协议</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="protocol" v-model="protocol" @change="changeProtocol">
|
||||
<option v-for="p in protocols" :value="p.code">{{p.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">端口 *</td>
|
||||
<td>
|
||||
<input type="text" name="address" ref="focus" v-model="address"/>
|
||||
<p class="comment">可以是一个数字端口(通常不超过65535),也可以是"地址:端口"的方式。<span v-if="supportRange">支持端口范围,形式为<code-label>port1-port2</code-label>。</span>
|
||||
<span v-if="from.length == 0 && protocol == 'http'">HTTP常用端口为<a href="" title="点击添加" @click.prevent="addPort('80')">80</a>。</span>
|
||||
<span v-if="from.length == 0 && protocol == 'https'">HTTPS常用端口为<a href="" title="点击添加" @click.prevent="addPort('443')">443</a>。</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
47
EdgeAdmin/web/views/@default/httpdns/addPortPopup.js
Normal file
47
EdgeAdmin/web/views/@default/httpdns/addPortPopup.js
Normal file
@@ -0,0 +1,47 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyPopup;
|
||||
|
||||
this.isUpdating = false
|
||||
|
||||
this.address = ""
|
||||
this.protocol = this.protocols[0].code
|
||||
|
||||
// 初始化
|
||||
// from 用来标记是否为特殊的节点
|
||||
if (this.from.length == 0) {
|
||||
if (this.protocol == "http") {
|
||||
this.address = "80"
|
||||
} else if (this.protocol == "https") {
|
||||
this.address = "443"
|
||||
}
|
||||
}
|
||||
|
||||
if (window.parent.UPDATING_ADDR != null) {
|
||||
this.isUpdating = true
|
||||
let addr = window.parent.UPDATING_ADDR
|
||||
this.protocol = addr.protocol
|
||||
if (addr.host.length == 0) {
|
||||
this.address = addr.portRange
|
||||
} else {
|
||||
this.address = addr.host.quoteIP() + ":" + addr.portRange
|
||||
}
|
||||
}
|
||||
|
||||
this.changeProtocol = function () {
|
||||
if (this.from.length > 0) {
|
||||
return
|
||||
}
|
||||
switch (this.protocol) {
|
||||
case "http":
|
||||
this.address = "80"
|
||||
break
|
||||
case "https":
|
||||
this.address = "443"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.addPort = function (port) {
|
||||
this.address = port
|
||||
}
|
||||
});
|
||||
@@ -67,8 +67,24 @@
|
||||
|
||||
<table class="ui table selectable definition" v-show="activeSection == 'basic'">
|
||||
<tr>
|
||||
<td class="title">App ID</td>
|
||||
<td><code>{{settings.appId}}</code></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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">应用启用</td>
|
||||
@@ -86,14 +102,34 @@
|
||||
</table>
|
||||
|
||||
<table class="ui table selectable definition httpdns-auth-table" v-show="activeSection == 'auth'">
|
||||
<tr>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
</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>
|
||||
@@ -102,14 +138,9 @@
|
||||
<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>
|
||||
|
||||
@@ -34,10 +34,13 @@
|
||||
<tr>
|
||||
<td>所属用户</td>
|
||||
<td>
|
||||
<user-selector></user-selector>
|
||||
<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>
|
||||
<p class="comment">可以选择当前应用所属的平台用户。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{$layout}
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="ui margin"></div>
|
||||
@@ -26,16 +26,22 @@
|
||||
|
||||
<table class="ui table selectable celled httpdns-apps-table" v-if="apps.length > 0">
|
||||
<colgroup>
|
||||
<col style="width:32%;" />
|
||||
<col style="width:26%;" />
|
||||
<col style="width:20%;" />
|
||||
<col style="width:12%;" />
|
||||
<col style="width:10%;" />
|
||||
<col style="width:11%;" />
|
||||
<col style="width:11%;" />
|
||||
<col style="width:12%;" />
|
||||
<col style="width:8%;" />
|
||||
<col style="width:6%;" />
|
||||
<col style="width:20%;" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>应用名称</th>
|
||||
<th>AppID</th>
|
||||
<th>主集群</th>
|
||||
<th>备用集群</th>
|
||||
<th>用户</th>
|
||||
<th class="center">绑定域名数</th>
|
||||
<th class="center">状态</th>
|
||||
<th>操作</th>
|
||||
@@ -54,6 +60,26 @@
|
||||
<code>{{app.appId}}</code>
|
||||
<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>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="app.user != null && app.user.id > 0">
|
||||
{{app.user.fullname}} ({{app.user.username}})
|
||||
</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center"><a :href="'/httpdns/apps/domains?appId=' + app.id">{{app.domainCount}}</a></td>
|
||||
<td class="center">
|
||||
<label-on :v-is-on="app.isOn"></label-on>
|
||||
@@ -68,4 +94,4 @@
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<div class="httpdns-sdk-actions">
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=android"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=android"><i class="icon book"></i> 下载文档</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=android" @click="downloadSDK('android', $event)"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=android" @click="downloadDoc('android', $event)"><i class="icon book"></i> 下载文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,8 +63,8 @@
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<div class="httpdns-sdk-actions">
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=ios"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=ios"><i class="icon book"></i> 下载文档</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=ios" @click="downloadSDK('ios', $event)"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=ios" @click="downloadDoc('ios', $event)"><i class="icon book"></i> 下载文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,8 +77,8 @@
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<div class="httpdns-sdk-actions">
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=flutter"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=flutter"><i class="icon book"></i> 下载文档</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=flutter" @click="downloadSDK('flutter', $event)"><i class="icon download"></i> 下载 SDK</a>
|
||||
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=flutter" @click="downloadDoc('flutter', $event)"><i class="icon book"></i> 下载文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
123
EdgeAdmin/web/views/@default/httpdns/apps/sdk.js
Normal file
123
EdgeAdmin/web/views/@default/httpdns/apps/sdk.js
Normal file
@@ -0,0 +1,123 @@
|
||||
Tea.context(function () {
|
||||
this.downloadSDK = function (platform, event) {
|
||||
this.checkAndDownload(platform, "sdk", event)
|
||||
}
|
||||
|
||||
this.downloadDoc = function (platform, event) {
|
||||
this.checkAndDownload(platform, "doc", event)
|
||||
}
|
||||
|
||||
this.checkAndDownload = function (platform, type, event) {
|
||||
if (event != null && typeof event.preventDefault == "function") {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
this.$get("/httpdns/apps/sdk/check")
|
||||
.params({
|
||||
platform: platform,
|
||||
type: type
|
||||
})
|
||||
.success(function (resp) {
|
||||
let data = (resp != null && resp.data != null) ? resp.data : {}
|
||||
if (!data.exists) {
|
||||
teaweb.warn(data.message || "当前暂无可下载文件")
|
||||
return
|
||||
}
|
||||
if (typeof data.url == "string" && data.url.length > 0) {
|
||||
this.downloadByBlob(data.url, platform, type)
|
||||
return
|
||||
}
|
||||
teaweb.warn("下载地址生成失败,请稍后重试")
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.downloadByBlob = function (url, platform, type) {
|
||||
let defaultFileName = "httpdns-sdk-" + platform + (type == "doc" ? ".md" : ".zip")
|
||||
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", url, true)
|
||||
xhr.responseType = "blob"
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
teaweb.warn("下载失败(HTTP " + xhr.status + ")")
|
||||
return
|
||||
}
|
||||
if (xhr.status == 204) {
|
||||
teaweb.warn("下载被浏览器扩展或下载工具拦截,请暂时关闭相关扩展后重试")
|
||||
return
|
||||
}
|
||||
|
||||
let contentType = (xhr.getResponseHeader("Content-Type") || "").toLowerCase()
|
||||
if (contentType.indexOf("application/json") >= 0) {
|
||||
let reader = new FileReader()
|
||||
reader.onload = function () {
|
||||
try {
|
||||
let json = JSON.parse(reader.result)
|
||||
teaweb.warn((json && json.message) ? json.message : "下载失败,请稍后重试")
|
||||
} catch (_e) {
|
||||
teaweb.warn("下载失败,请稍后重试")
|
||||
}
|
||||
}
|
||||
reader.readAsText(xhr.response)
|
||||
return
|
||||
}
|
||||
|
||||
let disposition = xhr.getResponseHeader("Content-Disposition") || ""
|
||||
let fileName = xhr.getResponseHeader("X-SDK-Filename") || this.parseFileName(disposition) || defaultFileName
|
||||
if (xhr.response == null || xhr.response.size <= 0) {
|
||||
teaweb.warn("下载文件为空,请检查已上传 SDK 包是否完整")
|
||||
return
|
||||
}
|
||||
this.saveBlob(xhr.response, fileName)
|
||||
}.bind(this)
|
||||
|
||||
xhr.onerror = function () {
|
||||
teaweb.warn("下载失败,请检查网络后重试")
|
||||
}
|
||||
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
this.parseFileName = function (disposition) {
|
||||
if (typeof disposition != "string" || disposition.length == 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||
if (utf8Match != null && utf8Match.length > 1) {
|
||||
try {
|
||||
return decodeURIComponent(utf8Match[1])
|
||||
} catch (_e) {
|
||||
}
|
||||
}
|
||||
|
||||
let plainMatch = disposition.match(/filename="?([^";]+)"?/i)
|
||||
if (plainMatch != null && plainMatch.length > 1) {
|
||||
return plainMatch[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
this.saveBlob = function (blob, fileName) {
|
||||
if (window.navigator != null && typeof window.navigator.msSaveOrOpenBlob == "function") {
|
||||
window.navigator.msSaveOrOpenBlob(blob, fileName)
|
||||
return
|
||||
}
|
||||
|
||||
let objectURL = window.URL.createObjectURL(blob)
|
||||
let a = document.createElement("a")
|
||||
a.style.display = "none"
|
||||
a.href = objectURL
|
||||
a.download = fileName
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
setTimeout(function () {
|
||||
window.URL.revokeObjectURL(objectURL)
|
||||
if (a.parentNode != null) {
|
||||
a.parentNode.removeChild(a)
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
{$layout}
|
||||
{$layout}
|
||||
|
||||
<second-menu>
|
||||
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
|
||||
@@ -34,7 +34,7 @@
|
||||
<td class="title">版本号 *</td>
|
||||
<td>
|
||||
<input type="text" name="version" v-model="version" maxlength="32"/>
|
||||
<p class="comment">默认 `1.0.0`。同平台上传会覆盖“最新版本”下载内容。</p>
|
||||
<p class="comment">默认 `1.0.0`。同平台+同版本再次上传会覆盖该版本文件。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -90,4 +90,3 @@
|
||||
<div class="ui message" v-else>
|
||||
暂无上传记录。
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Tea.context(function () {
|
||||
Tea.context(function () {
|
||||
this.platform = "android";
|
||||
this.version = this.defaultVersion || "1.0.0";
|
||||
this.isUploading = false;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<div class="item"><strong>集群设置</strong></div>
|
||||
</second-menu>
|
||||
|
||||
|
||||
{$template "/left_menu_with_menu"}
|
||||
|
||||
<div class="right-box with-menu">
|
||||
@@ -39,50 +38,65 @@
|
||||
<td>节点安装根目录</td>
|
||||
<td>
|
||||
<input type="text" name="installDir" maxlength="100" v-model="settings.installDir" />
|
||||
<p class="comment">边缘节点安装 HTTPDNS 服务的默认所在目录,此目录将被用于下发配置。通常保持默认即可。</p>
|
||||
<p class="comment">边缘节点安装 HTTPDNS 服务的默认目录,通常保持默认即可。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认解析 TTL</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl"
|
||||
style="width: 6em" />
|
||||
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl" style="width: 6em" />
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">SDK 向 HTTPDNS 请求域名解析时,返回的默认缓存有效期 (TTL)。SDK 超时后将重新发起请求。</p>
|
||||
<p class="comment">SDK 通过 HTTPDNS 解析域名时返回的默认 TTL。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>降级超时容忍度</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" name="fallbackTimeout" maxlength="5" v-model="settings.fallbackTimeout"
|
||||
style="width: 6em" />
|
||||
<input type="text" name="fallbackTimeout" maxlength="5" v-model="settings.fallbackTimeout" style="width: 6em" />
|
||||
<span class="ui label">毫秒</span>
|
||||
</div>
|
||||
<p class="comment">HTTPDNS 节点在回源查询其它 DNS 时的最大等待时间。超出此时间将触发服务降级逻辑(返回上一有效缓存或错误)。</p>
|
||||
<p class="comment">节点回源查询上游 DNS 时的最大等待时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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" @change="syncDefaultCluster" />
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">如果取消启用,整个集群的 HTTPDNS 解析服务将停止。</p>
|
||||
<p class="comment">取消启用后,该集群不会参与 HTTPDNS 服务。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isDefaultCluster" value="1" v-model="settings.isDefaultCluster" />
|
||||
<label>设置为默认部署集群</label>
|
||||
<input type="checkbox" value="1" v-model="settings.defaultClusterEnabled" @change="syncDefaultClusterSelection" />
|
||||
<label>设为默认集群</label>
|
||||
</div>
|
||||
<p class="comment">全局设置。如果应用未单独指定集群,将默认分配和部署到该集群中。</p>
|
||||
<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>
|
||||
@@ -91,15 +105,12 @@
|
||||
<tr>
|
||||
<td class="title">绑定端口 *</td>
|
||||
<td>
|
||||
<network-addresses-box :v-url="'/httpdns/addPortPopup'" :v-addresses="tlsConfig.listen"
|
||||
:v-protocol="'tls'" :v-support-range="true"></network-addresses-box>
|
||||
<network-addresses-box :v-url="'/httpdns/addPortPopup'" :v-addresses="tlsConfig.listen" :v-protocol="'tls'" :v-support-range="true"></network-addresses-box>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- SSL配置 -->
|
||||
<ssl-config-box v-show="activeSection == 'tls'" :v-ssl-policy="tlsConfig.sslPolicy"
|
||||
:v-protocol="'tls'"></ssl-config-box>
|
||||
<ssl-config-box v-show="activeSection == 'tls'" :v-ssl-policy="tlsConfig.sslPolicy" :v-protocol="'tls'"></ssl-config-box>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Tea.context(function () {
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
|
||||
this.activeSection = this.activeSection || "basic"
|
||||
@@ -8,9 +8,38 @@ Tea.context(function () {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{$layout}
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="ui margin"></div>
|
||||
@@ -27,7 +27,7 @@
|
||||
<input type="text" name="cacheTtl" maxlength="5" value="30" style="width: 6em" />
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">SDK 向 HTTPDNS 请求域名解析时,返回的默认缓存有效期 (TTL)。SDK 超时后将重新发起请求。</p>
|
||||
<p class="comment">SDK 通过 HTTPDNS 解析域名时返回的默认 TTL。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -37,36 +37,52 @@
|
||||
<input type="text" name="fallbackTimeout" maxlength="5" value="300" style="width: 6em" />
|
||||
<span class="ui label">毫秒</span>
|
||||
</div>
|
||||
<p class="comment">HTTPDNS 节点在回源查询其它 DNS 时的最大等待时间。超出此时间将触发服务降级逻辑(返回上一有效缓存或错误)。</p>
|
||||
<p class="comment">节点回源查询上游 DNS 时的最大等待时间。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>节点安装根目录</td>
|
||||
<td>
|
||||
<input type="text" name="installDir" maxlength="255" value="/opt/edge-httpdns" />
|
||||
<p class="comment">边缘节点安装 HTTPDNS 服务的默认所在目录,此目录将被用于下发配置。通常保持默认即可。</p>
|
||||
<p class="comment">边缘节点安装 HTTPDNS 服务的默认目录。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>启用当前集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isOn" value="1" checked />
|
||||
<input type="checkbox" name="isOn" value="1" v-model="isOn" @change="syncDefaultClusterEnabled" checked />
|
||||
<label></label>
|
||||
</div>
|
||||
<p class="comment">如果取消启用,整个集群的 HTTPDNS 解析服务将不被系统分配。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>默认集群</td>
|
||||
<td>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="isDefault" value="1" />
|
||||
<label>设置为默认部署集群</label>
|
||||
<input type="checkbox" value="1" v-model="defaultClusterEnabled" @change="syncDefaultClusterEnabled" />
|
||||
<label>设为默认集群</label>
|
||||
</div>
|
||||
<p class="comment">全局设置。如果应用未单独指定集群,将默认分配和部署到该集群中。</p>
|
||||
<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,4 +1,15 @@
|
||||
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
|
||||
if (resp != null && resp.data != null && typeof resp.data.clusterId != "undefined") {
|
||||
|
||||
@@ -256,5 +256,17 @@
|
||||
<div class="margin"></div>
|
||||
</div>
|
||||
|
||||
<div class="margin"></div>
|
||||
<h4>HTTPDNS服务</h4>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -7,7 +7,8 @@ Tea.context(function () {
|
||||
this.mobileVerificationMoreOptions = false
|
||||
this.mobileResetPasswordMoreOptions = false
|
||||
|
||||
this.featureOp = "overwrite"
|
||||
// 默认不影响已有用户功能,避免保存注册设置时误改用户功能绑定
|
||||
this.featureOp = "keep"
|
||||
this.featuresVisible = false
|
||||
|
||||
this.showFeatures = function () {
|
||||
@@ -27,4 +28,4 @@ Tea.context(function () {
|
||||
})
|
||||
return names.join(" / ")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user