管理端全部功能跑通
This commit is contained in:
@@ -17,6 +17,10 @@ func (this *AppAction) Init() {
|
||||
func (this *AppAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
app := pickApp(params.AppId)
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.RedirectURL("/httpdns/apps/domains?appId=" + strconv.FormatInt(app.GetInt64("id"), 10))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type AppSettingsAction struct {
|
||||
@@ -22,33 +23,45 @@ func (this *AppSettingsAction) RunGet(params struct {
|
||||
Section string
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
app := pickApp(params.AppId)
|
||||
|
||||
// 顶部 tabbar
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "settings")
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "settings")
|
||||
|
||||
// 当前选中的 section
|
||||
section := params.Section
|
||||
if len(section) == 0 {
|
||||
section = "basic"
|
||||
}
|
||||
this.Data["activeSection"] = section
|
||||
appIdStr := strconv.FormatInt(params.AppId, 10)
|
||||
this.Data["leftMenuItems"] = []map[string]interface{}{
|
||||
|
||||
appIDStr := strconv.FormatInt(app.GetInt64("id"), 10)
|
||||
this.Data["leftMenuItems"] = []maps.Map{
|
||||
{
|
||||
"name": "基础配置",
|
||||
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "§ion=basic",
|
||||
"url": "/httpdns/apps/app/settings?appId=" + appIDStr + "§ion=basic",
|
||||
"isActive": section == "basic",
|
||||
},
|
||||
{
|
||||
"name": "认证与密钥",
|
||||
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "§ion=auth",
|
||||
"url": "/httpdns/apps/app/settings?appId=" + appIDStr + "§ion=auth",
|
||||
"isActive": section == "auth",
|
||||
},
|
||||
}
|
||||
|
||||
settings := loadAppSettings(app)
|
||||
this.Data["clusters"] = policies.LoadAvailableDeployClusters()
|
||||
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"),
|
||||
}
|
||||
this.Data["app"] = app
|
||||
this.Data["settings"] = settings
|
||||
this.Show()
|
||||
@@ -57,36 +70,37 @@ func (this *AppSettingsAction) RunGet(params struct {
|
||||
func (this *AppSettingsAction) RunPost(params struct {
|
||||
AppId int64
|
||||
|
||||
AppStatus bool
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId int64
|
||||
AppStatus bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "please select app")
|
||||
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "please select primary cluster")
|
||||
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
||||
this.FailField("backupClusterId", "backup cluster must be different from primary cluster")
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
|
||||
appResp, err := this.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(this.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||
AppDbId: params.AppId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if appResp.GetApp() == nil {
|
||||
this.Fail("找不到对应的应用")
|
||||
return
|
||||
}
|
||||
|
||||
app := pickApp(params.AppId)
|
||||
settings := loadAppSettings(app)
|
||||
settings["appStatus"] = params.AppStatus
|
||||
settings["primaryClusterId"] = params.PrimaryClusterId
|
||||
settings["backupClusterId"] = params.BackupClusterId
|
||||
_, err = this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(this.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||
AppDbId: params.AppId,
|
||||
Name: appResp.GetApp().GetName(),
|
||||
PrimaryClusterId: appResp.GetApp().GetPrimaryClusterId(),
|
||||
BackupClusterId: appResp.GetApp().GetBackupClusterId(),
|
||||
IsOn: params.AppStatus,
|
||||
UserId: appResp.GetApp().GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// SNI strategy is fixed to level2 empty.
|
||||
settings["sniPolicy"] = "level2"
|
||||
settings["level2Mode"] = "empty"
|
||||
settings["publicSniDomain"] = ""
|
||||
settings["echFallbackPolicy"] = "level2"
|
||||
settings["ecsMode"] = "off"
|
||||
settings["ecsIPv4Prefix"] = 24
|
||||
settings["ecsIPv6Prefix"] = 56
|
||||
settings["pinningMode"] = "off"
|
||||
settings["sanMode"] = "off"
|
||||
|
||||
saveAppSettings(app.GetInt64("id"), settings)
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
@@ -13,11 +14,16 @@ func (this *AppSettingsResetSignSecretAction) RunPost(params struct {
|
||||
AppId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
|
||||
app := pickApp(params.AppId)
|
||||
resetSignSecret(app)
|
||||
_, err := this.RPC().HTTPDNSAppRPC().ResetHTTPDNSAppSignSecret(this.AdminContext(), &pb.ResetHTTPDNSAppSignSecretRequest{
|
||||
AppDbId: params.AppId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
@@ -14,14 +15,17 @@ func (this *AppSettingsToggleSignEnabledAction) RunPost(params struct {
|
||||
IsOn int
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
|
||||
app := pickApp(params.AppId)
|
||||
settings := loadAppSettings(app)
|
||||
settings["signEnabled"] = params.IsOn == 1
|
||||
saveAppSettings(app.GetInt64("id"), settings)
|
||||
_, err := this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSAppSignEnabled(this.AdminContext(), &pb.UpdateHTTPDNSAppSignEnabledRequest{
|
||||
AppDbId: params.AppId,
|
||||
SignEnabled: params.IsOn == 1,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var appSettingsStore = struct {
|
||||
sync.RWMutex
|
||||
data map[int64]maps.Map
|
||||
}{
|
||||
data: map[int64]maps.Map{},
|
||||
}
|
||||
|
||||
func defaultAppSettings(app maps.Map) maps.Map {
|
||||
signSecretPlain := randomPlainSecret("ss")
|
||||
return maps.Map{
|
||||
"appId": app.GetString("appId"),
|
||||
"primaryClusterId": app.GetInt64("clusterId"),
|
||||
"backupClusterId": int64(0),
|
||||
"signSecretPlain": signSecretPlain,
|
||||
"signSecretMasked": maskSecret(signSecretPlain),
|
||||
"signSecretUpdatedAt": "2026-02-20 12:30:00",
|
||||
"appStatus": app.GetBool("isOn"),
|
||||
"defaultTTL": 30,
|
||||
"fallbackTimeoutMs": 300,
|
||||
"sniPolicy": "level2",
|
||||
"level2Mode": "empty",
|
||||
"publicSniDomain": "",
|
||||
"echFallbackPolicy": "level2",
|
||||
"ecsMode": "off",
|
||||
"ecsIPv4Prefix": 24,
|
||||
"ecsIPv6Prefix": 56,
|
||||
"pinningMode": "off",
|
||||
"sanMode": "off",
|
||||
"signEnabled": true,
|
||||
}
|
||||
}
|
||||
|
||||
func cloneSettings(settings maps.Map) maps.Map {
|
||||
return maps.Map{
|
||||
"appId": settings.GetString("appId"),
|
||||
"primaryClusterId": settings.GetInt64("primaryClusterId"),
|
||||
"backupClusterId": settings.GetInt64("backupClusterId"),
|
||||
"signSecretPlain": settings.GetString("signSecretPlain"),
|
||||
"signSecretMasked": settings.GetString("signSecretMasked"),
|
||||
"signSecretUpdatedAt": settings.GetString("signSecretUpdatedAt"),
|
||||
"appStatus": settings.GetBool("appStatus"),
|
||||
"defaultTTL": settings.GetInt("defaultTTL"),
|
||||
"fallbackTimeoutMs": settings.GetInt("fallbackTimeoutMs"),
|
||||
"sniPolicy": settings.GetString("sniPolicy"),
|
||||
"level2Mode": settings.GetString("level2Mode"),
|
||||
"publicSniDomain": settings.GetString("publicSniDomain"),
|
||||
"echFallbackPolicy": settings.GetString("echFallbackPolicy"),
|
||||
"ecsMode": settings.GetString("ecsMode"),
|
||||
"ecsIPv4Prefix": settings.GetInt("ecsIPv4Prefix"),
|
||||
"ecsIPv6Prefix": settings.GetInt("ecsIPv6Prefix"),
|
||||
"pinningMode": settings.GetString("pinningMode"),
|
||||
"sanMode": settings.GetString("sanMode"),
|
||||
"signEnabled": settings.GetBool("signEnabled"),
|
||||
}
|
||||
}
|
||||
|
||||
func loadAppSettings(app maps.Map) maps.Map {
|
||||
appId := app.GetInt64("id")
|
||||
appSettingsStore.RLock()
|
||||
settings, ok := appSettingsStore.data[appId]
|
||||
appSettingsStore.RUnlock()
|
||||
if ok {
|
||||
if ensureSettingsFields(settings) {
|
||||
saveAppSettings(appId, settings)
|
||||
}
|
||||
return cloneSettings(settings)
|
||||
}
|
||||
|
||||
settings = defaultAppSettings(app)
|
||||
saveAppSettings(appId, settings)
|
||||
return cloneSettings(settings)
|
||||
}
|
||||
|
||||
func saveAppSettings(appId int64, settings maps.Map) {
|
||||
appSettingsStore.Lock()
|
||||
appSettingsStore.data[appId] = cloneSettings(settings)
|
||||
appSettingsStore.Unlock()
|
||||
}
|
||||
|
||||
func deleteAppSettings(appId int64) {
|
||||
appSettingsStore.Lock()
|
||||
delete(appSettingsStore.data, appId)
|
||||
appSettingsStore.Unlock()
|
||||
}
|
||||
|
||||
func resetSignSecret(app maps.Map) maps.Map {
|
||||
settings := loadAppSettings(app)
|
||||
signSecretPlain := randomPlainSecret("ss")
|
||||
settings["signSecretPlain"] = signSecretPlain
|
||||
settings["signSecretMasked"] = maskSecret(signSecretPlain)
|
||||
settings["signSecretUpdatedAt"] = nowDateTime()
|
||||
saveAppSettings(app.GetInt64("id"), settings)
|
||||
return settings
|
||||
}
|
||||
|
||||
func nowDateTime() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
func randomPlainSecret(prefix string) string {
|
||||
suffix := time.Now().UnixNano() & 0xffff
|
||||
return fmt.Sprintf("%s_%016x", prefix, suffix)
|
||||
}
|
||||
|
||||
func maskSecret(secret string) string {
|
||||
if len(secret) < 4 {
|
||||
return "******"
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
for i := 0; i < len(secret); i++ {
|
||||
if secret[i] == '_' {
|
||||
prefix = secret[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(prefix) == 0 {
|
||||
prefix = secret[:2]
|
||||
}
|
||||
|
||||
if len(secret) <= 8 {
|
||||
return prefix + "xxxx"
|
||||
}
|
||||
return prefix + "xxxxxxxx" + secret[len(secret)-4:]
|
||||
}
|
||||
|
||||
func ensureSettingsFields(settings maps.Map) bool {
|
||||
changed := false
|
||||
|
||||
if settings.GetInt64("primaryClusterId") <= 0 {
|
||||
settings["primaryClusterId"] = int64(1)
|
||||
changed = true
|
||||
}
|
||||
if settings.GetInt64("backupClusterId") < 0 {
|
||||
settings["backupClusterId"] = int64(0)
|
||||
changed = true
|
||||
}
|
||||
if settings.GetInt64("backupClusterId") > 0 && settings.GetInt64("backupClusterId") == settings.GetInt64("primaryClusterId") {
|
||||
settings["backupClusterId"] = int64(0)
|
||||
changed = true
|
||||
}
|
||||
|
||||
signSecretPlain := settings.GetString("signSecretPlain")
|
||||
if len(signSecretPlain) == 0 {
|
||||
signSecretPlain = randomPlainSecret("ss")
|
||||
settings["signSecretPlain"] = signSecretPlain
|
||||
changed = true
|
||||
}
|
||||
if len(settings.GetString("signSecretMasked")) == 0 {
|
||||
settings["signSecretMasked"] = maskSecret(signSecretPlain)
|
||||
changed = true
|
||||
}
|
||||
if len(settings.GetString("signSecretUpdatedAt")) == 0 {
|
||||
settings["signSecretUpdatedAt"] = nowDateTime()
|
||||
changed = true
|
||||
}
|
||||
|
||||
if len(settings.GetString("sniPolicy")) == 0 {
|
||||
settings["sniPolicy"] = "level2"
|
||||
changed = true
|
||||
} else if settings.GetString("sniPolicy") != "level2" {
|
||||
settings["sniPolicy"] = "level2"
|
||||
changed = true
|
||||
}
|
||||
if settings.GetString("level2Mode") != "empty" {
|
||||
settings["level2Mode"] = "empty"
|
||||
changed = true
|
||||
}
|
||||
if len(settings.GetString("publicSniDomain")) > 0 {
|
||||
settings["publicSniDomain"] = ""
|
||||
changed = true
|
||||
}
|
||||
if len(settings.GetString("echFallbackPolicy")) == 0 {
|
||||
settings["echFallbackPolicy"] = "level2"
|
||||
changed = true
|
||||
} else if settings.GetString("echFallbackPolicy") != "level2" {
|
||||
settings["echFallbackPolicy"] = "level2"
|
||||
changed = true
|
||||
}
|
||||
if settings.GetString("ecsMode") != "off" {
|
||||
settings["ecsMode"] = "off"
|
||||
changed = true
|
||||
}
|
||||
if settings.GetInt("ecsIPv4Prefix") <= 0 {
|
||||
settings["ecsIPv4Prefix"] = 24
|
||||
changed = true
|
||||
}
|
||||
if settings.GetInt("ecsIPv6Prefix") <= 0 {
|
||||
settings["ecsIPv6Prefix"] = 56
|
||||
changed = true
|
||||
}
|
||||
if settings.GetString("pinningMode") != "off" {
|
||||
settings["pinningMode"] = "off"
|
||||
changed = true
|
||||
}
|
||||
if settings.GetString("sanMode") != "off" {
|
||||
settings["sanMode"] = "off"
|
||||
changed = true
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
// LoadAppSettingsByAppID exposes app settings for other httpdns sub-modules
|
||||
// such as sandbox mock responses.
|
||||
func LoadAppSettingsByAppID(appID string) maps.Map {
|
||||
for _, app := range mockApps() {
|
||||
if app.GetString("appId") == appID {
|
||||
return loadAppSettings(app)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type CreateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreateAction) Init() {
|
||||
this.Nav("", "", "create")
|
||||
}
|
||||
|
||||
func (this *CreateAction) 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()))
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusters = append(clusters, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
})
|
||||
}
|
||||
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
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId 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", "备用服务集群必须和主服务集群不一致")
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["appId"] = createResp.GetAppDbId()
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
clusters := policies.LoadAvailableDeployClusters()
|
||||
this.Data["clusters"] = clusters
|
||||
|
||||
defaultPrimaryClusterId := policies.LoadDefaultClusterID()
|
||||
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
|
||||
|
||||
// Mock users for dropdown
|
||||
this.Data["users"] = []maps.Map{
|
||||
{"id": int64(1), "name": "User A", "username": "zhangsan"},
|
||||
{"id": int64(2), "name": "User B", "username": "lisi"},
|
||||
{"id": int64(3), "name": "User C", "username": "wangwu"},
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
Name string
|
||||
PrimaryClusterId int64
|
||||
BackupClusterId int64
|
||||
UserId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("name", params.Name).Require("please input app name")
|
||||
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "please select primary cluster")
|
||||
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
||||
this.FailField("backupClusterId", "backup cluster must be different from primary cluster")
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
@@ -20,25 +20,32 @@ func (this *CustomRecordsAction) RunGet(params struct {
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
|
||||
app := pickApp(params.AppId)
|
||||
// 自定义解析属于域名管理子页,顶部沿用应用 tabbar(高亮域名列表)
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "domains")
|
||||
|
||||
domains := mockDomains(app.GetInt64("id"))
|
||||
domain := pickDomainFromDomains(domains, params.DomainId)
|
||||
domainName := domain.GetString("name")
|
||||
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := findDomainMap(domains, params.DomainId)
|
||||
|
||||
records := make([]maps.Map, 0)
|
||||
for _, record := range loadCustomRecords(app.GetInt64("id")) {
|
||||
if len(domainName) > 0 && record.GetString("domain") != domainName {
|
||||
continue
|
||||
if domain.GetInt64("id") > 0 {
|
||||
records, err = listCustomRuleMaps(this.Parent(), domain.GetInt64("id"))
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, record := range records {
|
||||
record["domain"] = domain.GetString("name")
|
||||
record["lineText"] = buildLineText(record)
|
||||
record["recordValueText"] = buildRecordValueText(record)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
record["lineText"] = buildLineText(record)
|
||||
record["recordValueText"] = buildRecordValueText(record)
|
||||
}
|
||||
|
||||
this.Data["app"] = app
|
||||
@@ -47,17 +54,3 @@ func (this *CustomRecordsAction) RunGet(params struct {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func pickDomainFromDomains(domains []maps.Map, domainID int64) maps.Map {
|
||||
if len(domains) == 0 {
|
||||
return maps.Map{}
|
||||
}
|
||||
if domainID <= 0 {
|
||||
return domains[0]
|
||||
}
|
||||
for _, domain := range domains {
|
||||
if domain.GetInt64("id") == domainID {
|
||||
return domain
|
||||
}
|
||||
}
|
||||
return domains[0]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
@@ -23,11 +24,18 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
RecordId int64
|
||||
}) {
|
||||
app := pickApp(params.AppId)
|
||||
this.Data["app"] = app
|
||||
domains := mockDomains(app.GetInt64("id"))
|
||||
domain := pickDomainFromDomains(domains, params.DomainId)
|
||||
this.Data["domain"] = domain
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := findDomainMap(domains, params.DomainId)
|
||||
|
||||
record := maps.Map{
|
||||
"id": int64(0),
|
||||
@@ -45,77 +53,35 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
|
||||
"recordItemsJson": `[{"type":"A","value":"","weight":100}]`,
|
||||
}
|
||||
|
||||
if params.RecordId > 0 {
|
||||
existing := findCustomRecord(app.GetInt64("id"), params.RecordId)
|
||||
if len(existing) > 0 {
|
||||
record["id"] = existing.GetInt64("id")
|
||||
if len(record.GetString("domain")) == 0 {
|
||||
record["domain"] = existing.GetString("domain")
|
||||
if params.RecordId > 0 && domain.GetInt64("id") > 0 {
|
||||
rules, err := listCustomRuleMaps(this.Parent(), domain.GetInt64("id"))
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.GetInt64("id") != params.RecordId {
|
||||
continue
|
||||
}
|
||||
|
||||
record["lineScope"] = strings.TrimSpace(existing.GetString("lineScope"))
|
||||
record["lineCarrier"] = strings.TrimSpace(existing.GetString("lineCarrier"))
|
||||
record["lineRegion"] = strings.TrimSpace(existing.GetString("lineRegion"))
|
||||
record["lineProvince"] = strings.TrimSpace(existing.GetString("lineProvince"))
|
||||
record["lineContinent"] = strings.TrimSpace(existing.GetString("lineContinent"))
|
||||
record["lineCountry"] = strings.TrimSpace(existing.GetString("lineCountry"))
|
||||
record["ruleName"] = existing.GetString("ruleName")
|
||||
record["weightEnabled"] = existing.GetBool("weightEnabled")
|
||||
record["ttl"] = existing.GetInt("ttl")
|
||||
record["isOn"] = existing.GetBool("isOn")
|
||||
|
||||
recordItems := make([]maps.Map, 0)
|
||||
recordType := strings.ToUpper(strings.TrimSpace(existing.GetString("recordType")))
|
||||
values, _ := existing["recordValues"].([]maps.Map)
|
||||
for _, item := range values {
|
||||
itemType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
||||
if len(itemType) == 0 {
|
||||
itemType = recordType
|
||||
}
|
||||
if itemType != "A" && itemType != "AAAA" {
|
||||
itemType = "A"
|
||||
}
|
||||
|
||||
recordItems = append(recordItems, maps.Map{
|
||||
"type": itemType,
|
||||
"value": strings.TrimSpace(item.GetString("value")),
|
||||
"weight": item.GetInt("weight"),
|
||||
})
|
||||
}
|
||||
if len(recordItems) == 0 {
|
||||
recordItems = append(recordItems, maps.Map{
|
||||
"type": "A",
|
||||
"value": "",
|
||||
"weight": 100,
|
||||
})
|
||||
}
|
||||
record["recordItemsJson"] = marshalJSON(recordItems, "[]")
|
||||
record["id"] = rule.GetInt64("id")
|
||||
record["domain"] = domain.GetString("name")
|
||||
record["lineScope"] = rule.GetString("lineScope")
|
||||
record["lineCarrier"] = defaultLineField(rule.GetString("lineCarrier"))
|
||||
record["lineRegion"] = defaultLineField(rule.GetString("lineRegion"))
|
||||
record["lineProvince"] = defaultLineField(rule.GetString("lineProvince"))
|
||||
record["lineContinent"] = defaultLineField(rule.GetString("lineContinent"))
|
||||
record["lineCountry"] = defaultLineField(rule.GetString("lineCountry"))
|
||||
record["ruleName"] = rule.GetString("ruleName")
|
||||
record["weightEnabled"] = rule.GetBool("weightEnabled")
|
||||
record["ttl"] = rule.GetInt("ttl")
|
||||
record["isOn"] = rule.GetBool("isOn")
|
||||
record["recordItemsJson"] = marshalJSON(rule["recordValues"], "[]")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if record.GetString("lineScope") != "china" && record.GetString("lineScope") != "overseas" {
|
||||
if len(strings.TrimSpace(record.GetString("lineContinent"))) > 0 || len(strings.TrimSpace(record.GetString("lineCountry"))) > 0 {
|
||||
record["lineScope"] = "overseas"
|
||||
} else {
|
||||
record["lineScope"] = "china"
|
||||
}
|
||||
}
|
||||
if len(record.GetString("lineCarrier")) == 0 {
|
||||
record["lineCarrier"] = "默认"
|
||||
}
|
||||
if len(record.GetString("lineRegion")) == 0 {
|
||||
record["lineRegion"] = "默认"
|
||||
}
|
||||
if len(record.GetString("lineProvince")) == 0 {
|
||||
record["lineProvince"] = "默认"
|
||||
}
|
||||
if len(record.GetString("lineContinent")) == 0 {
|
||||
record["lineContinent"] = "默认"
|
||||
}
|
||||
if len(record.GetString("lineCountry")) == 0 {
|
||||
record["lineCountry"] = "默认"
|
||||
}
|
||||
|
||||
this.Data["app"] = app
|
||||
this.Data["domain"] = domain
|
||||
this.Data["record"] = record
|
||||
this.Data["isEditing"] = params.RecordId > 0
|
||||
this.Show()
|
||||
@@ -138,40 +104,26 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
||||
RuleName string
|
||||
RecordItemsJSON string
|
||||
WeightEnabled bool
|
||||
TTL int
|
||||
Ttl int
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "please select app")
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
params.Must.Field("domainId", params.DomainId).Gt(0, "请选择所属域名")
|
||||
|
||||
params.Domain = strings.TrimSpace(params.Domain)
|
||||
params.LineScope = strings.ToLower(strings.TrimSpace(params.LineScope))
|
||||
params.RuleName = strings.TrimSpace(params.RuleName)
|
||||
params.RecordItemsJSON = strings.TrimSpace(params.RecordItemsJSON)
|
||||
|
||||
domain := maps.Map{}
|
||||
if params.DomainId > 0 {
|
||||
domain = pickDomainFromDomains(mockDomains(params.AppId), params.DomainId)
|
||||
}
|
||||
if len(domain) > 0 {
|
||||
params.Domain = strings.TrimSpace(domain.GetString("name"))
|
||||
}
|
||||
if len(params.Domain) == 0 {
|
||||
this.Fail("please select domain")
|
||||
return
|
||||
}
|
||||
|
||||
if params.LineScope != "china" && params.LineScope != "overseas" {
|
||||
params.LineScope = "china"
|
||||
}
|
||||
params.RuleName = strings.TrimSpace(params.RuleName)
|
||||
if len(params.RuleName) == 0 {
|
||||
this.Fail("please input rule name")
|
||||
this.Fail("请输入规则名称")
|
||||
return
|
||||
}
|
||||
if params.TTL <= 0 || params.TTL > 86400 {
|
||||
this.Fail("ttl should be in 1-86400")
|
||||
if params.Ttl <= 0 || params.Ttl > 86400 {
|
||||
this.Fail("TTL值必须在 1-86400 范围内")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,11 +133,11 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
if len(recordValues) == 0 {
|
||||
this.Fail("please input record values")
|
||||
this.Fail("请输入解析记录值")
|
||||
return
|
||||
}
|
||||
if len(recordValues) > 10 {
|
||||
this.Fail("record values should be <= 10")
|
||||
this.Fail("单个规则最多只能添加 10 条解析记录")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -209,7 +161,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
||||
if len(lineCountry) == 0 {
|
||||
lineCountry = "默认"
|
||||
}
|
||||
|
||||
if params.LineScope == "overseas" {
|
||||
lineCarrier = ""
|
||||
lineRegion = ""
|
||||
@@ -219,40 +170,57 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
||||
lineCountry = ""
|
||||
}
|
||||
|
||||
recordType := recordValues[0].GetString("type")
|
||||
if len(recordType) == 0 {
|
||||
recordType = "A"
|
||||
records := make([]*pb.HTTPDNSRuleRecord, 0, len(recordValues))
|
||||
for i, item := range recordValues {
|
||||
records = append(records, &pb.HTTPDNSRuleRecord{
|
||||
Id: 0,
|
||||
RuleId: 0,
|
||||
RecordType: item.GetString("type"),
|
||||
RecordValue: item.GetString("value"),
|
||||
Weight: int32(item.GetInt("weight")),
|
||||
Sort: int32(i + 1),
|
||||
})
|
||||
}
|
||||
|
||||
saveCustomRecord(params.AppId, maps.Map{
|
||||
"id": params.RecordId,
|
||||
"domain": params.Domain,
|
||||
"lineScope": params.LineScope,
|
||||
"lineCarrier": lineCarrier,
|
||||
"lineRegion": lineRegion,
|
||||
"lineProvince": lineProvince,
|
||||
"lineContinent": lineContinent,
|
||||
"lineCountry": lineCountry,
|
||||
"ruleName": params.RuleName,
|
||||
"sdnsParams": []maps.Map{},
|
||||
"recordType": recordType,
|
||||
"recordValues": recordValues,
|
||||
"weightEnabled": params.WeightEnabled,
|
||||
"ttl": params.TTL,
|
||||
"isOn": params.IsOn,
|
||||
})
|
||||
rule := &pb.HTTPDNSCustomRule{
|
||||
Id: params.RecordId,
|
||||
AppId: params.AppId,
|
||||
DomainId: params.DomainId,
|
||||
RuleName: params.RuleName,
|
||||
LineScope: params.LineScope,
|
||||
LineCarrier: lineCarrier,
|
||||
LineRegion: lineRegion,
|
||||
LineProvince: lineProvince,
|
||||
LineContinent: lineContinent,
|
||||
LineCountry: lineCountry,
|
||||
Ttl: int32(params.Ttl),
|
||||
IsOn: params.IsOn,
|
||||
Priority: 100,
|
||||
Records: records,
|
||||
}
|
||||
|
||||
if params.RecordId > 0 {
|
||||
err = updateCustomRule(this.Parent(), rule)
|
||||
} else {
|
||||
_, err = createCustomRule(this.Parent(), rule)
|
||||
}
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if len(raw) == 0 {
|
||||
return []maps.Map{}, nil
|
||||
}
|
||||
|
||||
list := []maps.Map{}
|
||||
if err := json.Unmarshal([]byte(raw), &list); err != nil {
|
||||
return nil, fmt.Errorf("record items json is invalid")
|
||||
return nil, fmt.Errorf("解析记录格式不正确")
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(list))
|
||||
@@ -263,10 +231,10 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
||||
continue
|
||||
}
|
||||
if recordType != "A" && recordType != "AAAA" {
|
||||
return nil, fmt.Errorf("record type should be A or AAAA")
|
||||
return nil, fmt.Errorf("记录类型只能是 A 或 AAAA")
|
||||
}
|
||||
if len(recordValue) == 0 {
|
||||
return nil, fmt.Errorf("record value should not be empty")
|
||||
return nil, fmt.Errorf("记录值不能为空")
|
||||
}
|
||||
|
||||
weight := item.GetInt("weight")
|
||||
@@ -274,7 +242,7 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
||||
weight = 100
|
||||
}
|
||||
if weight < 1 || weight > 100 {
|
||||
return nil, fmt.Errorf("weight should be in 1-100")
|
||||
return nil, fmt.Errorf("权重值必须在 1-100 之间")
|
||||
}
|
||||
|
||||
result = append(result, maps.Map{
|
||||
@@ -283,7 +251,6 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
||||
"weight": weight,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,12 @@ func (this *CustomRecordsDeleteAction) RunPost(params struct {
|
||||
AppId int64
|
||||
RecordId int64
|
||||
}) {
|
||||
if params.AppId > 0 && params.RecordId > 0 {
|
||||
deleteCustomRecord(params.AppId, params.RecordId)
|
||||
if params.RecordId > 0 {
|
||||
err := deleteCustomRule(this.Parent(), params.RecordId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -11,8 +11,12 @@ func (this *CustomRecordsToggleAction) RunPost(params struct {
|
||||
RecordId int64
|
||||
IsOn bool
|
||||
}) {
|
||||
if params.AppId > 0 && params.RecordId > 0 {
|
||||
toggleCustomRecord(params.AppId, params.RecordId, params.IsOn)
|
||||
if params.RecordId > 0 {
|
||||
err := toggleCustomRule(this.Parent(), params.RecordId, params.IsOn)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var customRecordStore = struct {
|
||||
sync.RWMutex
|
||||
nextID int64
|
||||
data map[int64][]maps.Map
|
||||
}{
|
||||
nextID: 1000,
|
||||
data: map[int64][]maps.Map{
|
||||
1: {
|
||||
{
|
||||
"id": int64(1001),
|
||||
"domain": "api.business.com",
|
||||
"lineScope": "china",
|
||||
"lineCarrier": "电信",
|
||||
"lineRegion": "华东",
|
||||
"lineProvince": "上海",
|
||||
"ruleName": "上海电信灰度-v2",
|
||||
"sdnsParams": []maps.Map{},
|
||||
"recordType": "A",
|
||||
"recordValues": []maps.Map{{"type": "A", "value": "1.1.1.10", "weight": 100}},
|
||||
"weightEnabled": false,
|
||||
"ttl": 30,
|
||||
"isOn": true,
|
||||
"updatedAt": "2026-02-23 10:20:00",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func loadCustomRecords(appID int64) []maps.Map {
|
||||
customRecordStore.RLock()
|
||||
defer customRecordStore.RUnlock()
|
||||
|
||||
records := customRecordStore.data[appID]
|
||||
result := make([]maps.Map, 0, len(records))
|
||||
for _, record := range records {
|
||||
result = append(result, cloneCustomRecord(record))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func countCustomRecordsByDomain(appID int64, domain string) int {
|
||||
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||
if len(domain) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
customRecordStore.RLock()
|
||||
defer customRecordStore.RUnlock()
|
||||
|
||||
count := 0
|
||||
for _, record := range customRecordStore.data[appID] {
|
||||
if strings.ToLower(strings.TrimSpace(record.GetString("domain"))) == domain {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func findCustomRecord(appID int64, recordID int64) maps.Map {
|
||||
for _, record := range loadCustomRecords(appID) {
|
||||
if record.GetInt64("id") == recordID {
|
||||
return record
|
||||
}
|
||||
}
|
||||
return maps.Map{}
|
||||
}
|
||||
|
||||
func saveCustomRecord(appID int64, record maps.Map) maps.Map {
|
||||
customRecordStore.Lock()
|
||||
defer customRecordStore.Unlock()
|
||||
|
||||
if appID <= 0 {
|
||||
return maps.Map{}
|
||||
}
|
||||
|
||||
record = cloneCustomRecord(record)
|
||||
recordID := record.GetInt64("id")
|
||||
if recordID <= 0 {
|
||||
customRecordStore.nextID++
|
||||
recordID = customRecordStore.nextID
|
||||
record["id"] = recordID
|
||||
}
|
||||
record["updatedAt"] = nowCustomRecordTime()
|
||||
|
||||
records := customRecordStore.data[appID]
|
||||
found := false
|
||||
for i, oldRecord := range records {
|
||||
if oldRecord.GetInt64("id") == recordID {
|
||||
records[i] = cloneCustomRecord(record)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
records = append(records, cloneCustomRecord(record))
|
||||
}
|
||||
customRecordStore.data[appID] = records
|
||||
|
||||
return cloneCustomRecord(record)
|
||||
}
|
||||
|
||||
func deleteCustomRecord(appID int64, recordID int64) {
|
||||
customRecordStore.Lock()
|
||||
defer customRecordStore.Unlock()
|
||||
|
||||
records := customRecordStore.data[appID]
|
||||
if len(records) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
filtered := make([]maps.Map, 0, len(records))
|
||||
for _, record := range records {
|
||||
if record.GetInt64("id") == recordID {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, record)
|
||||
}
|
||||
customRecordStore.data[appID] = filtered
|
||||
}
|
||||
|
||||
func deleteCustomRecordsByApp(appID int64) {
|
||||
customRecordStore.Lock()
|
||||
defer customRecordStore.Unlock()
|
||||
delete(customRecordStore.data, appID)
|
||||
}
|
||||
|
||||
func toggleCustomRecord(appID int64, recordID int64, isOn bool) {
|
||||
customRecordStore.Lock()
|
||||
defer customRecordStore.Unlock()
|
||||
|
||||
records := customRecordStore.data[appID]
|
||||
for i, record := range records {
|
||||
if record.GetInt64("id") == recordID {
|
||||
record["isOn"] = isOn
|
||||
record["updatedAt"] = nowCustomRecordTime()
|
||||
records[i] = record
|
||||
break
|
||||
}
|
||||
}
|
||||
customRecordStore.data[appID] = records
|
||||
}
|
||||
|
||||
func cloneCustomRecord(src maps.Map) maps.Map {
|
||||
dst := maps.Map{}
|
||||
for k, v := range src {
|
||||
switch k {
|
||||
case "sdnsParams", "recordValues":
|
||||
if list, ok := v.([]maps.Map); ok {
|
||||
cloned := make([]maps.Map, 0, len(list))
|
||||
for _, item := range list {
|
||||
m := maps.Map{}
|
||||
for k2, v2 := range item {
|
||||
m[k2] = v2
|
||||
}
|
||||
cloned = append(cloned, m)
|
||||
}
|
||||
dst[k] = cloned
|
||||
} else {
|
||||
dst[k] = []maps.Map{}
|
||||
}
|
||||
default:
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func nowCustomRecordTime() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
func buildLineText(record maps.Map) string {
|
||||
parts := []string{}
|
||||
if strings.TrimSpace(record.GetString("lineScope")) == "overseas" {
|
||||
parts = append(parts,
|
||||
strings.TrimSpace(record.GetString("lineContinent")),
|
||||
strings.TrimSpace(record.GetString("lineCountry")),
|
||||
)
|
||||
} else {
|
||||
parts = append(parts,
|
||||
strings.TrimSpace(record.GetString("lineCarrier")),
|
||||
strings.TrimSpace(record.GetString("lineRegion")),
|
||||
strings.TrimSpace(record.GetString("lineProvince")),
|
||||
)
|
||||
}
|
||||
|
||||
finalParts := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if len(part) == 0 || part == "默认" {
|
||||
continue
|
||||
}
|
||||
finalParts = append(finalParts, part)
|
||||
}
|
||||
if len(finalParts) == 0 {
|
||||
return "默认"
|
||||
}
|
||||
return strings.Join(finalParts, " / ")
|
||||
}
|
||||
|
||||
func buildRecordValueText(record maps.Map) string {
|
||||
values, ok := record["recordValues"].([]maps.Map)
|
||||
if !ok || len(values) == 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
weightEnabled := record.GetBool("weightEnabled")
|
||||
defaultType := strings.ToUpper(strings.TrimSpace(record.GetString("recordType")))
|
||||
parts := make([]string, 0, len(values))
|
||||
for _, item := range values {
|
||||
value := strings.TrimSpace(item.GetString("value"))
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
recordType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
||||
if len(recordType) == 0 {
|
||||
recordType = defaultType
|
||||
}
|
||||
if recordType != "A" && recordType != "AAAA" {
|
||||
recordType = "A"
|
||||
}
|
||||
part := recordType + " " + value
|
||||
if weightEnabled {
|
||||
part += "(" + strconv.Itoa(item.GetInt("weight")) + ")"
|
||||
} else {
|
||||
// no extra suffix
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "-"
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
@@ -17,10 +17,19 @@ func (this *DeleteAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
app := pickApp(params.AppId)
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "delete")
|
||||
this.Data["app"] = app
|
||||
this.Data["domainCount"] = len(mockDomains(app.GetInt64("id")))
|
||||
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["domainCount"] = len(domains)
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -28,9 +37,10 @@ func (this *DeleteAction) RunPost(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
if params.AppId > 0 {
|
||||
if deleteApp(params.AppId) {
|
||||
deleteAppSettings(params.AppId)
|
||||
deleteCustomRecordsByApp(params.AppId)
|
||||
err := deleteAppByID(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,19 @@ func (this *DomainsAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
app := pickApp(params.AppId)
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建顶部 tabbar
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
|
||||
|
||||
domains := mockDomains(app.GetInt64("id"))
|
||||
for _, domain := range domains {
|
||||
domainName := domain.GetString("name")
|
||||
domain["customRecordCount"] = countCustomRecordsByDomain(app.GetInt64("id"), domainName)
|
||||
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["app"] = app
|
||||
|
||||
@@ -16,7 +16,12 @@ func (this *DomainsCreatePopupAction) Init() {
|
||||
func (this *DomainsCreatePopupAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
this.Data["app"] = pickApp(params.AppId)
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["app"] = app
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -27,6 +32,13 @@ func (this *DomainsCreatePopupAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("domain", params.Domain).Require("please input domain")
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
params.Must.Field("domain", params.Domain).Require("请输入域名")
|
||||
|
||||
err := createDomain(this.Parent(), params.AppId, params.Domain)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ type DomainsDeleteAction struct {
|
||||
func (this *DomainsDeleteAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
_ = params.DomainId
|
||||
if params.DomainId > 0 {
|
||||
err := deleteDomain(this.Parent(), params.DomainId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func maskSecret(secret string) string {
|
||||
secret = strings.TrimSpace(secret)
|
||||
if len(secret) < 4 {
|
||||
return "******"
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
for i := 0; i < len(secret); i++ {
|
||||
if secret[i] == '_' {
|
||||
prefix = secret[:i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(prefix) == 0 {
|
||||
prefix = secret[:2]
|
||||
}
|
||||
|
||||
if len(secret) <= 8 {
|
||||
return prefix + "xxxx"
|
||||
}
|
||||
return prefix + "xxxxxxxx" + secret[len(secret)-4:]
|
||||
}
|
||||
|
||||
func buildLineText(record maps.Map) string {
|
||||
parts := []string{}
|
||||
if strings.TrimSpace(record.GetString("lineScope")) == "overseas" {
|
||||
parts = append(parts,
|
||||
strings.TrimSpace(record.GetString("lineContinent")),
|
||||
strings.TrimSpace(record.GetString("lineCountry")),
|
||||
)
|
||||
} else {
|
||||
parts = append(parts,
|
||||
strings.TrimSpace(record.GetString("lineCarrier")),
|
||||
strings.TrimSpace(record.GetString("lineRegion")),
|
||||
strings.TrimSpace(record.GetString("lineProvince")),
|
||||
)
|
||||
}
|
||||
|
||||
finalParts := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if len(part) == 0 || part == "默认" {
|
||||
continue
|
||||
}
|
||||
finalParts = append(finalParts, part)
|
||||
}
|
||||
if len(finalParts) == 0 {
|
||||
return "默认"
|
||||
}
|
||||
return strings.Join(finalParts, " / ")
|
||||
}
|
||||
|
||||
func buildRecordValueText(record maps.Map) string {
|
||||
values, ok := record["recordValues"].([]maps.Map)
|
||||
if !ok || len(values) == 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
weightEnabled := record.GetBool("weightEnabled")
|
||||
defaultType := strings.ToUpper(strings.TrimSpace(record.GetString("recordType")))
|
||||
parts := make([]string, 0, len(values))
|
||||
for _, item := range values {
|
||||
value := strings.TrimSpace(item.GetString("value"))
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
recordType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
||||
if len(recordType) == 0 {
|
||||
recordType = defaultType
|
||||
}
|
||||
if recordType != "A" && recordType != "AAAA" {
|
||||
recordType = "A"
|
||||
}
|
||||
part := recordType + " " + value
|
||||
if weightEnabled {
|
||||
part += "(" + strconv.Itoa(item.GetInt("weight")) + ")"
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "-"
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
@@ -18,6 +18,11 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["apps"] = filterApps(params.Keyword, "", "", "")
|
||||
apps, err := listAppMaps(this.Parent(), params.Keyword)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["apps"] = apps
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -15,13 +15,17 @@ func init() {
|
||||
Prefix("/httpdns/apps").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/app", new(AppAction)).
|
||||
Get("/sdk", new(SDKAction)).
|
||||
Get("/sdk", new(SdkAction)).
|
||||
GetPost("/sdk/upload", new(SdkUploadAction)).
|
||||
Post("/sdk/upload/delete", new(SdkUploadDeleteAction)).
|
||||
Get("/sdk/download", new(SdkDownloadAction)).
|
||||
Get("/sdk/doc", new(SdkDocAction)).
|
||||
GetPost("/app/settings", new(AppSettingsAction)).
|
||||
Post("/app/settings/toggleSignEnabled", new(AppSettingsToggleSignEnabledAction)).
|
||||
Post("/app/settings/resetSignSecret", new(AppSettingsResetSignSecretAction)).
|
||||
Get("/domains", new(DomainsAction)).
|
||||
Get("/customRecords", new(CustomRecordsAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
GetPost("/delete", new(DeleteAction)).
|
||||
GetPost("/domains/createPopup", new(DomainsCreatePopupAction)).
|
||||
Post("/domains/delete", new(DomainsDeleteAction)).
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var appStore = struct {
|
||||
sync.RWMutex
|
||||
data []maps.Map
|
||||
}{
|
||||
data: defaultMockApps(),
|
||||
}
|
||||
|
||||
func defaultMockApps() []maps.Map {
|
||||
return []maps.Map{
|
||||
{
|
||||
"id": int64(1),
|
||||
"name": "\u4e3b\u7ad9\u79fb\u52a8\u4e1a\u52a1",
|
||||
"appId": "ab12xc34s2",
|
||||
"clusterId": int64(1),
|
||||
"domainCount": 3,
|
||||
"isOn": true,
|
||||
"authStatus": "enabled",
|
||||
"ecsMode": "auto",
|
||||
"pinningMode": "report",
|
||||
"sanMode": "strict",
|
||||
"riskLevel": "medium",
|
||||
"riskSummary": "Pinning \u5904\u4e8e\u89c2\u5bdf\u6a21\u5f0f",
|
||||
"secretVersion": "v2026.02.20",
|
||||
},
|
||||
{
|
||||
"id": int64(2),
|
||||
"name": "\u89c6\u9891\u7f51\u5173\u4e1a\u52a1",
|
||||
"appId": "vd8992ksm1",
|
||||
"clusterId": int64(2),
|
||||
"domainCount": 1,
|
||||
"isOn": true,
|
||||
"authStatus": "enabled",
|
||||
"ecsMode": "custom",
|
||||
"pinningMode": "enforce",
|
||||
"sanMode": "strict",
|
||||
"riskLevel": "low",
|
||||
"riskSummary": "\u5df2\u542f\u7528\u5f3a\u6821\u9a8c",
|
||||
"secretVersion": "v2026.02.18",
|
||||
},
|
||||
{
|
||||
"id": int64(3),
|
||||
"name": "\u6d77\u5916\u7070\u5ea6\u6d4b\u8bd5",
|
||||
"appId": "ov7711hkq9",
|
||||
"clusterId": int64(1),
|
||||
"domainCount": 2,
|
||||
"isOn": false,
|
||||
"authStatus": "disabled",
|
||||
"ecsMode": "off",
|
||||
"pinningMode": "off",
|
||||
"sanMode": "report",
|
||||
"riskLevel": "high",
|
||||
"riskSummary": "\u5e94\u7528\u5173\u95ed\u4e14\u8bc1\u4e66\u7b56\u7565\u504f\u5f31",
|
||||
"secretVersion": "v2026.01.30",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMap(src maps.Map) maps.Map {
|
||||
dst := maps.Map{}
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func cloneApps(apps []maps.Map) []maps.Map {
|
||||
result := make([]maps.Map, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
result = append(result, cloneMap(app))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mockApps() []maps.Map {
|
||||
appStore.RLock()
|
||||
defer appStore.RUnlock()
|
||||
return cloneApps(appStore.data)
|
||||
}
|
||||
|
||||
func deleteApp(appID int64) bool {
|
||||
if appID <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
appStore.Lock()
|
||||
defer appStore.Unlock()
|
||||
|
||||
found := false
|
||||
filtered := make([]maps.Map, 0, len(appStore.data))
|
||||
for _, app := range appStore.data {
|
||||
if app.GetInt64("id") == appID {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, app)
|
||||
}
|
||||
if found {
|
||||
appStore.data = filtered
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func filterApps(keyword string, riskLevel string, ecsMode string, pinningMode string) []maps.Map {
|
||||
all := mockApps()
|
||||
if len(keyword) == 0 && len(riskLevel) == 0 && len(ecsMode) == 0 && len(pinningMode) == 0 {
|
||||
return all
|
||||
}
|
||||
|
||||
keyword = strings.ToLower(strings.TrimSpace(keyword))
|
||||
result := make([]maps.Map, 0)
|
||||
for _, app := range all {
|
||||
if len(keyword) > 0 {
|
||||
name := strings.ToLower(app.GetString("name"))
|
||||
appID := strings.ToLower(app.GetString("appId"))
|
||||
if !strings.Contains(name, keyword) && !strings.Contains(appID, keyword) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(riskLevel) > 0 && app.GetString("riskLevel") != riskLevel {
|
||||
continue
|
||||
}
|
||||
if len(ecsMode) > 0 && app.GetString("ecsMode") != ecsMode {
|
||||
continue
|
||||
}
|
||||
if len(pinningMode) > 0 && app.GetString("pinningMode") != pinningMode {
|
||||
continue
|
||||
}
|
||||
result = append(result, app)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func pickApp(appID int64) maps.Map {
|
||||
apps := mockApps()
|
||||
if len(apps) == 0 {
|
||||
return maps.Map{
|
||||
"id": int64(0),
|
||||
"name": "",
|
||||
"appId": "",
|
||||
"clusterId": int64(0),
|
||||
}
|
||||
}
|
||||
|
||||
if appID <= 0 {
|
||||
return apps[0]
|
||||
}
|
||||
for _, app := range apps {
|
||||
if app.GetInt64("id") == appID {
|
||||
return app
|
||||
}
|
||||
}
|
||||
return apps[0]
|
||||
}
|
||||
|
||||
func mockDomains(appID int64) []maps.Map {
|
||||
_ = appID
|
||||
return []maps.Map{
|
||||
{
|
||||
"id": int64(101),
|
||||
"name": "api.business.com",
|
||||
},
|
||||
{
|
||||
"id": int64(102),
|
||||
"name": "payment.business.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pickDomain(domainID int64) maps.Map {
|
||||
domains := mockDomains(0)
|
||||
if domainID <= 0 {
|
||||
return domains[0]
|
||||
}
|
||||
for _, domain := range domains {
|
||||
if domain.GetInt64("id") == domainID {
|
||||
return domain
|
||||
}
|
||||
}
|
||||
return domains[0]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type PoliciesAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *PoliciesAction) Init() {
|
||||
this.Nav("httpdns", "app", "")
|
||||
}
|
||||
|
||||
func (this *PoliciesAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["policies"] = loadGlobalPolicies()
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *PoliciesAction) RunPost(params struct {
|
||||
DefaultTTL int
|
||||
DefaultSniPolicy string
|
||||
DefaultFallbackMs int
|
||||
ECSMode string
|
||||
ECSIPv4Prefix int
|
||||
ECSIPv6Prefix int
|
||||
PinningMode string
|
||||
SANMode string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("defaultTTL", params.DefaultTTL).Gt(0, "默认 TTL 需要大于 0")
|
||||
params.Must.Field("defaultFallbackMs", params.DefaultFallbackMs).Gt(0, "默认超时需要大于 0")
|
||||
|
||||
if params.DefaultTTL > 86400 {
|
||||
this.Fail("默认 TTL 不能超过 86400 秒")
|
||||
return
|
||||
}
|
||||
if params.DefaultFallbackMs > 10000 {
|
||||
this.Fail("默认超时不能超过 10000 毫秒")
|
||||
return
|
||||
}
|
||||
if params.DefaultSniPolicy != "level1" && params.DefaultSniPolicy != "level2" && params.DefaultSniPolicy != "level3" {
|
||||
this.Fail("默认 SNI 等级不正确")
|
||||
return
|
||||
}
|
||||
if params.ECSMode != "off" && params.ECSMode != "auto" && params.ECSMode != "custom" {
|
||||
this.Fail("ECS 模式不正确")
|
||||
return
|
||||
}
|
||||
if params.ECSIPv4Prefix < 0 || params.ECSIPv4Prefix > 32 {
|
||||
this.Fail("IPv4 掩码范围是 0-32")
|
||||
return
|
||||
}
|
||||
if params.ECSIPv6Prefix < 0 || params.ECSIPv6Prefix > 128 {
|
||||
this.Fail("IPv6 掩码范围是 0-128")
|
||||
return
|
||||
}
|
||||
if params.PinningMode != "off" && params.PinningMode != "report" && params.PinningMode != "enforce" {
|
||||
this.Fail("Pinning 策略不正确")
|
||||
return
|
||||
}
|
||||
if params.SANMode != "off" && params.SANMode != "report" && params.SANMode != "strict" {
|
||||
this.Fail("SAN 策略不正确")
|
||||
return
|
||||
}
|
||||
|
||||
saveGlobalPolicies(maps.Map{
|
||||
"defaultTTL": params.DefaultTTL,
|
||||
"defaultSniPolicy": params.DefaultSniPolicy,
|
||||
"defaultFallbackMs": params.DefaultFallbackMs,
|
||||
"ecsMode": params.ECSMode,
|
||||
"ecsIPv4Prefix": params.ECSIPv4Prefix,
|
||||
"ecsIPv6Prefix": params.ECSIPv6Prefix,
|
||||
"pinningMode": params.PinningMode,
|
||||
"sanMode": params.SANMode,
|
||||
})
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var globalPoliciesStore = struct {
|
||||
sync.RWMutex
|
||||
data maps.Map
|
||||
}{
|
||||
data: maps.Map{
|
||||
"defaultTTL": 30,
|
||||
"defaultSniPolicy": "level2",
|
||||
"defaultFallbackMs": 300,
|
||||
"ecsMode": "auto",
|
||||
"ecsIPv4Prefix": 24,
|
||||
"ecsIPv6Prefix": 56,
|
||||
"pinningMode": "report",
|
||||
"sanMode": "strict",
|
||||
},
|
||||
}
|
||||
|
||||
func loadGlobalPolicies() maps.Map {
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
|
||||
return maps.Map{
|
||||
"defaultTTL": globalPoliciesStore.data.GetInt("defaultTTL"),
|
||||
"defaultSniPolicy": globalPoliciesStore.data.GetString("defaultSniPolicy"),
|
||||
"defaultFallbackMs": globalPoliciesStore.data.GetInt("defaultFallbackMs"),
|
||||
"ecsMode": globalPoliciesStore.data.GetString("ecsMode"),
|
||||
"ecsIPv4Prefix": globalPoliciesStore.data.GetInt("ecsIPv4Prefix"),
|
||||
"ecsIPv6Prefix": globalPoliciesStore.data.GetInt("ecsIPv6Prefix"),
|
||||
"pinningMode": globalPoliciesStore.data.GetString("pinningMode"),
|
||||
"sanMode": globalPoliciesStore.data.GetString("sanMode"),
|
||||
}
|
||||
}
|
||||
|
||||
func saveGlobalPolicies(policies maps.Map) {
|
||||
globalPoliciesStore.Lock()
|
||||
globalPoliciesStore.data = maps.Map{
|
||||
"defaultTTL": policies.GetInt("defaultTTL"),
|
||||
"defaultSniPolicy": policies.GetString("defaultSniPolicy"),
|
||||
"defaultFallbackMs": policies.GetInt("defaultFallbackMs"),
|
||||
"ecsMode": policies.GetString("ecsMode"),
|
||||
"ecsIPv4Prefix": policies.GetInt("ecsIPv4Prefix"),
|
||||
"ecsIPv6Prefix": policies.GetInt("ecsIPv6Prefix"),
|
||||
"pinningMode": policies.GetString("pinningMode"),
|
||||
"sanMode": policies.GetString("sanMode"),
|
||||
}
|
||||
globalPoliciesStore.Unlock()
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSAppRPC().ListHTTPDNSApps(parent.AdminContext(), &pb.ListHTTPDNSAppsRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
Keyword: strings.TrimSpace(keyword),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(resp.GetApps()))
|
||||
for _, app := range resp.GetApps() {
|
||||
domainResp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||
AppDbId: app.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains()))))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, error) {
|
||||
if appDbId > 0 {
|
||||
resp, err := parent.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(parent.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||
AppDbId: appDbId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.GetApp() != nil {
|
||||
domainResp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||
AppDbId: appDbId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains()))), nil
|
||||
}
|
||||
}
|
||||
|
||||
apps, err := listAppMaps(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return maps.Map{
|
||||
"id": int64(0),
|
||||
"name": "",
|
||||
"appId": "",
|
||||
}, nil
|
||||
}
|
||||
return apps[0], nil
|
||||
}
|
||||
|
||||
func createApp(parent *actionutils.ParentAction, name string, primaryClusterId int64, backupClusterId int64) (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,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resp.GetAppDbId(), nil
|
||||
}
|
||||
|
||||
func deleteAppByID(parent *actionutils.ParentAction, appDbId int64) error {
|
||||
_, err := parent.RPC().HTTPDNSAppRPC().DeleteHTTPDNSApp(parent.AdminContext(), &pb.DeleteHTTPDNSAppRequest{
|
||||
AppDbId: appDbId,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func updateAppSettings(parent *actionutils.ParentAction, appDbId int64, name string, primaryClusterId int64, backupClusterId int64, 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,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func updateAppSignEnabled(parent *actionutils.ParentAction, appDbId int64, signEnabled bool) error {
|
||||
_, err := parent.RPC().HTTPDNSAppRPC().UpdateHTTPDNSAppSignEnabled(parent.AdminContext(), &pb.UpdateHTTPDNSAppSignEnabledRequest{
|
||||
AppDbId: appDbId,
|
||||
SignEnabled: signEnabled,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func resetAppSignSecret(parent *actionutils.ParentAction, appDbId int64) (*pb.ResetHTTPDNSAppSignSecretResponse, error) {
|
||||
return parent.RPC().HTTPDNSAppRPC().ResetHTTPDNSAppSignSecret(parent.AdminContext(), &pb.ResetHTTPDNSAppSignSecretRequest{
|
||||
AppDbId: appDbId,
|
||||
})
|
||||
}
|
||||
|
||||
func listDomainMaps(parent *actionutils.ParentAction, appDbId int64, keyword string) ([]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||
AppDbId: appDbId,
|
||||
Keyword: strings.TrimSpace(keyword),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(resp.GetDomains()))
|
||||
for _, domain := range resp.GetDomains() {
|
||||
result = append(result, maps.Map{
|
||||
"id": domain.GetId(),
|
||||
"name": domain.GetDomain(),
|
||||
"isOn": domain.GetIsOn(),
|
||||
"customRecordCount": domain.GetRuleCount(),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createDomain(parent *actionutils.ParentAction, appDbId int64, domain string) error {
|
||||
_, err := parent.RPC().HTTPDNSDomainRPC().CreateHTTPDNSDomain(parent.AdminContext(), &pb.CreateHTTPDNSDomainRequest{
|
||||
AppDbId: appDbId,
|
||||
Domain: strings.TrimSpace(domain),
|
||||
IsOn: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteDomain(parent *actionutils.ParentAction, domainId int64) error {
|
||||
_, err := parent.RPC().HTTPDNSDomainRPC().DeleteHTTPDNSDomain(parent.AdminContext(), &pb.DeleteHTTPDNSDomainRequest{
|
||||
DomainId: domainId,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func findDomainMap(domains []maps.Map, domainID int64) maps.Map {
|
||||
if len(domains) == 0 {
|
||||
return maps.Map{}
|
||||
}
|
||||
if domainID <= 0 {
|
||||
return domains[0]
|
||||
}
|
||||
for _, domain := range domains {
|
||||
if domain.GetInt64("id") == domainID {
|
||||
return domain
|
||||
}
|
||||
}
|
||||
return domains[0]
|
||||
}
|
||||
|
||||
func listCustomRuleMaps(parent *actionutils.ParentAction, domainId int64) ([]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSRuleRPC().ListHTTPDNSCustomRulesWithDomainId(parent.AdminContext(), &pb.ListHTTPDNSCustomRulesWithDomainIdRequest{
|
||||
DomainId: domainId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(resp.GetRules()))
|
||||
for _, rule := range resp.GetRules() {
|
||||
recordValues := make([]maps.Map, 0, len(rule.GetRecords()))
|
||||
recordType := "A"
|
||||
weightEnabled := false
|
||||
for _, record := range rule.GetRecords() {
|
||||
if len(recordType) == 0 {
|
||||
recordType = strings.ToUpper(strings.TrimSpace(record.GetRecordType()))
|
||||
}
|
||||
if record.GetWeight() > 0 && record.GetWeight() != 100 {
|
||||
weightEnabled = true
|
||||
}
|
||||
recordValues = append(recordValues, maps.Map{
|
||||
"type": strings.ToUpper(strings.TrimSpace(record.GetRecordType())),
|
||||
"value": record.GetRecordValue(),
|
||||
"weight": record.GetWeight(),
|
||||
})
|
||||
}
|
||||
if len(recordValues) == 0 {
|
||||
recordValues = append(recordValues, maps.Map{
|
||||
"type": "A",
|
||||
"value": "",
|
||||
"weight": 100,
|
||||
})
|
||||
}
|
||||
|
||||
item := maps.Map{
|
||||
"id": rule.GetId(),
|
||||
"lineScope": rule.GetLineScope(),
|
||||
"lineCarrier": defaultLineField(rule.GetLineCarrier()),
|
||||
"lineRegion": defaultLineField(rule.GetLineRegion()),
|
||||
"lineProvince": defaultLineField(rule.GetLineProvince()),
|
||||
"lineContinent": defaultLineField(rule.GetLineContinent()),
|
||||
"lineCountry": defaultLineField(rule.GetLineCountry()),
|
||||
"ruleName": rule.GetRuleName(),
|
||||
"recordType": recordType,
|
||||
"recordValues": recordValues,
|
||||
"weightEnabled": weightEnabled,
|
||||
"ttl": rule.GetTtl(),
|
||||
"isOn": rule.GetIsOn(),
|
||||
"updatedAt": formatDateTime(rule.GetUpdatedAt()),
|
||||
}
|
||||
item["lineText"] = buildLineText(item)
|
||||
item["recordValueText"] = buildRecordValueText(item)
|
||||
result = append(result, item)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createCustomRule(parent *actionutils.ParentAction, rule *pb.HTTPDNSCustomRule) (int64, error) {
|
||||
resp, err := parent.RPC().HTTPDNSRuleRPC().CreateHTTPDNSCustomRule(parent.AdminContext(), &pb.CreateHTTPDNSCustomRuleRequest{
|
||||
Rule: rule,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resp.GetRuleId(), nil
|
||||
}
|
||||
|
||||
func updateCustomRule(parent *actionutils.ParentAction, rule *pb.HTTPDNSCustomRule) error {
|
||||
_, err := parent.RPC().HTTPDNSRuleRPC().UpdateHTTPDNSCustomRule(parent.AdminContext(), &pb.UpdateHTTPDNSCustomRuleRequest{
|
||||
Rule: rule,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteCustomRule(parent *actionutils.ParentAction, ruleId int64) error {
|
||||
_, err := parent.RPC().HTTPDNSRuleRPC().DeleteHTTPDNSCustomRule(parent.AdminContext(), &pb.DeleteHTTPDNSCustomRuleRequest{
|
||||
RuleId: ruleId,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func toggleCustomRule(parent *actionutils.ParentAction, ruleId int64, isOn bool) error {
|
||||
_, err := parent.RPC().HTTPDNSRuleRPC().UpdateHTTPDNSCustomRuleStatus(parent.AdminContext(), &pb.UpdateHTTPDNSCustomRuleStatusRequest{
|
||||
RuleId: ruleId,
|
||||
IsOn: isOn,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
|
||||
signSecret := app.GetSignSecret()
|
||||
return maps.Map{
|
||||
"id": app.GetId(),
|
||||
"name": app.GetName(),
|
||||
"appId": app.GetAppId(),
|
||||
"clusterId": app.GetPrimaryClusterId(),
|
||||
"primaryClusterId": app.GetPrimaryClusterId(),
|
||||
"backupClusterId": app.GetBackupClusterId(),
|
||||
"userId": app.GetUserId(),
|
||||
"isOn": app.GetIsOn(),
|
||||
"domainCount": domainCount,
|
||||
"sniPolicyText": "隐匿 SNI",
|
||||
"signEnabled": app.GetSignEnabled(),
|
||||
"signSecretPlain": signSecret,
|
||||
"signSecretMasked": maskSecret(signSecret),
|
||||
"signSecretUpdated": formatDateTime(app.GetSignUpdatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultLineField(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 {
|
||||
return "默认"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func formatDateTime(ts int64) string {
|
||||
if ts <= 0 {
|
||||
return ""
|
||||
}
|
||||
return timeutil.FormatTime("Y-m-d H:i:s", ts)
|
||||
}
|
||||
@@ -5,23 +5,26 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
)
|
||||
|
||||
type SDKAction struct {
|
||||
type SdkAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SDKAction) Init() {
|
||||
func (this *SdkAction) Init() {
|
||||
this.Nav("httpdns", "app", "sdk")
|
||||
}
|
||||
|
||||
func (this *SDKAction) RunGet(params struct {
|
||||
func (this *SdkAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
app := pickApp(params.AppId)
|
||||
|
||||
// 构建顶部 tabbar
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
|
||||
|
||||
this.Data["app"] = app
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SdkDocAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SdkDocAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *SdkDocAction) RunGet(params struct {
|
||||
Platform string
|
||||
}) {
|
||||
platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var data []byte
|
||||
uploadedDocPath := findUploadedSDKDocPath(platform)
|
||||
if len(uploadedDocPath) > 0 {
|
||||
data, err = os.ReadFile(uploadedDocPath)
|
||||
}
|
||||
|
||||
sdkRoot, sdkRootErr := findSDKRoot()
|
||||
if len(data) == 0 && sdkRootErr == nil {
|
||||
readmePath := filepath.Join(sdkRoot, readmeRelativePath)
|
||||
data, err = os.ReadFile(readmePath)
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
localDocPath := findLocalSDKDocPath(platform)
|
||||
if len(localDocPath) > 0 {
|
||||
data, err = os.ReadFile(localDocPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) == 0 || err != nil {
|
||||
this.Fail("当前服务器未找到 SDK 集成文档,请先在“SDK 集成”页面上传对应平台文档")
|
||||
return
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Type", "text/markdown; charset=utf-8")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\"httpdns-sdk-"+strings.ToLower(platform)+"-README.md\";")
|
||||
_, _ = this.ResponseWriter.Write(data)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SdkDownloadAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SdkDownloadAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *SdkDownloadAction) RunGet(params struct {
|
||||
Platform string
|
||||
}) {
|
||||
_, _, _, filename, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
archivePath := findSDKArchivePath(filename)
|
||||
if len(archivePath) == 0 {
|
||||
this.Fail("当前服务器未找到 SDK 包,请先在“SDK 集成”页面上传对应平台包: " + filename)
|
||||
return
|
||||
}
|
||||
|
||||
fp, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
this.Fail("打开 SDK 包失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
this.AddHeader("Content-Type", "application/zip")
|
||||
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
|
||||
this.AddHeader("X-Accel-Buffering", "no")
|
||||
_, _ = io.Copy(this.ResponseWriter, fp)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sdkUploadDir() string {
|
||||
return filepath.Clean(Tea.Root + "/data/httpdns/sdk")
|
||||
}
|
||||
|
||||
func findFirstExistingDir(paths []string) string {
|
||||
for _, path := range paths {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil && stat.IsDir() {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findFirstExistingFile(paths []string) string {
|
||||
for _, path := range paths {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil && !stat.IsDir() {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findNewestExistingFile(paths []string) string {
|
||||
type fileInfo struct {
|
||||
path string
|
||||
modTime time.Time
|
||||
}
|
||||
result := fileInfo{}
|
||||
for _, path := range paths {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil || stat.IsDir() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
return result.path
|
||||
}
|
||||
|
||||
func findSDKRoot() (string, error) {
|
||||
candidates := []string{
|
||||
filepath.Clean(Tea.Root + "/EdgeHttpDNS/sdk"),
|
||||
filepath.Clean(Tea.Root + "/edge-httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/edge-httpdns/edge-httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../EdgeHttpDNS/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../../EdgeHttpDNS/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../edge-httpdns/sdk"),
|
||||
filepath.Clean(Tea.Root + "/../../edge-httpdns/sdk"),
|
||||
}
|
||||
|
||||
dir := findFirstExistingDir(candidates)
|
||||
if len(dir) > 0 {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
return "", errors.New("SDK files are not found on current server")
|
||||
}
|
||||
|
||||
func resolveSDKPlatform(platform string) (key string, relativeDir string, readmeRelativePath string, downloadFilename string, err error) {
|
||||
switch strings.ToLower(strings.TrimSpace(platform)) {
|
||||
case "android":
|
||||
return "android", "android", "android/README.md", "httpdns-sdk-android.zip", nil
|
||||
case "ios":
|
||||
return "ios", "ios", "ios/README.md", "httpdns-sdk-ios.zip", nil
|
||||
case "flutter":
|
||||
return "flutter", "flutter/aliyun_httpdns", "flutter/aliyun_httpdns/README.md", "httpdns-sdk-flutter.zip", nil
|
||||
default:
|
||||
return "", "", "", "", errors.New("invalid platform, expected one of: android, ios, flutter")
|
||||
}
|
||||
}
|
||||
|
||||
func findSDKArchivePath(downloadFilename string) string {
|
||||
searchDirs := []string{sdkUploadDir()}
|
||||
|
||||
// 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
|
||||
base := strings.TrimSuffix(downloadFilename, ".zip")
|
||||
patternName := base + "-*.zip"
|
||||
matches := []string{}
|
||||
for _, dir := range searchDirs {
|
||||
found, _ := filepath.Glob(filepath.Join(dir, patternName))
|
||||
for _, file := range found {
|
||||
stat, err := os.Stat(file)
|
||||
if err == nil && !stat.IsDir() {
|
||||
matches = append(matches, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(matches) > 0 {
|
||||
return findNewestExistingFile(matches)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func findUploadedSDKDocPath(platform 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 {
|
||||
return ""
|
||||
}
|
||||
sort.Strings(matches)
|
||||
return findNewestExistingFile(matches)
|
||||
}
|
||||
|
||||
func findLocalSDKDocPath(platform string) string {
|
||||
filename := strings.ToLower(strings.TrimSpace(platform)) + ".md"
|
||||
candidates := []string{
|
||||
filepath.Clean(Tea.Root + "/edge-admin/web/views/@default/httpdns/apps/docs/" + filename),
|
||||
filepath.Clean(Tea.Root + "/EdgeAdmin/web/views/@default/httpdns/apps/docs/" + filename),
|
||||
}
|
||||
return findFirstExistingFile(candidates)
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type SdkUploadAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SdkUploadAction) Init() {
|
||||
this.Nav("httpdns", "app", "sdk")
|
||||
}
|
||||
|
||||
func (this *SdkUploadAction) RunGet(params struct {
|
||||
AppId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
|
||||
app, err := findAppMap(this.Parent(), params.AppId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
|
||||
|
||||
this.Data["app"] = app
|
||||
this.Data["defaultVersion"] = "1.0.0"
|
||||
this.Data["uploadedFiles"] = listUploadedSDKFiles()
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *SdkUploadAction) RunPost(params struct {
|
||||
AppId int64
|
||||
Platform string
|
||||
Version string
|
||||
SDKFile *actions.File
|
||||
DocFile *actions.File
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||
|
||||
platform, _, _, downloadFilename, err := resolveSDKPlatform(params.Platform)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
version, err := normalizeSDKVersion(params.Version)
|
||||
if err != nil {
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if params.SDKFile == nil && params.DocFile == nil {
|
||||
this.Fail("请至少上传一个文件")
|
||||
return
|
||||
}
|
||||
|
||||
uploadDir := sdkUploadDir()
|
||||
err = os.MkdirAll(uploadDir, 0755)
|
||||
if err != nil {
|
||||
this.Fail("创建上传目录失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
if readErr != nil {
|
||||
this.Fail("读取 SDK 包失败: " + readErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
baseName := strings.TrimSuffix(downloadFilename, ".zip")
|
||||
err = saveSDKUploadFile(uploadDir, downloadFilename, sdkData)
|
||||
if err == nil {
|
||||
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
|
||||
}
|
||||
if err != nil {
|
||||
this.Fail("保存 SDK 包失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.DocFile != nil {
|
||||
docName := strings.ToLower(strings.TrimSpace(params.DocFile.Filename))
|
||||
if !strings.HasSuffix(docName, ".md") {
|
||||
this.Fail("集成文档仅支持 .md 文件")
|
||||
return
|
||||
}
|
||||
|
||||
docData, readErr := params.DocFile.Read()
|
||||
if readErr != nil {
|
||||
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 err != nil {
|
||||
this.Fail("保存集成文档失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func normalizeSDKVersion(version string) (string, error) {
|
||||
version = strings.TrimSpace(version)
|
||||
if len(version) == 0 {
|
||||
version = "1.0.0"
|
||||
}
|
||||
for _, c := range version {
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' {
|
||||
continue
|
||||
}
|
||||
return "", errors.New("版本号格式不正确")
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
|
||||
targetPath := filepath.Join(baseDir, filename)
|
||||
tmpPath := targetPath + ".tmp"
|
||||
err := os.WriteFile(tmpPath, data, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpPath, targetPath)
|
||||
}
|
||||
|
||||
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
|
||||
FileType string
|
||||
Version string
|
||||
SizeBytes int64
|
||||
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 {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
if items[i].UpdatedAt == items[j].UpdatedAt {
|
||||
return items[i].Name > items[j].Name
|
||||
}
|
||||
return items[i].UpdatedAt > items[j].UpdatedAt
|
||||
})
|
||||
|
||||
result := make([]map[string]interface{}, 0, len(items))
|
||||
for _, item := 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"),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseSDKUploadFilename(filename string) (platform string, version string, fileType string, ok bool) {
|
||||
if !strings.HasPrefix(filename, "httpdns-sdk-") {
|
||||
return "", "", "", false
|
||||
}
|
||||
|
||||
ext := ""
|
||||
switch {
|
||||
case strings.HasSuffix(filename, ".zip"):
|
||||
ext = ".zip"
|
||||
fileType = "SDK包"
|
||||
case strings.HasSuffix(filename, ".md"):
|
||||
ext = ".md"
|
||||
fileType = "集成文档"
|
||||
default:
|
||||
return "", "", "", false
|
||||
}
|
||||
|
||||
main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext)
|
||||
version = "latest"
|
||||
if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) {
|
||||
version = main[idx+2:]
|
||||
main = main[:idx]
|
||||
}
|
||||
|
||||
main = strings.ToLower(strings.TrimSpace(main))
|
||||
switch main {
|
||||
case "android", "ios", "flutter":
|
||||
platform = main
|
||||
return platform, version, fileType, true
|
||||
default:
|
||||
return "", "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
func formatSDKFileSize(size int64) string {
|
||||
if size < 1024 {
|
||||
return strconv.FormatInt(size, 10) + " B"
|
||||
}
|
||||
sizeKB := float64(size) / 1024
|
||||
if sizeKB < 1024 {
|
||||
return strconv.FormatFloat(sizeKB, 'f', 1, 64) + " KB"
|
||||
}
|
||||
sizeMB := sizeKB / 1024
|
||||
if sizeMB < 1024 {
|
||||
return strconv.FormatFloat(sizeMB, 'f', 1, 64) + " MB"
|
||||
}
|
||||
sizeGB := sizeMB / 1024
|
||||
return strconv.FormatFloat(sizeGB, 'f', 1, 64) + " GB"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SdkUploadDeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SdkUploadDeleteAction) Init() {
|
||||
this.Nav("httpdns", "app", "sdk")
|
||||
}
|
||||
|
||||
func (this *SdkUploadDeleteAction) RunPost(params struct {
|
||||
AppId int64
|
||||
Filename string
|
||||
}) {
|
||||
if params.AppId <= 0 {
|
||||
this.Fail("请选择应用")
|
||||
return
|
||||
}
|
||||
|
||||
filename := strings.TrimSpace(params.Filename)
|
||||
if len(filename) == 0 {
|
||||
this.Fail("文件名不能为空")
|
||||
return
|
||||
}
|
||||
if strings.Contains(filename, "/") || strings.Contains(filename, "\\") || strings.Contains(filename, "..") {
|
||||
this.Fail("文件名不合法")
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(filename, "httpdns-sdk-") {
|
||||
this.Fail("不允许删除该文件")
|
||||
return
|
||||
}
|
||||
if !(strings.HasSuffix(filename, ".zip") || strings.HasSuffix(filename, ".md")) {
|
||||
this.Fail("不允许删除该文件")
|
||||
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
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package clusters
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
)
|
||||
|
||||
type CertsAction struct {
|
||||
@@ -15,7 +13,5 @@ func (this *CertsAction) Init() {
|
||||
}
|
||||
|
||||
func (this *CertsAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["certs"] = policies.LoadPublicSNICertificates()
|
||||
this.Show()
|
||||
this.RedirectURL("/httpdns/clusters")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type ClusterAction struct {
|
||||
@@ -20,19 +23,75 @@ func (this *ClusterAction) RunGet(params struct {
|
||||
Keyword string
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
cluster := pickCluster(params.ClusterId)
|
||||
|
||||
// 构建顶部 tabbar
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
||||
|
||||
nodes, err := listNodeMaps(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
nodes = filterClusterNodes(nodes, params.InstalledState, params.ActiveState, params.Keyword)
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["cluster"] = cluster
|
||||
this.Data["installState"] = params.InstalledState
|
||||
this.Data["activeState"] = params.ActiveState
|
||||
this.Data["keyword"] = params.Keyword
|
||||
nodes := mockNodes(params.ClusterId, params.InstalledState, params.ActiveState, params.Keyword)
|
||||
this.Data["nodes"] = nodes
|
||||
this.Data["hasNodes"] = len(nodes) > 0
|
||||
this.Data["page"] = ""
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func filterClusterNodes(nodes []maps.Map, installedState int, activeState int, keyword string) []maps.Map {
|
||||
keyword = strings.ToLower(strings.TrimSpace(keyword))
|
||||
|
||||
result := make([]maps.Map, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
isInstalled := node.GetBool("isInstalled")
|
||||
if installedState == 1 && !isInstalled {
|
||||
continue
|
||||
}
|
||||
if installedState == 2 && isInstalled {
|
||||
continue
|
||||
}
|
||||
|
||||
status := node.GetMap("status")
|
||||
isOnline := node.GetBool("isOn") && node.GetBool("isUp") && status.GetBool("isActive")
|
||||
if activeState == 1 && !isOnline {
|
||||
continue
|
||||
}
|
||||
if activeState == 2 && isOnline {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(keyword) > 0 {
|
||||
hit := strings.Contains(strings.ToLower(node.GetString("name")), keyword)
|
||||
if !hit {
|
||||
ipAddresses, ok := node["ipAddresses"].([]maps.Map)
|
||||
if ok {
|
||||
for _, ipAddr := range ipAddresses {
|
||||
if strings.Contains(strings.ToLower(ipAddr.GetString("ip")), keyword) {
|
||||
hit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hit {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, node)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -18,51 +20,37 @@ func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
||||
|
||||
this.Data["nodeDatetime"] = "2026-02-22 12:00:00"
|
||||
this.Data["nodeTimeDiff"] = 0
|
||||
this.Data["shouldUpgrade"] = false
|
||||
this.Data["newVersion"] = ""
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": params.NodeId,
|
||||
"name": "Mock HTTPDNS Node",
|
||||
"ipAddresses": []maps.Map{{"ip": "100.200.100.200", "name": "Public IP", "canAccess": true, "isOn": true, "isUp": true}},
|
||||
"cluster": maps.Map{"id": params.ClusterId, "name": "Mock Cluster", "installDir": "/opt/edge-httpdns"},
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
"isInstalled": true,
|
||||
"uniqueId": "m-1234567890",
|
||||
"secret": "mock-secret-key",
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
"apiNodeAddrs": []string{"192.168.1.100:8001"},
|
||||
"login": nil,
|
||||
|
||||
"status": maps.Map{
|
||||
"isActive": true,
|
||||
"updatedAt": 1670000000,
|
||||
"hostname": "node-01.local",
|
||||
"cpuUsage": 0.15,
|
||||
"cpuUsageText": "15.00%",
|
||||
"memUsage": 0.45,
|
||||
"memUsageText": "45.00%",
|
||||
"connectionCount": 100,
|
||||
"buildVersion": "1.0.0",
|
||||
"cpuPhysicalCount": 4,
|
||||
"cpuLogicalCount": 8,
|
||||
"load1m": "0.50",
|
||||
"load5m": "0.60",
|
||||
"load15m": "0.70",
|
||||
"cacheTotalDiskSize": "10G",
|
||||
"cacheTotalMemorySize": "2G",
|
||||
"exePath": "/opt/edge-httpdns/bin/edge-httpdns",
|
||||
"apiSuccessPercent": 100.0,
|
||||
"apiAvgCostSeconds": 0.05,
|
||||
},
|
||||
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["node"] = node
|
||||
this.Data["currentCluster"] = cluster
|
||||
|
||||
status := node.GetMap("status")
|
||||
updatedAt := status.GetInt64("updatedAt")
|
||||
nodeDatetime := ""
|
||||
nodeTimeDiff := int64(0)
|
||||
if updatedAt > 0 {
|
||||
nodeDatetime = timeutil.FormatTime("Y-m-d H:i:s", updatedAt)
|
||||
nodeTimeDiff = time.Now().Unix() - updatedAt
|
||||
if nodeTimeDiff < 0 {
|
||||
nodeTimeDiff = -nodeTimeDiff
|
||||
}
|
||||
}
|
||||
this.Data["nodeDatetime"] = nodeDatetime
|
||||
this.Data["nodeTimeDiff"] = nodeTimeDiff
|
||||
|
||||
this.Data["shouldUpgrade"] = false
|
||||
this.Data["newVersion"] = ""
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type InstallAction struct {
|
||||
@@ -14,28 +18,87 @@ func (this *InstallAction) Init() {
|
||||
this.SecondMenu("nodes")
|
||||
}
|
||||
|
||||
func (this *InstallAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
||||
func (this *InstallAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
||||
this.Data["currentCluster"] = cluster
|
||||
this.Data["node"] = node
|
||||
this.Data["installStatus"] = node.GetMap("installStatus")
|
||||
|
||||
this.Data["apiEndpoints"] = "\"http://127.0.0.1:7788\""
|
||||
this.Data["sshAddr"] = "192.168.1.100:22"
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": params.NodeId,
|
||||
"name": "Mock Node",
|
||||
"isInstalled": false,
|
||||
"uniqueId": "m-1234567890",
|
||||
"secret": "mock-secret-key",
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
"cluster": maps.Map{"installDir": "/opt/edge-httpdns"},
|
||||
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["installStatus"] = nil
|
||||
|
||||
apiEndpoints := make([]string, 0, 8)
|
||||
for _, apiNode := range apiNodesResp.GetApiNodes() {
|
||||
if !apiNode.GetIsOn() {
|
||||
continue
|
||||
}
|
||||
apiEndpoints = append(apiEndpoints, apiNode.GetAccessAddrs()...)
|
||||
}
|
||||
if len(apiEndpoints) == 0 {
|
||||
apiEndpoints = []string{"http://127.0.0.1:7788"}
|
||||
}
|
||||
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
||||
this.Data["sshAddr"] = ""
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *InstallAction) RunPost(params struct{ NodeId int64 }) {
|
||||
func (this *InstallAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
nodeResp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
node := nodeResp.GetNode()
|
||||
if node == nil {
|
||||
this.Fail("节点不存在")
|
||||
return
|
||||
}
|
||||
|
||||
existingStatus := map[string]interface{}{}
|
||||
if len(node.GetInstallStatusJSON()) > 0 {
|
||||
_ = json.Unmarshal(node.GetInstallStatusJSON(), &existingStatus)
|
||||
}
|
||||
|
||||
existingStatus["isRunning"] = true
|
||||
existingStatus["isFinished"] = false
|
||||
existingStatus["isOk"] = false
|
||||
existingStatus["error"] = ""
|
||||
existingStatus["errorCode"] = ""
|
||||
existingStatus["updatedAt"] = time.Now().Unix()
|
||||
|
||||
installStatusJSON, _ := json.Marshal(existingStatus)
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: node.GetIsUp(),
|
||||
IsInstalled: false,
|
||||
IsActive: node.GetIsActive(),
|
||||
StatusJSON: node.GetStatusJSON(),
|
||||
InstallStatusJSON: installStatusJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -14,18 +18,65 @@ func (this *LogsAction) Init() {
|
||||
this.SecondMenu("nodes")
|
||||
}
|
||||
|
||||
func (this *LogsAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
||||
func (this *LogsAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
DayFrom string
|
||||
DayTo string
|
||||
Level string
|
||||
Keyword string
|
||||
}) {
|
||||
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
||||
this.Data["currentCluster"] = cluster
|
||||
this.Data["node"] = node
|
||||
this.Data["dayFrom"] = params.DayFrom
|
||||
this.Data["dayTo"] = params.DayTo
|
||||
this.Data["level"] = params.Level
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
this.Data["dayFrom"] = ""
|
||||
this.Data["dayTo"] = ""
|
||||
this.Data["keyword"] = ""
|
||||
this.Data["level"] = ""
|
||||
this.Data["logs"] = []maps.Map{}
|
||||
day := strings.TrimSpace(params.DayFrom)
|
||||
if len(day) == 0 {
|
||||
day = strings.TrimSpace(params.DayTo)
|
||||
}
|
||||
|
||||
resp, err := this.RPC().HTTPDNSRuntimeLogRPC().ListHTTPDNSRuntimeLogs(this.AdminContext(), &pb.ListHTTPDNSRuntimeLogsRequest{
|
||||
Day: day,
|
||||
ClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
Level: strings.TrimSpace(params.Level),
|
||||
Keyword: strings.TrimSpace(params.Keyword),
|
||||
Offset: 0,
|
||||
Size: 100,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
logs := make([]maps.Map, 0, len(resp.GetLogs()))
|
||||
for _, item := range resp.GetLogs() {
|
||||
logs = append(logs, maps.Map{
|
||||
"level": item.GetLevel(),
|
||||
"tag": item.GetType(),
|
||||
"description": item.GetDescription(),
|
||||
"createdAt": item.GetCreatedAt(),
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", item.GetCreatedAt()),
|
||||
"count": item.GetCount(),
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logs
|
||||
this.Data["page"] = ""
|
||||
this.Data["node"] = maps.Map{"id": params.NodeId, "name": "Mock Node"}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func findHTTPDNSClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
|
||||
ClusterId: clusterID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.GetCluster() == nil {
|
||||
return maps.Map{
|
||||
"id": clusterID,
|
||||
"name": "",
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
}, nil
|
||||
}
|
||||
|
||||
cluster := resp.GetCluster()
|
||||
installDir := strings.TrimSpace(cluster.GetInstallDir())
|
||||
if len(installDir) == 0 {
|
||||
installDir = "/opt/edge-httpdns"
|
||||
}
|
||||
return maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"installDir": installDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(parent.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: nodeID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.GetNode() == nil {
|
||||
return maps.Map{}, nil
|
||||
}
|
||||
|
||||
node := resp.GetNode()
|
||||
statusMap := decodeNodeStatus(node.GetStatusJSON())
|
||||
installStatusMap := decodeInstallStatus(node.GetInstallStatusJSON())
|
||||
|
||||
var ipAddresses = []maps.Map{}
|
||||
if installStatusMap.Has("ipAddresses") {
|
||||
for _, addr := range installStatusMap.GetSlice("ipAddresses") {
|
||||
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||
ipAddresses = append(ipAddresses, maps.Map(addrMap))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ip := node.GetName()
|
||||
if savedIP := strings.TrimSpace(installStatusMap.GetString("ipAddr")); len(savedIP) > 0 {
|
||||
ip = savedIP
|
||||
} else if hostIP := strings.TrimSpace(statusMap.GetString("hostIP")); len(hostIP) > 0 {
|
||||
ip = hostIP
|
||||
}
|
||||
ipAddresses = append(ipAddresses, maps.Map{
|
||||
"id": node.GetId(),
|
||||
"name": "Public IP",
|
||||
"ip": ip,
|
||||
"canAccess": true,
|
||||
"isOn": node.GetIsOn(),
|
||||
"isUp": node.GetIsUp(),
|
||||
})
|
||||
}
|
||||
|
||||
installDir := strings.TrimSpace(node.GetInstallDir())
|
||||
if len(installDir) == 0 {
|
||||
installDir = "/opt/edge-httpdns"
|
||||
}
|
||||
|
||||
clusterMap, err := findHTTPDNSClusterMap(parent, node.GetClusterId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构造 node.login 用于 index.html 展示 SSH 信息
|
||||
var loginMap maps.Map = nil
|
||||
sshInfo := installStatusMap.GetMap("ssh")
|
||||
if sshInfo != nil {
|
||||
grantId := sshInfo.GetInt64("grantId")
|
||||
var grantMap maps.Map = nil
|
||||
if grantId > 0 {
|
||||
grantResp, grantErr := parent.RPC().NodeGrantRPC().FindEnabledNodeGrant(parent.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||
NodeGrantId: grantId,
|
||||
})
|
||||
if grantErr == nil && grantResp.GetNodeGrant() != nil {
|
||||
g := grantResp.GetNodeGrant()
|
||||
grantMap = maps.Map{
|
||||
"id": g.Id,
|
||||
"name": g.Name,
|
||||
"methodName": g.Method,
|
||||
"username": g.Username,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginMap = maps.Map{
|
||||
"params": maps.Map{
|
||||
"host": sshInfo.GetString("host"),
|
||||
"port": sshInfo.GetInt("port"),
|
||||
},
|
||||
"grant": grantMap,
|
||||
}
|
||||
}
|
||||
|
||||
return maps.Map{
|
||||
"id": node.GetId(),
|
||||
"clusterId": node.GetClusterId(),
|
||||
"name": node.GetName(),
|
||||
"isOn": node.GetIsOn(),
|
||||
"isUp": node.GetIsUp(),
|
||||
"isInstalled": node.GetIsInstalled(),
|
||||
"isActive": node.GetIsActive(),
|
||||
"uniqueId": node.GetUniqueId(),
|
||||
"secret": node.GetSecret(),
|
||||
"installDir": installDir,
|
||||
"status": statusMap,
|
||||
"installStatus": installStatusMap,
|
||||
"cluster": clusterMap,
|
||||
"login": loginMap,
|
||||
"apiNodeAddrs": []string{},
|
||||
"ipAddresses": ipAddresses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeNodeStatus(raw []byte) maps.Map {
|
||||
status := &nodeconfigs.NodeStatus{}
|
||||
if len(raw) > 0 {
|
||||
_ = json.Unmarshal(raw, status)
|
||||
}
|
||||
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
|
||||
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
|
||||
|
||||
return maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
"updatedAt": status.UpdatedAt,
|
||||
"hostname": status.Hostname,
|
||||
"hostIP": status.HostIP,
|
||||
"cpuUsage": status.CPUUsage,
|
||||
"cpuUsageText": cpuText,
|
||||
"memUsage": status.MemoryUsage,
|
||||
"memUsageText": memText,
|
||||
"load1m": status.Load1m,
|
||||
"load5m": status.Load5m,
|
||||
"load15m": status.Load15m,
|
||||
"buildVersion": status.BuildVersion,
|
||||
"cpuPhysicalCount": status.CPUPhysicalCount,
|
||||
"cpuLogicalCount": status.CPULogicalCount,
|
||||
"exePath": status.ExePath,
|
||||
"apiSuccessPercent": status.APISuccessPercent,
|
||||
"apiAvgCostSeconds": status.APIAvgCostSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func decodeInstallStatus(raw []byte) maps.Map {
|
||||
result := maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
_ = json.Unmarshal(raw, &result)
|
||||
return result
|
||||
}
|
||||
@@ -1,13 +1,41 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type StartAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *StartAction) RunPost(params struct{ NodeId int64 }) {
|
||||
func (this *StartAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
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
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: true,
|
||||
IsInstalled: node.GetIsInstalled(),
|
||||
IsActive: true,
|
||||
StatusJSON: node.GetStatusJSON(),
|
||||
InstallStatusJSON: node.GetInstallStatusJSON(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type StatusAction struct {
|
||||
@@ -14,14 +14,28 @@ func (this *StatusAction) Init() {
|
||||
this.SecondMenu("nodes")
|
||||
}
|
||||
|
||||
func (this *StatusAction) RunPost(params struct{ NodeId int64 }) {
|
||||
this.Data["installStatus"] = maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
func (this *StatusAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["isInstalled"] = true
|
||||
if resp.GetNode() == nil {
|
||||
this.Fail("节点不存在")
|
||||
return
|
||||
}
|
||||
|
||||
nodeMap, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["installStatus"] = nodeMap.GetMap("installStatus")
|
||||
this.Data["isInstalled"] = nodeMap.GetBool("isInstalled")
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,41 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type StopAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *StopAction) RunPost(params struct{ NodeId int64 }) {
|
||||
func (this *StopAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
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
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: node.GetIsUp(),
|
||||
IsInstalled: node.GetIsInstalled(),
|
||||
IsActive: false,
|
||||
StatusJSON: node.GetStatusJSON(),
|
||||
InstallStatusJSON: node.GetInstallStatusJSON(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -14,28 +21,210 @@ func (this *UpdateAction) Init() {
|
||||
this.SecondMenu("nodes")
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
||||
|
||||
this.Data["clusters"] = []maps.Map{{"id": params.ClusterId, "name": "Mock Cluster"}}
|
||||
this.Data["loginId"] = 0
|
||||
this.Data["sshHost"] = "192.168.1.100"
|
||||
this.Data["sshPort"] = 22
|
||||
this.Data["grant"] = nil
|
||||
this.Data["currentCluster"] = cluster
|
||||
this.Data["clusters"] = []maps.Map{cluster}
|
||||
this.Data["apiNodeAddrs"] = []string{}
|
||||
this.Data["node"] = node
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": params.NodeId,
|
||||
"name": "Mock Node",
|
||||
"isOn": true,
|
||||
"ipAddresses": []maps.Map{},
|
||||
sshHost := ""
|
||||
sshPort := 22
|
||||
ipAddresses := []maps.Map{}
|
||||
var grantId int64
|
||||
this.Data["grant"] = nil
|
||||
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err == nil && resp.GetNode() != nil {
|
||||
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))
|
||||
}
|
||||
}
|
||||
} else if ip := strings.TrimSpace(installStatus.GetString("ipAddr")); len(ip) > 0 {
|
||||
ipAddresses = append(ipAddresses, maps.Map{
|
||||
"ip": ip,
|
||||
"name": "",
|
||||
"canAccess": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["sshHost"] = sshHost
|
||||
this.Data["sshPort"] = sshPort
|
||||
this.Data["ipAddresses"] = ipAddresses
|
||||
|
||||
if grantId > 0 {
|
||||
grantResp, grantErr := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||
NodeGrantId: grantId,
|
||||
})
|
||||
if grantErr == nil && grantResp.GetNodeGrant() != nil {
|
||||
g := grantResp.GetNodeGrant()
|
||||
this.Data["grant"] = maps.Map{
|
||||
"id": g.Id,
|
||||
"name": g.Name,
|
||||
"methodName": g.Method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunPost(params struct{ NodeId int64 }) {
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
Name string
|
||||
ClusterId int64
|
||||
IsOn bool
|
||||
SshHost string
|
||||
SshPort int
|
||||
GrantId int64
|
||||
IpAddressesJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Name = strings.TrimSpace(params.Name)
|
||||
params.Must.Field("name", params.Name).Require("请输入节点名称")
|
||||
|
||||
params.SshHost = strings.TrimSpace(params.SshHost)
|
||||
hasSSHUpdate := len(params.SshHost) > 0 || params.SshPort > 0 || params.GrantId > 0
|
||||
if hasSSHUpdate {
|
||||
if len(params.SshHost) == 0 {
|
||||
this.Fail("请输入 SSH 主机地址")
|
||||
}
|
||||
if params.SshPort <= 0 || params.SshPort > 65535 {
|
||||
this.Fail("SSH 端口范围必须在 1-65535 之间")
|
||||
}
|
||||
if params.GrantId <= 0 {
|
||||
this.Fail("请选择 SSH 登录认证")
|
||||
}
|
||||
|
||||
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
|
||||
this.Fail("SSH 主机地址格式错误")
|
||||
}
|
||||
}
|
||||
|
||||
ipAddresses := []maps.Map{}
|
||||
if len(params.IpAddressesJSON) > 0 {
|
||||
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range ipAddresses {
|
||||
ip := addr.GetString("ip")
|
||||
if net.ParseIP(ip) == nil {
|
||||
this.Fail("IP地址格式错误: " + ip)
|
||||
}
|
||||
}
|
||||
|
||||
needUpdateInstallStatus := hasSSHUpdate || len(ipAddresses) > 0
|
||||
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if resp.GetNode() == nil {
|
||||
this.Fail("节点不存在")
|
||||
return
|
||||
}
|
||||
node := resp.GetNode()
|
||||
|
||||
installDir := strings.TrimSpace(node.GetInstallDir())
|
||||
if len(installDir) == 0 {
|
||||
installDir = "/opt/edge-httpdns"
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(this.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
Name: params.Name,
|
||||
InstallDir: installDir,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if needUpdateInstallStatus {
|
||||
installStatus := maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": node.GetIsInstalled(),
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,58 @@
|
||||
package node
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type UpdateInstallStatusAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateInstallStatusAction) RunPost(params struct{ NodeId int64 }) {
|
||||
func (this *UpdateInstallStatusAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
IsInstalled bool
|
||||
}) {
|
||||
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
|
||||
}
|
||||
|
||||
installStatus := map[string]interface{}{}
|
||||
if len(node.GetInstallStatusJSON()) > 0 {
|
||||
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||
}
|
||||
installStatus["isRunning"] = false
|
||||
installStatus["isFinished"] = true
|
||||
installStatus["isOk"] = params.IsInstalled
|
||||
installStatus["error"] = ""
|
||||
installStatus["errorCode"] = ""
|
||||
installStatus["updatedAt"] = time.Now().Unix()
|
||||
|
||||
installStatusJSON, _ := json.Marshal(installStatus)
|
||||
|
||||
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsUp: node.GetIsUp(),
|
||||
IsInstalled: params.IsInstalled,
|
||||
IsActive: node.GetIsActive(),
|
||||
StatusJSON: node.GetStatusJSON(),
|
||||
InstallStatusJSON: installStatusJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
@@ -27,80 +27,79 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
||||
Section string
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
cluster := pickCluster(params.ClusterId)
|
||||
settings := loadClusterSettings(cluster)
|
||||
cluster["name"] = settings.GetString("name")
|
||||
|
||||
// 构建顶部 tabbar
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "setting")
|
||||
|
||||
// 当前选中的 section
|
||||
section := params.Section
|
||||
section := strings.TrimSpace(params.Section)
|
||||
if len(section) == 0 {
|
||||
section = "basic"
|
||||
}
|
||||
this.Data["activeSection"] = section
|
||||
|
||||
// 左侧菜单
|
||||
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"),
|
||||
}
|
||||
if settings.GetInt("cacheTtl") <= 0 {
|
||||
settings["cacheTtl"] = 30
|
||||
}
|
||||
if settings.GetInt("fallbackTimeout") <= 0 {
|
||||
settings["fallbackTimeout"] = 300
|
||||
}
|
||||
if len(settings.GetString("installDir")) == 0 {
|
||||
settings["installDir"] = "/opt/edge-httpdns"
|
||||
}
|
||||
|
||||
listenAddresses := []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolHTTPS,
|
||||
Host: "",
|
||||
PortRange: "443",
|
||||
},
|
||||
}
|
||||
sslPolicy := &sslconfigs.SSLPolicy{
|
||||
IsOn: true,
|
||||
MinVersion: "TLS 1.1",
|
||||
}
|
||||
|
||||
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
|
||||
tlsConfig := maps.Map{}
|
||||
if err := json.Unmarshal([]byte(rawTLS), &tlsConfig); err == nil {
|
||||
if listenRaw := tlsConfig.Get("listen"); listenRaw != nil {
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
_ = json.Unmarshal(data, &listenAddresses)
|
||||
}
|
||||
}
|
||||
if sslRaw := tlsConfig.Get("sslPolicy"); sslRaw != nil {
|
||||
if data, err := json.Marshal(sslRaw); err == nil {
|
||||
_ = json.Unmarshal(data, sslPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["activeSection"] = section
|
||||
cid := strconv.FormatInt(params.ClusterId, 10)
|
||||
this.Data["leftMenuItems"] = []map[string]interface{}{
|
||||
{"name": "基础设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=basic", "isActive": section == "basic"},
|
||||
{"name": "端口设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=tls", "isActive": section == "tls"},
|
||||
{"name": "TLS", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=tls", "isActive": section == "tls"},
|
||||
}
|
||||
|
||||
settings["isDefaultCluster"] = (policies.LoadDefaultClusterID() == cluster.GetInt64("id"))
|
||||
|
||||
this.Data["cluster"] = cluster
|
||||
// 构造前端需要的 tlsConfig 格式
|
||||
var listenAddresses []*serverconfigs.NetworkAddressConfig
|
||||
listenAddrsRaw := settings.GetString("listenAddrsJSON")
|
||||
if len(listenAddrsRaw) > 0 {
|
||||
_ = json.Unmarshal([]byte(listenAddrsRaw), &listenAddresses)
|
||||
} else {
|
||||
// 默认 443 端口
|
||||
listenAddresses = []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolHTTPS,
|
||||
Host: "",
|
||||
PortRange: "443",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 构造前端需要的 SSLPolicy
|
||||
var sslPolicy *sslconfigs.SSLPolicy
|
||||
originCertPem := settings.GetString("originCertPem")
|
||||
originKeyPem := settings.GetString("originKeyPem")
|
||||
if len(originCertPem) > 0 && len(originKeyPem) > 0 {
|
||||
sslPolicy = &sslconfigs.SSLPolicy{
|
||||
IsOn: true,
|
||||
MinVersion: settings.GetString("tlsMinVersion"),
|
||||
CipherSuitesIsOn: settings.GetBool("tlsCipherSuitesOn"),
|
||||
OCSPIsOn: settings.GetBool("tlsOcspOn"),
|
||||
ClientAuthType: int(settings.GetInt32("tlsClientAuthType")),
|
||||
Certs: []*sslconfigs.SSLCertConfig{
|
||||
{
|
||||
IsOn: true,
|
||||
CertData: []byte(originCertPem),
|
||||
KeyData: []byte(originKeyPem),
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
sslPolicy = &sslconfigs.SSLPolicy{
|
||||
IsOn: true,
|
||||
MinVersion: "TLS 1.1",
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["settings"] = settings
|
||||
this.Data["tlsConfig"] = maps.Map{
|
||||
"isOn": true,
|
||||
"listen": listenAddresses,
|
||||
"sslPolicy": sslPolicy,
|
||||
}
|
||||
|
||||
this.Data["cluster"] = cluster
|
||||
this.Data["settings"] = settings
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -120,88 +119,80 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("clusterId", params.ClusterId).Gt(0, "please select cluster")
|
||||
params.Must.Field("name", params.Name).Require("please input cluster name")
|
||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("please input service domain")
|
||||
params.Must.Field("cacheTtl", params.CacheTtl).Gt(0, "cache ttl should be greater than 0")
|
||||
params.Must.Field("fallbackTimeout", params.FallbackTimeout).Gt(0, "fallback timeout should be greater than 0")
|
||||
params.Must.Field("installDir", params.InstallDir).Require("please input install dir")
|
||||
params.Name = strings.TrimSpace(params.Name)
|
||||
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
|
||||
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||
|
||||
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
|
||||
params.Must.Field("name", params.Name).Require("请输入集群名称")
|
||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||
|
||||
if params.CacheTtl <= 0 {
|
||||
params.CacheTtl = 30
|
||||
}
|
||||
if params.FallbackTimeout <= 0 {
|
||||
params.FallbackTimeout = 300
|
||||
}
|
||||
if len(params.InstallDir) == 0 {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
if params.IsDefaultCluster && !params.IsOn {
|
||||
this.Fail("默认集群必须保持启用状态")
|
||||
return
|
||||
}
|
||||
|
||||
cluster := pickCluster(params.ClusterId)
|
||||
settings := loadClusterSettings(cluster)
|
||||
settings["name"] = strings.TrimSpace(params.Name)
|
||||
settings["gatewayDomain"] = strings.TrimSpace(params.GatewayDomain)
|
||||
settings["cacheTtl"] = int(params.CacheTtl)
|
||||
settings["fallbackTimeout"] = int(params.FallbackTimeout)
|
||||
settings["installDir"] = strings.TrimSpace(params.InstallDir)
|
||||
settings["isOn"] = params.IsOn
|
||||
// 处理地址
|
||||
var addresses = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(params.Addresses) > 0 {
|
||||
err := json.Unmarshal(params.Addresses, &addresses)
|
||||
if err != nil {
|
||||
this.Fail("端口地址解析失败:" + err.Error())
|
||||
}
|
||||
|
||||
addressesJSON, _ := json.Marshal(addresses)
|
||||
settings["listenAddrsJSON"] = string(addressesJSON)
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理 SSL 配置
|
||||
var originCertPem = ""
|
||||
var originKeyPem = ""
|
||||
var tlsMinVersion = "TLS 1.1"
|
||||
var tlsCipherSuitesOn = false
|
||||
var tlsOcspOn = false
|
||||
var tlsClientAuthType = sslconfigs.SSLClientAuthType(0)
|
||||
tlsConfig := maps.Map{}
|
||||
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
|
||||
_ = json.Unmarshal([]byte(rawTLS), &tlsConfig)
|
||||
}
|
||||
|
||||
if len(params.Addresses) > 0 {
|
||||
var addresses []*serverconfigs.NetworkAddressConfig
|
||||
if err := json.Unmarshal(params.Addresses, &addresses); err != nil {
|
||||
this.Fail("监听端口配置格式不正确")
|
||||
return
|
||||
}
|
||||
tlsConfig["listen"] = addresses
|
||||
}
|
||||
|
||||
if len(params.SslPolicyJSON) > 0 {
|
||||
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||
err := json.Unmarshal(params.SslPolicyJSON, sslPolicy)
|
||||
if err == nil {
|
||||
tlsMinVersion = sslPolicy.MinVersion
|
||||
tlsCipherSuitesOn = sslPolicy.CipherSuitesIsOn
|
||||
tlsOcspOn = sslPolicy.OCSPIsOn
|
||||
tlsClientAuthType = sslconfigs.SSLClientAuthType(sslPolicy.ClientAuthType)
|
||||
if err := json.Unmarshal(params.SslPolicyJSON, sslPolicy); err != nil {
|
||||
this.Fail("TLS 配置格式不正确")
|
||||
return
|
||||
}
|
||||
tlsConfig["sslPolicy"] = sslPolicy
|
||||
}
|
||||
|
||||
if len(sslPolicy.Certs) > 0 {
|
||||
cert := sslPolicy.Certs[0]
|
||||
originCertPem = string(cert.CertData)
|
||||
originKeyPem = string(cert.KeyData)
|
||||
}
|
||||
var tlsPolicyJSON []byte
|
||||
if len(tlsConfig) > 0 {
|
||||
tlsPolicyJSON, err = json.Marshal(tlsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(originCertPem) == 0 || len(originKeyPem) == 0 {
|
||||
this.Fail("请上传或选择证书")
|
||||
}
|
||||
|
||||
settings["originHttps"] = true
|
||||
settings["originCertPem"] = originCertPem
|
||||
settings["originKeyPem"] = originKeyPem
|
||||
if len(tlsMinVersion) == 0 {
|
||||
tlsMinVersion = "TLS 1.1"
|
||||
}
|
||||
settings["tlsMinVersion"] = tlsMinVersion
|
||||
settings["tlsCipherSuitesOn"] = tlsCipherSuitesOn
|
||||
settings["tlsOcspOn"] = tlsOcspOn
|
||||
settings["tlsClientAuthType"] = int(tlsClientAuthType)
|
||||
settings["lastModifiedAt"] = nowDateTime()
|
||||
settings["certUpdatedAt"] = nowDateTime()
|
||||
|
||||
saveClusterSettings(params.ClusterId, settings)
|
||||
|
||||
currentDefaultClusterId := policies.LoadDefaultClusterID()
|
||||
if params.IsDefaultCluster {
|
||||
policies.SaveDefaultClusterID(params.ClusterId)
|
||||
} else if currentDefaultClusterId == params.ClusterId {
|
||||
policies.SaveDefaultClusterID(0)
|
||||
_, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(this.AdminContext(), &pb.UpdateHTTPDNSClusterRequest{
|
||||
ClusterId: params.ClusterId,
|
||||
Name: params.Name,
|
||||
ServiceDomain: params.GatewayDomain,
|
||||
DefaultTTL: params.CacheTtl,
|
||||
FallbackTimeoutMs: params.FallbackTimeout,
|
||||
InstallDir: params.InstallDir,
|
||||
TlsPolicyJSON: tlsPolicyJSON,
|
||||
IsOn: params.IsOn,
|
||||
IsDefault: params.IsDefaultCluster,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var clusterSettingsStore = struct {
|
||||
sync.RWMutex
|
||||
data map[int64]maps.Map
|
||||
}{
|
||||
data: map[int64]maps.Map{},
|
||||
}
|
||||
|
||||
func defaultClusterSettings(cluster maps.Map) maps.Map {
|
||||
installDir := strings.TrimSpace(cluster.GetString("installDir"))
|
||||
if len(installDir) == 0 {
|
||||
installDir = "/opt/edge-httpdns"
|
||||
}
|
||||
|
||||
return maps.Map{
|
||||
"name": cluster.GetString("name"),
|
||||
"gatewayDomain": strings.TrimSpace(cluster.GetString("gatewayDomain")),
|
||||
"cacheTtl": cluster.GetInt("cacheTtl"),
|
||||
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
|
||||
"installDir": installDir,
|
||||
"isOn": cluster.GetBool("isOn"),
|
||||
"originHttps": true,
|
||||
"originCertPem": "",
|
||||
"originKeyPem": "",
|
||||
"tlsMinVersion": "TLS 1.1",
|
||||
"tlsCipherSuitesOn": false,
|
||||
"tlsOcspOn": false,
|
||||
"tlsClientAuthType": 0,
|
||||
"certUpdatedAt": "",
|
||||
"lastModifiedAt": "",
|
||||
}
|
||||
}
|
||||
|
||||
func cloneClusterSettings(settings maps.Map) maps.Map {
|
||||
return maps.Map{
|
||||
"name": settings.GetString("name"),
|
||||
"gatewayDomain": settings.GetString("gatewayDomain"),
|
||||
"cacheTtl": settings.GetInt("cacheTtl"),
|
||||
"fallbackTimeout": settings.GetInt("fallbackTimeout"),
|
||||
"installDir": settings.GetString("installDir"),
|
||||
"isOn": settings.GetBool("isOn"),
|
||||
"originHttps": true,
|
||||
"originCertPem": settings.GetString("originCertPem"),
|
||||
"originKeyPem": settings.GetString("originKeyPem"),
|
||||
"tlsMinVersion": settings.GetString("tlsMinVersion"),
|
||||
"tlsCipherSuitesOn": settings.GetBool("tlsCipherSuitesOn"),
|
||||
"tlsOcspOn": settings.GetBool("tlsOcspOn"),
|
||||
"tlsClientAuthType": settings.GetInt("tlsClientAuthType"),
|
||||
"certUpdatedAt": settings.GetString("certUpdatedAt"),
|
||||
"lastModifiedAt": settings.GetString("lastModifiedAt"),
|
||||
}
|
||||
}
|
||||
|
||||
func loadClusterSettings(cluster maps.Map) maps.Map {
|
||||
clusterId := cluster.GetInt64("id")
|
||||
|
||||
clusterSettingsStore.RLock()
|
||||
settings, ok := clusterSettingsStore.data[clusterId]
|
||||
clusterSettingsStore.RUnlock()
|
||||
if ok {
|
||||
if len(settings.GetString("tlsMinVersion")) == 0 {
|
||||
settings["tlsMinVersion"] = "TLS 1.1"
|
||||
}
|
||||
return cloneClusterSettings(settings)
|
||||
}
|
||||
|
||||
settings = defaultClusterSettings(cluster)
|
||||
saveClusterSettings(clusterId, settings)
|
||||
return cloneClusterSettings(settings)
|
||||
}
|
||||
|
||||
func saveClusterSettings(clusterId int64, settings maps.Map) {
|
||||
clusterSettingsStore.Lock()
|
||||
clusterSettingsStore.data[clusterId] = cloneClusterSettings(settings)
|
||||
clusterSettingsStore.Unlock()
|
||||
}
|
||||
|
||||
func applyClusterSettingsOverrides(cluster maps.Map) {
|
||||
clusterId := cluster.GetInt64("id")
|
||||
if clusterId <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
clusterSettingsStore.RLock()
|
||||
settings, ok := clusterSettingsStore.data[clusterId]
|
||||
clusterSettingsStore.RUnlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
cluster["name"] = settings.GetString("name")
|
||||
cluster["gatewayDomain"] = settings.GetString("gatewayDomain")
|
||||
cluster["cacheTtl"] = settings.GetInt("cacheTtl")
|
||||
cluster["fallbackTimeout"] = settings.GetInt("fallbackTimeout")
|
||||
cluster["installDir"] = settings.GetString("installDir")
|
||||
cluster["isOn"] = settings.GetBool("isOn")
|
||||
}
|
||||
|
||||
func nowDateTime() string {
|
||||
return time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"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/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type CreateAction struct {
|
||||
@@ -17,3 +21,48 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
GatewayDomain string
|
||||
CacheTtl int32
|
||||
FallbackTimeout int32
|
||||
InstallDir string
|
||||
IsOn bool
|
||||
IsDefault bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Name = strings.TrimSpace(params.Name)
|
||||
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
|
||||
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||
if len(params.InstallDir) == 0 {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
if params.CacheTtl <= 0 {
|
||||
params.CacheTtl = 30
|
||||
}
|
||||
if params.FallbackTimeout <= 0 {
|
||||
params.FallbackTimeout = 300
|
||||
}
|
||||
|
||||
params.Must.Field("name", params.Name).Require("请输入集群名称")
|
||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||
|
||||
resp, err := this.RPC().HTTPDNSClusterRPC().CreateHTTPDNSCluster(this.AdminContext(), &pb.CreateHTTPDNSClusterRequest{
|
||||
Name: params.Name,
|
||||
ServiceDomain: params.GatewayDomain,
|
||||
DefaultTTL: params.CacheTtl,
|
||||
FallbackTimeoutMs: params.FallbackTimeout,
|
||||
InstallDir: params.InstallDir,
|
||||
IsOn: params.IsOn,
|
||||
IsDefault: params.IsDefault,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["clusterId"] = resp.GetClusterId()
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type CreateNodeAction struct {
|
||||
@@ -13,13 +16,18 @@ func (this *CreateNodeAction) Init() {
|
||||
this.Nav("httpdns", "cluster", "createNode")
|
||||
}
|
||||
|
||||
func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
|
||||
func (this *CreateNodeAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
cluster := pickCluster(params.ClusterId)
|
||||
|
||||
// 构建顶部 tabbar
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
||||
|
||||
this.Data["clusterId"] = params.ClusterId
|
||||
this.Data["cluster"] = cluster
|
||||
this.Show()
|
||||
@@ -28,6 +36,28 @@ func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
|
||||
func (this *CreateNodeAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
Name string
|
||||
InstallDir string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Name = strings.TrimSpace(params.Name)
|
||||
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
|
||||
params.Must.Field("name", params.Name).Require("请输入节点名称")
|
||||
|
||||
if len(params.InstallDir) == 0 {
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err == nil {
|
||||
params.InstallDir = strings.TrimSpace(cluster.GetString("installDir"))
|
||||
}
|
||||
if len(params.InstallDir) == 0 {
|
||||
params.InstallDir = "/opt/edge-httpdns"
|
||||
}
|
||||
}
|
||||
|
||||
if err := createNode(this.Parent(), params.ClusterId, params.Name, params.InstallDir); err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package clusters
|
||||
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"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
@@ -17,11 +18,14 @@ func (this *DeleteAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
cluster := pickCluster(params.ClusterId)
|
||||
|
||||
// 构建顶部 tabbar
|
||||
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "delete")
|
||||
|
||||
this.Data["cluster"] = cluster
|
||||
this.Show()
|
||||
}
|
||||
@@ -29,6 +33,12 @@ func (this *DeleteAction) RunGet(params struct {
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
_ = params.ClusterId
|
||||
_, err := this.RPC().HTTPDNSClusterRPC().DeleteHTTPDNSCluster(this.AdminContext(), &pb.DeleteHTTPDNSClusterRequest{
|
||||
ClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
package clusters
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
)
|
||||
type DeleteNodeAction struct { actionutils.ParentAction }
|
||||
func (this *DeleteNodeAction) RunPost(params struct{ ClusterId int64; NodeId int64 }) { this.Success() }
|
||||
package clusters
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type DeleteNodeAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteNodeAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
if err := deleteNode(this.Parent(), params.NodeId); err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -13,8 +13,27 @@ func (this *IndexAction) Init() {
|
||||
this.Nav("httpdns", "cluster", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["clusters"] = mockClusters()
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
clusters, err := listClusterMaps(this.Parent(), params.Keyword)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
|
||||
hasErrorLogs := false
|
||||
for _, cluster := range clusters {
|
||||
if cluster.GetInt("countAllNodes") > cluster.GetInt("countActiveNodes") {
|
||||
hasErrorLogs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
this.Data["hasErrorLogs"] = hasErrorLogs
|
||||
this.Data["page"] = ""
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func init() {
|
||||
Data("teaSubMenu", "cluster").
|
||||
Prefix("/httpdns/clusters").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/create", new(CreateAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
Get("/cluster", new(ClusterAction)).
|
||||
GetPost("/cluster/settings", new(ClusterSettingsAction)).
|
||||
GetPost("/delete", new(DeleteAction)).
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package clusters
|
||||
|
||||
import "github.com/iwind/TeaGo/maps"
|
||||
|
||||
func mockClusters() []maps.Map {
|
||||
clusters := []maps.Map{
|
||||
{
|
||||
"id": int64(1),
|
||||
"name": "gateway-cn-hz",
|
||||
"region": "cn-hangzhou",
|
||||
"gatewayDomain": "gw-hz.httpdns.example.com",
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
"countAllNodes": 3,
|
||||
"countActiveNodes": 3,
|
||||
"countApps": 5,
|
||||
"cacheTtl": 30,
|
||||
"fallbackTimeout": 300,
|
||||
"isOn": true,
|
||||
},
|
||||
{
|
||||
"id": int64(2),
|
||||
"name": "gateway-cn-bj",
|
||||
"region": "cn-beijing",
|
||||
"gatewayDomain": "gw-bj.httpdns.example.com",
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
"countAllNodes": 3,
|
||||
"countActiveNodes": 2,
|
||||
"countApps": 2,
|
||||
"cacheTtl": 30,
|
||||
"fallbackTimeout": 300,
|
||||
"isOn": true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cluster := range clusters {
|
||||
applyClusterSettingsOverrides(cluster)
|
||||
}
|
||||
|
||||
return clusters
|
||||
}
|
||||
|
||||
func pickCluster(clusterId int64) maps.Map {
|
||||
clusters := mockClusters()
|
||||
if clusterId <= 0 {
|
||||
return clusters[0]
|
||||
}
|
||||
for _, c := range clusters {
|
||||
if c.GetInt64("id") == clusterId {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return clusters[0]
|
||||
}
|
||||
|
||||
func mockNodes(clusterId int64, installState int, activeState int, keyword string) []maps.Map {
|
||||
_ = clusterId
|
||||
return []maps.Map{
|
||||
{
|
||||
"id": int64(101),
|
||||
"name": "45.250.184.56",
|
||||
"isInstalled": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
"installStatus": maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
},
|
||||
"status": maps.Map{
|
||||
"isActive": true,
|
||||
"updatedAt": 1700000000,
|
||||
"hostname": "node-01",
|
||||
"cpuUsage": 0.0253,
|
||||
"cpuUsageText": "2.53%",
|
||||
"memUsage": 0.5972,
|
||||
"memUsageText": "59.72%",
|
||||
"load1m": 0.02,
|
||||
},
|
||||
"ipAddresses": []maps.Map{
|
||||
{
|
||||
"id": 1,
|
||||
"name": "",
|
||||
"ip": "45.250.184.56",
|
||||
"canAccess": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": int64(102),
|
||||
"name": "45.250.184.53",
|
||||
"isInstalled": true,
|
||||
"isOn": true,
|
||||
"isUp": true,
|
||||
"installStatus": maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
},
|
||||
"status": maps.Map{
|
||||
"isActive": true,
|
||||
"updatedAt": 1700000000,
|
||||
"hostname": "node-02",
|
||||
"cpuUsage": 0.0039,
|
||||
"cpuUsageText": "0.39%",
|
||||
"memUsage": 0.0355,
|
||||
"memUsageText": "3.55%",
|
||||
"load1m": 0.0,
|
||||
},
|
||||
"ipAddresses": []maps.Map{
|
||||
{
|
||||
"id": 2,
|
||||
"name": "",
|
||||
"ip": "45.250.184.53",
|
||||
"canAccess": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mockCerts() []maps.Map {
|
||||
return []maps.Map{
|
||||
{
|
||||
"id": int64(11),
|
||||
"domain": "resolve.edge.example.com",
|
||||
"issuer": "Mock CA",
|
||||
"expiresAt": "2026-12-31 23:59:59",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(parent.AdminContext(), &pb.ListHTTPDNSClustersRequest{
|
||||
Offset: 0,
|
||||
Size: 10_000,
|
||||
Keyword: strings.TrimSpace(keyword),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(resp.GetClusters()))
|
||||
for _, cluster := range resp.GetClusters() {
|
||||
nodesResp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{
|
||||
ClusterId: cluster.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
countAllNodes := len(nodesResp.GetNodes())
|
||||
countActiveNodes := 0
|
||||
for _, node := range nodesResp.GetNodes() {
|
||||
if node.GetIsOn() && node.GetIsUp() && node.GetIsActive() {
|
||||
countActiveNodes++
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"gatewayDomain": cluster.GetServiceDomain(),
|
||||
"defaultTTL": cluster.GetDefaultTTL(),
|
||||
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
|
||||
"installDir": cluster.GetInstallDir(),
|
||||
"isOn": cluster.GetIsOn(),
|
||||
"isDefault": cluster.GetIsDefault(),
|
||||
"countAllNodes": countAllNodes,
|
||||
"countActiveNodes": countActiveNodes,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
|
||||
if clusterID > 0 {
|
||||
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
|
||||
ClusterId: clusterID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.GetCluster() != nil {
|
||||
cluster := resp.GetCluster()
|
||||
return maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
"gatewayDomain": cluster.GetServiceDomain(),
|
||||
"defaultTTL": cluster.GetDefaultTTL(),
|
||||
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
|
||||
"installDir": cluster.GetInstallDir(),
|
||||
"isOn": cluster.GetIsOn(),
|
||||
"isDefault": cluster.GetIsDefault(),
|
||||
"tlsPolicyJSON": cluster.GetTlsPolicyJSON(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
clusters, err := listClusterMaps(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(clusters) == 0 {
|
||||
return maps.Map{
|
||||
"id": int64(0),
|
||||
"name": "",
|
||||
"gatewayDomain": "",
|
||||
"defaultTTL": 30,
|
||||
"fallbackTimeout": 300,
|
||||
"installDir": "/opt/edge-httpdns",
|
||||
}, nil
|
||||
}
|
||||
return clusters[0], nil
|
||||
}
|
||||
|
||||
func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{
|
||||
ClusterId: clusterID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]maps.Map, 0, len(resp.GetNodes()))
|
||||
for _, node := range resp.GetNodes() {
|
||||
statusMap := decodeNodeStatus(node.GetStatusJSON())
|
||||
installStatusMap := decodeInstallStatus(node.GetInstallStatusJSON())
|
||||
ip := node.GetName()
|
||||
if parsed := strings.TrimSpace(statusMap.GetString("hostIP")); len(parsed) > 0 {
|
||||
ip = parsed
|
||||
}
|
||||
nodeMap := maps.Map{
|
||||
"id": node.GetId(),
|
||||
"clusterId": node.GetClusterId(),
|
||||
"name": node.GetName(),
|
||||
"isOn": node.GetIsOn(),
|
||||
"isUp": node.GetIsUp(),
|
||||
"isInstalled": node.GetIsInstalled(),
|
||||
"isActive": node.GetIsActive(),
|
||||
"installDir": node.GetInstallDir(),
|
||||
"uniqueId": node.GetUniqueId(),
|
||||
"secret": node.GetSecret(),
|
||||
"status": statusMap,
|
||||
"installStatus": installStatusMap,
|
||||
"region": nil,
|
||||
"login": nil,
|
||||
"apiNodeAddrs": []string{},
|
||||
"cluster": maps.Map{
|
||||
"id": node.GetClusterId(),
|
||||
"installDir": node.GetInstallDir(),
|
||||
},
|
||||
"ipAddresses": []maps.Map{
|
||||
{
|
||||
"id": node.GetId(),
|
||||
"name": "Public IP",
|
||||
"ip": ip,
|
||||
"canAccess": true,
|
||||
"isOn": node.GetIsOn(),
|
||||
"isUp": node.GetIsUp(),
|
||||
},
|
||||
},
|
||||
}
|
||||
result = append(result, nodeMap)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func findNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Map, error) {
|
||||
resp, err := parent.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(parent.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: nodeID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.GetNode() == nil {
|
||||
return maps.Map{}, nil
|
||||
}
|
||||
nodes, err := listNodeMaps(parent, resp.GetNode().GetClusterId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.GetInt64("id") == nodeID {
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
return maps.Map{
|
||||
"id": resp.GetNode().GetId(),
|
||||
"name": resp.GetNode().GetName(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createNode(parent *actionutils.ParentAction, clusterID int64, name string, installDir string) error {
|
||||
_, err := parent.RPC().HTTPDNSNodeRPC().CreateHTTPDNSNode(parent.AdminContext(), &pb.CreateHTTPDNSNodeRequest{
|
||||
ClusterId: clusterID,
|
||||
Name: strings.TrimSpace(name),
|
||||
InstallDir: strings.TrimSpace(installDir),
|
||||
IsOn: true,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func updateNode(parent *actionutils.ParentAction, nodeID int64, name string, installDir string, isOn bool) error {
|
||||
_, err := parent.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(parent.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{
|
||||
NodeId: nodeID,
|
||||
Name: strings.TrimSpace(name),
|
||||
InstallDir: strings.TrimSpace(installDir),
|
||||
IsOn: isOn,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteNode(parent *actionutils.ParentAction, nodeID int64) error {
|
||||
_, err := parent.RPC().HTTPDNSNodeRPC().DeleteHTTPDNSNode(parent.AdminContext(), &pb.DeleteHTTPDNSNodeRequest{
|
||||
NodeId: nodeID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func decodeNodeStatus(raw []byte) maps.Map {
|
||||
status := &nodeconfigs.NodeStatus{}
|
||||
if len(raw) > 0 {
|
||||
_ = json.Unmarshal(raw, status)
|
||||
}
|
||||
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
|
||||
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
|
||||
return maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
"updatedAt": status.UpdatedAt,
|
||||
"hostname": status.Hostname,
|
||||
"hostIP": status.HostIP,
|
||||
"cpuUsage": status.CPUUsage,
|
||||
"cpuUsageText": cpuText,
|
||||
"memUsage": status.MemoryUsage,
|
||||
"memUsageText": memText,
|
||||
"load1m": status.Load1m,
|
||||
"load5m": status.Load5m,
|
||||
"load15m": status.Load15m,
|
||||
"buildVersion": status.BuildVersion,
|
||||
"cpuPhysicalCount": status.CPUPhysicalCount,
|
||||
"cpuLogicalCount": status.CPULogicalCount,
|
||||
"exePath": status.ExePath,
|
||||
}
|
||||
}
|
||||
|
||||
func decodeInstallStatus(raw []byte) maps.Map {
|
||||
if len(raw) == 0 {
|
||||
return maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
}
|
||||
result := maps.Map{}
|
||||
if err := json.Unmarshal(raw, &result); err != nil {
|
||||
return maps.Map{
|
||||
"isRunning": false,
|
||||
"isFinished": true,
|
||||
"isOk": true,
|
||||
"error": "",
|
||||
"errorCode": "",
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"regexp"
|
||||
"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/maps"
|
||||
)
|
||||
|
||||
@@ -12,18 +19,52 @@ type UpdateNodeSSHAction struct {
|
||||
func (this *UpdateNodeSSHAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
clusterId := int64(0)
|
||||
nodeName := ""
|
||||
if resp.GetNode() != nil {
|
||||
clusterId = resp.GetNode().GetClusterId()
|
||||
nodeName = resp.GetNode().GetName()
|
||||
}
|
||||
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["clusterId"] = 0
|
||||
this.Data["clusterId"] = clusterId
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": params.NodeId,
|
||||
"name": "Mock Node",
|
||||
"name": nodeName,
|
||||
}
|
||||
this.Data["loginId"] = 0
|
||||
this.Data["params"] = maps.Map{
|
||||
"host": "1.2.3.4",
|
||||
loginParams := maps.Map{
|
||||
"host": "",
|
||||
"port": 22,
|
||||
"grantId": 0,
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["params"] = loginParams
|
||||
this.Data["grant"] = nil
|
||||
this.Show()
|
||||
}
|
||||
@@ -34,6 +75,66 @@ func (this *UpdateNodeSSHAction) RunPost(params struct {
|
||||
SshHost string
|
||||
SshPort int
|
||||
GrantId int64
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.SshHost = strings.TrimSpace(params.SshHost)
|
||||
params.Must.
|
||||
Field("sshHost", params.SshHost).
|
||||
Require("请输入 SSH 主机地址").
|
||||
Field("sshPort", params.SshPort).
|
||||
Gt(0, "SSH 端口必须大于 0").
|
||||
Lt(65535, "SSH 端口必须小于 65535")
|
||||
if params.GrantId <= 0 {
|
||||
this.Fail("请选择节点登录认证信息")
|
||||
}
|
||||
|
||||
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package ech
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
)
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type AuditAction struct {
|
||||
actionutils.ParentAction
|
||||
@@ -14,15 +11,5 @@ func (this *AuditAction) Init() {
|
||||
}
|
||||
|
||||
func (this *AuditAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["auditLogs"] = []map[string]interface{}{
|
||||
{
|
||||
"id": 1,
|
||||
"scope": "tenant:1001 -> domain:api.business.com -> region:cn-hz",
|
||||
"operator": "admin",
|
||||
"result": "success",
|
||||
"createdAt": "2026-02-21 10:00:00",
|
||||
},
|
||||
}
|
||||
this.Show()
|
||||
this.RedirectURL("/httpdns/apps")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package ech
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
)
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
@@ -14,34 +11,5 @@ func (this *IndexAction) Init() {
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["health"] = map[string]interface{}{
|
||||
"keySyncRate": 0.9995,
|
||||
"decryptFailRate": 0.0001,
|
||||
"isHealthy": true,
|
||||
}
|
||||
|
||||
this.Data["echLogs"] = []map[string]interface{}{
|
||||
{
|
||||
"id": 1024,
|
||||
"version": "ech-v20260221-01",
|
||||
"recordType": "HTTPS(Type65)",
|
||||
"publicKey": "BFCf8h5Qmock_public_key_for_ui_preview_lx9v2k7p0n",
|
||||
"publishTime": "2026-02-21 00:00:00",
|
||||
"syncStatus": "success",
|
||||
"nodesPending": 0,
|
||||
"isCurrent": true,
|
||||
},
|
||||
{
|
||||
"id": 1023,
|
||||
"version": "ech-v20260220-01",
|
||||
"recordType": "TXT",
|
||||
"publicKey": "BE9x3a2Qmock_prev_key_for_ui_preview_vd1n7x5k2c",
|
||||
"publishTime": "2026-02-20 00:00:00",
|
||||
"syncStatus": "success",
|
||||
"nodesPending": 0,
|
||||
"isCurrent": false,
|
||||
},
|
||||
}
|
||||
this.Show()
|
||||
this.RedirectURL("/httpdns/apps")
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ func (this *RollbackMfaPopupAction) Init() {
|
||||
func (this *RollbackMfaPopupAction) RunGet(params struct {
|
||||
LogId int64
|
||||
}) {
|
||||
this.Data["logId"] = params.LogId
|
||||
this.Show()
|
||||
this.RedirectURL("/httpdns/apps")
|
||||
}
|
||||
|
||||
func (this *RollbackMfaPopupAction) RunPost(params struct {
|
||||
@@ -29,8 +28,5 @@ func (this *RollbackMfaPopupAction) RunPost(params struct {
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.Field("reason", params.Reason).Require("please input rollback reason")
|
||||
params.Must.Field("otpCode1", params.OtpCode1).Require("please input first otp code")
|
||||
params.Must.Field("otpCode2", params.OtpCode2).Require("please input second otp code")
|
||||
this.Success()
|
||||
this.Fail("feature removed")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
@@ -16,52 +11,9 @@ func (this *IndexAction) Init() {
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
this.Data["policies"] = loadGlobalPolicies()
|
||||
this.Data["availableClusters"] = loadAvailableDeployClusters()
|
||||
this.Show()
|
||||
this.RedirectURL("/httpdns/clusters")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
DefaultClusterId int64
|
||||
DefaultTTL int
|
||||
DefaultFallbackMs int
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
if params.DefaultClusterId <= 0 || !isValidClusterID(params.DefaultClusterId) {
|
||||
this.Fail("please select a valid default cluster")
|
||||
return
|
||||
}
|
||||
|
||||
params.Must.Field("defaultTTL", params.DefaultTTL).Gt(0, "default ttl should be > 0")
|
||||
params.Must.Field("defaultFallbackMs", params.DefaultFallbackMs).Gt(0, "default fallback should be > 0")
|
||||
|
||||
if params.DefaultTTL > 86400 {
|
||||
this.Fail("default TTL should be <= 86400")
|
||||
return
|
||||
}
|
||||
if params.DefaultFallbackMs > 10000 {
|
||||
this.Fail("default fallback should be <= 10000 ms")
|
||||
return
|
||||
}
|
||||
|
||||
saveGlobalPolicies(maps.Map{
|
||||
"defaultClusterId": params.DefaultClusterId,
|
||||
"enableUserDomainVerify": true,
|
||||
"defaultTTL": params.DefaultTTL,
|
||||
"defaultFallbackMs": params.DefaultFallbackMs,
|
||||
})
|
||||
|
||||
func (this *IndexAction) RunPost(params struct{}) {
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func isValidClusterID(clusterID int64) bool {
|
||||
for _, cluster := range loadAvailableDeployClusters() {
|
||||
if cluster.GetInt64("id") == clusterID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
var globalPoliciesStore = struct {
|
||||
sync.RWMutex
|
||||
data maps.Map
|
||||
publicSniPools []maps.Map
|
||||
publicSniCertificates []maps.Map
|
||||
}{
|
||||
data: maps.Map{
|
||||
"defaultClusterId": int64(1),
|
||||
"enableUserDomainVerify": true,
|
||||
"defaultTTL": 30,
|
||||
"defaultFallbackMs": 300,
|
||||
},
|
||||
publicSniPools: []maps.Map{
|
||||
{
|
||||
"domain": "public-sni-a.waf.example.com",
|
||||
"certId": "cert_public_sni_a",
|
||||
},
|
||||
{
|
||||
"domain": "public-sni-b.waf.example.com",
|
||||
"certId": "cert_public_sni_b",
|
||||
},
|
||||
},
|
||||
publicSniCertificates: []maps.Map{
|
||||
{
|
||||
"id": "cert_public_sni_a",
|
||||
"name": "public-sni-a.waf.example.com",
|
||||
"issuer": "Mock CA",
|
||||
"expiresAt": "2026-12-31 23:59:59",
|
||||
},
|
||||
{
|
||||
"id": "cert_public_sni_b",
|
||||
"name": "public-sni-b.waf.example.com",
|
||||
"issuer": "Mock CA",
|
||||
"expiresAt": "2027-03-31 23:59:59",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func loadGlobalPolicies() maps.Map {
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
|
||||
return maps.Map{
|
||||
"defaultClusterId": globalPoliciesStore.data.GetInt64("defaultClusterId"),
|
||||
"enableUserDomainVerify": true,
|
||||
"defaultTTL": globalPoliciesStore.data.GetInt("defaultTTL"),
|
||||
"defaultFallbackMs": globalPoliciesStore.data.GetInt("defaultFallbackMs"),
|
||||
}
|
||||
}
|
||||
|
||||
func saveGlobalPolicies(policies maps.Map) {
|
||||
globalPoliciesStore.Lock()
|
||||
globalPoliciesStore.data = maps.Map{
|
||||
"defaultClusterId": policies.GetInt64("defaultClusterId"),
|
||||
"enableUserDomainVerify": true,
|
||||
"defaultTTL": policies.GetInt("defaultTTL"),
|
||||
"defaultFallbackMs": policies.GetInt("defaultFallbackMs"),
|
||||
}
|
||||
|
||||
globalPoliciesStore.Unlock()
|
||||
}
|
||||
|
||||
func loadPublicSNICertificates() []maps.Map {
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
|
||||
return cloneMapSlice(globalPoliciesStore.publicSniCertificates)
|
||||
}
|
||||
|
||||
// LoadPublicSNICertificates returns global public SNI certificates for other httpdns modules.
|
||||
func LoadPublicSNICertificates() []maps.Map {
|
||||
return loadPublicSNICertificates()
|
||||
}
|
||||
|
||||
func loadAvailableDeployClusters() []maps.Map {
|
||||
return []maps.Map{
|
||||
{
|
||||
"id": int64(1),
|
||||
"name": "gateway-cn-hz",
|
||||
"region": "cn-hangzhou",
|
||||
"gatewayDomain": "gw-hz.httpdns.example.com",
|
||||
},
|
||||
{
|
||||
"id": int64(2),
|
||||
"name": "gateway-cn-bj",
|
||||
"region": "cn-beijing",
|
||||
"gatewayDomain": "gw-bj.httpdns.example.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAvailableDeployClusters returns selectable clusters for HTTPDNS user defaults.
|
||||
func LoadAvailableDeployClusters() []maps.Map {
|
||||
return loadAvailableDeployClusters()
|
||||
}
|
||||
|
||||
// LoadDefaultClusterID returns current default deploy cluster id for HTTPDNS users.
|
||||
func LoadDefaultClusterID() int64 {
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
return globalPoliciesStore.data.GetInt64("defaultClusterId")
|
||||
}
|
||||
|
||||
// SaveDefaultClusterID updates default deploy cluster id.
|
||||
// Pass 0 to clear and let caller fallback to first available cluster.
|
||||
func SaveDefaultClusterID(clusterID int64) {
|
||||
globalPoliciesStore.Lock()
|
||||
globalPoliciesStore.data["defaultClusterId"] = clusterID
|
||||
globalPoliciesStore.Unlock()
|
||||
}
|
||||
|
||||
// LoadClusterGatewayByID returns gateway domain for the selected cluster.
|
||||
func LoadClusterGatewayByID(clusterID int64) string {
|
||||
clusters := loadAvailableDeployClusters()
|
||||
for _, cluster := range clusters {
|
||||
if cluster.GetInt64("id") == clusterID {
|
||||
gatewayDomain := strings.TrimSpace(cluster.GetString("gatewayDomain"))
|
||||
if len(gatewayDomain) > 0 {
|
||||
return gatewayDomain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(clusters) > 0 {
|
||||
fallback := strings.TrimSpace(clusters[0].GetString("gatewayDomain"))
|
||||
if len(fallback) > 0 {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
return "gw.httpdns.example.com"
|
||||
}
|
||||
|
||||
// LoadPublicSNIPools returns global public SNI domain pool for other httpdns modules.
|
||||
func LoadPublicSNIPools() []maps.Map {
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
|
||||
return cloneMapSlice(globalPoliciesStore.publicSniPools)
|
||||
}
|
||||
|
||||
// HasPublicSNIDomain checks if a public SNI domain exists in global pool.
|
||||
func HasPublicSNIDomain(domain string) bool {
|
||||
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||
if len(domain) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
globalPoliciesStore.RLock()
|
||||
defer globalPoliciesStore.RUnlock()
|
||||
|
||||
for _, pool := range globalPoliciesStore.publicSniPools {
|
||||
if strings.ToLower(strings.TrimSpace(pool.GetString("domain"))) == domain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func cloneMapSlice(src []maps.Map) []maps.Map {
|
||||
result := make([]maps.Map, 0, len(src))
|
||||
for _, item := range src {
|
||||
cloned := maps.Map{}
|
||||
for k, v := range item {
|
||||
cloned[k] = v
|
||||
}
|
||||
result = append(result, cloned)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -5,6 +5,8 @@ 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"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -40,120 +42,88 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["status"] = params.Status
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
clusters := []map[string]interface{}{
|
||||
{"id": int64(1), "name": "gateway-cn-hz"},
|
||||
{"id": int64(2), "name": "gateway-cn-bj"},
|
||||
clusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
nodes := []map[string]interface{}{
|
||||
{"id": int64(101), "clusterId": int64(1), "name": "hz-node-01"},
|
||||
{"id": int64(102), "clusterId": int64(1), "name": "hz-node-02"},
|
||||
{"id": int64(201), "clusterId": int64(2), "name": "bj-node-01"},
|
||||
clusters := make([]map[string]interface{}, 0, len(clusterResp.GetClusters()))
|
||||
nodes := make([]map[string]interface{}, 0)
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusters = append(clusters, map[string]interface{}{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
})
|
||||
nodeResp, err := this.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(this.AdminContext(), &pb.ListHTTPDNSNodesRequest{
|
||||
ClusterId: cluster.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, node := range nodeResp.GetNodes() {
|
||||
nodes = append(nodes, map[string]interface{}{
|
||||
"id": node.GetId(),
|
||||
"clusterId": node.GetClusterId(),
|
||||
"name": node.GetName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
this.Data["nodes"] = nodes
|
||||
|
||||
allLogs := []map[string]interface{}{
|
||||
{
|
||||
"time": "2026-02-23 10:21:51",
|
||||
"clusterId": int64(1),
|
||||
"clusterName": "gateway-cn-hz",
|
||||
"nodeId": int64(101),
|
||||
"nodeName": "hz-node-01",
|
||||
"appName": "\u4e3b\u7ad9\u79fb\u52a8\u4e1a\u52a1",
|
||||
"appId": "ab12xc34s2",
|
||||
"domain": "api.business.com",
|
||||
"query": "A",
|
||||
"clientIp": "203.0.113.25",
|
||||
"os": "Android",
|
||||
"sdkVersion": "2.4.1",
|
||||
"ips": "198.51.100.10,198.51.100.11",
|
||||
"status": "success",
|
||||
"errorCode": "none",
|
||||
"costMs": 37,
|
||||
"pinningMode": "report",
|
||||
"pinningResult": "warn",
|
||||
"sanMode": "strict",
|
||||
"sanResult": "pass",
|
||||
},
|
||||
{
|
||||
"time": "2026-02-23 10:18:02",
|
||||
"clusterId": int64(2),
|
||||
"clusterName": "gateway-cn-bj",
|
||||
"nodeId": int64(201),
|
||||
"nodeName": "bj-node-01",
|
||||
"appName": "\u652f\u4ed8\u7f51\u5173\u4e1a\u52a1",
|
||||
"appId": "vd8992ksm1",
|
||||
"domain": "payment.business.com",
|
||||
"query": "A",
|
||||
"clientIp": "198.51.100.67",
|
||||
"os": "iOS",
|
||||
"sdkVersion": "2.3.9",
|
||||
"ips": "198.51.100.22",
|
||||
"status": "failed",
|
||||
"errorCode": "40301",
|
||||
"costMs": 52,
|
||||
"pinningMode": "enforce",
|
||||
"pinningResult": "fail",
|
||||
"sanMode": "strict",
|
||||
"sanResult": "fail",
|
||||
},
|
||||
{
|
||||
"time": "2026-02-23 10:12:44",
|
||||
"clusterId": int64(1),
|
||||
"clusterName": "gateway-cn-hz",
|
||||
"nodeId": int64(102),
|
||||
"nodeName": "hz-node-02",
|
||||
"appName": "\u4e3b\u7ad9\u79fb\u52a8\u4e1a\u52a1",
|
||||
"appId": "ab12xc34s2",
|
||||
"domain": "www.aliyun.com",
|
||||
"query": "A",
|
||||
"clientIp": "106.11.63.180",
|
||||
"os": "Unknown",
|
||||
"sdkVersion": "none",
|
||||
"ips": "3.33.120.190,15.197.120.33",
|
||||
"status": "success",
|
||||
"errorCode": "none",
|
||||
"costMs": 41,
|
||||
"pinningMode": "off",
|
||||
"pinningResult": "na",
|
||||
"sanMode": "report",
|
||||
"sanResult": "warn",
|
||||
},
|
||||
logResp, err := this.RPC().HTTPDNSAccessLogRPC().ListHTTPDNSAccessLogs(this.AdminContext(), &pb.ListHTTPDNSAccessLogsRequest{
|
||||
Day: "",
|
||||
ClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
AppId: strings.TrimSpace(params.AppId),
|
||||
Domain: strings.TrimSpace(params.Domain),
|
||||
Status: strings.TrimSpace(params.Status),
|
||||
Keyword: strings.TrimSpace(params.Keyword),
|
||||
Offset: 0,
|
||||
Size: 100,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
status := strings.TrimSpace(strings.ToLower(params.Status))
|
||||
appID := strings.TrimSpace(strings.ToLower(params.AppId))
|
||||
domain := strings.TrimSpace(strings.ToLower(params.Domain))
|
||||
keyword := strings.TrimSpace(strings.ToLower(params.Keyword))
|
||||
logs := make([]map[string]interface{}, 0, len(logResp.GetLogs()))
|
||||
for _, item := range logResp.GetLogs() {
|
||||
createdTime := ""
|
||||
if item.GetCreatedAt() > 0 {
|
||||
createdTime = timeutil.FormatTime("Y-m-d H:i:s", item.GetCreatedAt())
|
||||
}
|
||||
status := item.GetStatus()
|
||||
if len(status) == 0 {
|
||||
status = "failed"
|
||||
}
|
||||
errorCode := item.GetErrorCode()
|
||||
if len(errorCode) == 0 {
|
||||
errorCode = "none"
|
||||
}
|
||||
|
||||
filtered := make([]map[string]interface{}, 0)
|
||||
for _, log := range allLogs {
|
||||
if params.ClusterId > 0 && log["clusterId"].(int64) != params.ClusterId {
|
||||
continue
|
||||
}
|
||||
if params.NodeId > 0 && log["nodeId"].(int64) != params.NodeId {
|
||||
continue
|
||||
}
|
||||
if len(status) > 0 && log["status"].(string) != status {
|
||||
continue
|
||||
}
|
||||
if len(appID) > 0 && !strings.Contains(strings.ToLower(log["appId"].(string)), appID) {
|
||||
continue
|
||||
}
|
||||
if len(domain) > 0 && !strings.Contains(strings.ToLower(log["domain"].(string)), domain) {
|
||||
continue
|
||||
}
|
||||
if len(keyword) > 0 {
|
||||
if !strings.Contains(strings.ToLower(log["appName"].(string)), keyword) &&
|
||||
!strings.Contains(strings.ToLower(log["domain"].(string)), keyword) &&
|
||||
!strings.Contains(strings.ToLower(log["clientIp"].(string)), keyword) &&
|
||||
!strings.Contains(strings.ToLower(log["ips"].(string)), keyword) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, log)
|
||||
logs = append(logs, map[string]interface{}{
|
||||
"time": createdTime,
|
||||
"clusterId": item.GetClusterId(),
|
||||
"clusterName": item.GetClusterName(),
|
||||
"nodeId": item.GetNodeId(),
|
||||
"nodeName": item.GetNodeName(),
|
||||
"appName": item.GetAppName(),
|
||||
"appId": item.GetAppId(),
|
||||
"domain": item.GetDomain(),
|
||||
"query": item.GetQtype(),
|
||||
"clientIp": item.GetClientIP(),
|
||||
"os": item.GetOs(),
|
||||
"sdkVersion": item.GetSdkVersion(),
|
||||
"ips": item.GetResultIPs(),
|
||||
"status": status,
|
||||
"errorCode": errorCode,
|
||||
"costMs": item.GetCostMs(),
|
||||
})
|
||||
}
|
||||
|
||||
this.Data["resolveLogs"] = filtered
|
||||
this.Data["resolveLogs"] = logs
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ 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"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -40,105 +42,75 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.Data["level"] = params.Level
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
clusters := []map[string]interface{}{
|
||||
{"id": int64(1), "name": "gateway-cn-hz"},
|
||||
{"id": int64(2), "name": "gateway-cn-bj"},
|
||||
clusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
nodes := []map[string]interface{}{
|
||||
{"id": int64(101), "clusterId": int64(1), "name": "hz-node-01"},
|
||||
{"id": int64(102), "clusterId": int64(1), "name": "hz-node-02"},
|
||||
{"id": int64(201), "clusterId": int64(2), "name": "bj-node-01"},
|
||||
clusters := make([]map[string]interface{}, 0, len(clusterResp.GetClusters()))
|
||||
nodes := make([]map[string]interface{}, 0)
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusters = append(clusters, map[string]interface{}{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
})
|
||||
nodeResp, err := this.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(this.AdminContext(), &pb.ListHTTPDNSNodesRequest{
|
||||
ClusterId: cluster.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, node := range nodeResp.GetNodes() {
|
||||
nodes = append(nodes, map[string]interface{}{
|
||||
"id": node.GetId(),
|
||||
"clusterId": node.GetClusterId(),
|
||||
"name": node.GetName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
this.Data["nodes"] = nodes
|
||||
|
||||
allLogs := []map[string]interface{}{
|
||||
{
|
||||
"createdTime": "2026-02-23 10:30:12",
|
||||
"clusterId": int64(1),
|
||||
"clusterName": "gateway-cn-hz",
|
||||
"nodeId": int64(101),
|
||||
"nodeName": "hz-node-01",
|
||||
"level": "info",
|
||||
"tag": "config",
|
||||
"module": "resolver",
|
||||
"description": "\u914d\u7f6e\u70ed\u52a0\u8f7d\u5b8c\u6210\uff0c\u5df2\u542f\u7528\u6700\u65b0\u7b56\u7565\u5feb\u7167\u3002",
|
||||
"count": int64(1),
|
||||
"requestId": "rid-20260223-001",
|
||||
},
|
||||
{
|
||||
"createdTime": "2026-02-23 10:27:38",
|
||||
"clusterId": int64(2),
|
||||
"clusterName": "gateway-cn-bj",
|
||||
"nodeId": int64(201),
|
||||
"nodeName": "bj-node-01",
|
||||
"level": "warning",
|
||||
"tag": "sni",
|
||||
"module": "policy-engine",
|
||||
"description": "\u68c0\u6d4b\u5230 level3 \u6761\u4ef6\u4e0d\u6ee1\u8db3\uff0c\u81ea\u52a8\u964d\u7ea7\u5230 level2\u3002",
|
||||
"count": int64(3),
|
||||
"requestId": "rid-20260223-002",
|
||||
},
|
||||
{
|
||||
"createdTime": "2026-02-23 10:22:49",
|
||||
"clusterId": int64(1),
|
||||
"clusterName": "gateway-cn-hz",
|
||||
"nodeId": int64(102),
|
||||
"nodeName": "hz-node-02",
|
||||
"level": "success",
|
||||
"tag": "cache-refresh",
|
||||
"module": "cache",
|
||||
"description": "\u57df\u540d\u7f13\u5b58\u5237\u65b0\u4efb\u52a1\u5b8c\u6210\uff0c\u6210\u529f 2/2\u3002",
|
||||
"count": int64(1),
|
||||
"requestId": "rid-20260223-003",
|
||||
},
|
||||
{
|
||||
"createdTime": "2026-02-23 10:18:11",
|
||||
"clusterId": int64(1),
|
||||
"clusterName": "gateway-cn-hz",
|
||||
"nodeId": int64(101),
|
||||
"nodeName": "hz-node-01",
|
||||
"level": "error",
|
||||
"tag": "upstream",
|
||||
"module": "resolver",
|
||||
"description": "\u4e0a\u6e38\u6743\u5a01 DNS \u8bf7\u6c42\u8d85\u65f6\uff0c\u89e6\u53d1\u91cd\u8bd5\u540e\u6062\u590d\u3002",
|
||||
"count": int64(2),
|
||||
"requestId": "rid-20260223-004",
|
||||
},
|
||||
day := strings.TrimSpace(params.DayFrom)
|
||||
if len(day) == 0 {
|
||||
day = strings.TrimSpace(params.DayTo)
|
||||
}
|
||||
logResp, err := this.RPC().HTTPDNSRuntimeLogRPC().ListHTTPDNSRuntimeLogs(this.AdminContext(), &pb.ListHTTPDNSRuntimeLogsRequest{
|
||||
Day: day,
|
||||
ClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
Level: strings.TrimSpace(params.Level),
|
||||
Keyword: strings.TrimSpace(params.Keyword),
|
||||
Offset: 0,
|
||||
Size: 100,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(strings.ToLower(params.Keyword))
|
||||
filtered := make([]map[string]interface{}, 0)
|
||||
for _, log := range allLogs {
|
||||
if params.ClusterId > 0 && log["clusterId"].(int64) != params.ClusterId {
|
||||
continue
|
||||
runtimeLogs := make([]map[string]interface{}, 0, len(logResp.GetLogs()))
|
||||
for _, item := range logResp.GetLogs() {
|
||||
createdTime := ""
|
||||
if item.GetCreatedAt() > 0 {
|
||||
createdTime = timeutil.FormatTime("Y-m-d H:i:s", item.GetCreatedAt())
|
||||
}
|
||||
if params.NodeId > 0 && log["nodeId"].(int64) != params.NodeId {
|
||||
continue
|
||||
}
|
||||
if len(params.Level) > 0 && log["level"].(string) != params.Level {
|
||||
continue
|
||||
}
|
||||
if len(params.DayFrom) > 0 {
|
||||
if log["createdTime"].(string)[:10] < params.DayFrom {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(params.DayTo) > 0 {
|
||||
if log["createdTime"].(string)[:10] > params.DayTo {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(keyword) > 0 {
|
||||
if !strings.Contains(strings.ToLower(log["tag"].(string)), keyword) &&
|
||||
!strings.Contains(strings.ToLower(log["description"].(string)), keyword) &&
|
||||
!strings.Contains(strings.ToLower(log["nodeName"].(string)), keyword) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, log)
|
||||
runtimeLogs = append(runtimeLogs, map[string]interface{}{
|
||||
"createdTime": createdTime,
|
||||
"clusterId": item.GetClusterId(),
|
||||
"clusterName": item.GetClusterName(),
|
||||
"nodeId": item.GetNodeId(),
|
||||
"nodeName": item.GetNodeName(),
|
||||
"level": item.GetLevel(),
|
||||
"tag": item.GetType(),
|
||||
"module": item.GetModule(),
|
||||
"description": item.GetDescription(),
|
||||
"count": item.GetCount(),
|
||||
"requestId": item.GetRequestId(),
|
||||
})
|
||||
}
|
||||
|
||||
this.Data["runtimeLogs"] = filtered
|
||||
this.Data["runtimeLogs"] = runtimeLogs
|
||||
this.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ package sandbox
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -17,29 +18,48 @@ func (this *IndexAction) Init() {
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
httpdnsutils.AddLeftMenu(this.Parent())
|
||||
|
||||
this.Data["clusters"] = policies.LoadAvailableDeployClusters()
|
||||
this.Data["apps"] = []map[string]interface{}{
|
||||
{
|
||||
"id": int64(1),
|
||||
"name": "主站移动业务",
|
||||
"appId": "ab12xc34s2",
|
||||
"clusterId": int64(1),
|
||||
"domains": []string{"api.business.com", "www.aliyun.com"},
|
||||
},
|
||||
{
|
||||
"id": int64(2),
|
||||
"name": "支付网关业务",
|
||||
"appId": "vd8992ksm1",
|
||||
"clusterId": int64(2),
|
||||
"domains": []string{"payment.business.com"},
|
||||
},
|
||||
{
|
||||
"id": int64(3),
|
||||
"name": "海外灰度测试",
|
||||
"appId": "ov7711hkq9",
|
||||
"clusterId": int64(1),
|
||||
"domains": []string{"global.example.com", "edge.example.com"},
|
||||
},
|
||||
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()))
|
||||
for _, cluster := range clusterResp.GetClusters() {
|
||||
clusters = append(clusters, maps.Map{
|
||||
"id": cluster.GetId(),
|
||||
"name": cluster.GetName(),
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusters
|
||||
|
||||
appResp, err := this.RPC().HTTPDNSAppRPC().FindAllHTTPDNSApps(this.AdminContext(), &pb.FindAllHTTPDNSAppsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
apps := make([]maps.Map, 0, len(appResp.GetApps()))
|
||||
for _, app := range appResp.GetApps() {
|
||||
domainResp, err := this.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(this.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||
AppDbId: app.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domains := make([]string, 0, len(domainResp.GetDomains()))
|
||||
for _, domain := range domainResp.GetDomains() {
|
||||
domains = append(domains, domain.GetDomain())
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
this.Data["apps"] = apps
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
)
|
||||
|
||||
type TestAction struct {
|
||||
@@ -21,96 +27,125 @@ func (this *TestAction) RunPost(params struct {
|
||||
ClientIp string
|
||||
Qtype string
|
||||
}) {
|
||||
if len(params.ClientIp) == 0 {
|
||||
params.ClientIp = "203.0.113.100"
|
||||
clientIP := strings.TrimSpace(params.ClientIp)
|
||||
if len(clientIP) == 0 {
|
||||
clientIP = strings.TrimSpace(loginutils.RemoteIP(&this.ActionObject))
|
||||
}
|
||||
qtype := strings.ToUpper(strings.TrimSpace(params.Qtype))
|
||||
if len(qtype) == 0 {
|
||||
qtype = "A"
|
||||
}
|
||||
|
||||
clientSubnet := this.maskSubnet(params.ClientIp, 24, 56)
|
||||
if len(clientSubnet) == 0 {
|
||||
clientSubnet = "203.0.113.0/24"
|
||||
resp, err := this.RPC().HTTPDNSSandboxRPC().TestHTTPDNSResolve(this.AdminContext(), &pb.TestHTTPDNSResolveRequest{
|
||||
ClusterId: params.ClusterId,
|
||||
AppId: strings.TrimSpace(params.AppId),
|
||||
Domain: strings.TrimSpace(params.Domain),
|
||||
Qtype: qtype,
|
||||
ClientIP: clientIP,
|
||||
Sid: "",
|
||||
SdkVersion: "",
|
||||
Os: "",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
sniPolicy := "empty"
|
||||
publicSNI := ""
|
||||
ecsMode := "off"
|
||||
ecsIPv4Prefix := 24
|
||||
ecsIPv6Prefix := 56
|
||||
pinningMode := "off"
|
||||
sanMode := "off"
|
||||
clusterDomain := ""
|
||||
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 len(clusterDomain) == 0 {
|
||||
clusterDomain = "httpdns.example.com"
|
||||
}
|
||||
query := url.Values{}
|
||||
query.Set("appId", params.AppId)
|
||||
query.Set("dn", params.Domain)
|
||||
query.Set("cip", params.ClientIp)
|
||||
query.Set("qtype", params.Qtype)
|
||||
clusterServiceDomain := policies.LoadClusterGatewayByID(params.ClusterId)
|
||||
if len(clusterServiceDomain) == 0 {
|
||||
clusterServiceDomain = "gw.httpdns.example.com"
|
||||
query.Set("qtype", qtype)
|
||||
if len(clientIP) > 0 {
|
||||
query.Set("cip", clientIP)
|
||||
}
|
||||
|
||||
signEnabled, signSecret := this.findAppSignConfig(params.AppId)
|
||||
if signEnabled && len(signSecret) > 0 {
|
||||
exp := strconv.FormatInt(time.Now().Unix()+300, 10)
|
||||
nonce := "sandbox-" + rands.HexString(16)
|
||||
sign := buildSandboxResolveSign(signSecret, params.AppId, params.Domain, qtype, exp, nonce)
|
||||
query.Set("exp", exp)
|
||||
query.Set("nonce", nonce)
|
||||
query.Set("sign", sign)
|
||||
}
|
||||
requestURL := "https://" + clusterDomain + "/resolve?" + query.Encode()
|
||||
|
||||
resultCode := 1
|
||||
if strings.EqualFold(resp.GetCode(), "SUCCESS") {
|
||||
resultCode = 0
|
||||
}
|
||||
rows := make([]maps.Map, 0, len(resp.GetRecords()))
|
||||
ips := make([]string, 0, len(resp.GetRecords()))
|
||||
lineName := strings.TrimSpace(resp.GetClientCarrier())
|
||||
if len(lineName) == 0 {
|
||||
lineName = "-"
|
||||
}
|
||||
for _, record := range resp.GetRecords() {
|
||||
ips = append(ips, record.GetIp())
|
||||
if lineName == "-" && len(record.GetLine()) > 0 {
|
||||
lineName = record.GetLine()
|
||||
}
|
||||
rows = append(rows, maps.Map{
|
||||
"domain": resp.GetDomain(),
|
||||
"type": record.GetType(),
|
||||
"ip": record.GetIp(),
|
||||
"ttl": record.GetTtl(),
|
||||
"region": record.GetRegion(),
|
||||
"line": record.GetLine(),
|
||||
})
|
||||
}
|
||||
requestURL := "https://" + clusterServiceDomain + "/resolve?" + query.Encode()
|
||||
|
||||
this.Data["result"] = maps.Map{
|
||||
"code": 0,
|
||||
"message": "ok (mock)",
|
||||
"requestId": "mock-rid-20260221-001",
|
||||
"code": resultCode,
|
||||
"message": resp.GetMessage(),
|
||||
"requestId": resp.GetRequestId(),
|
||||
"data": maps.Map{
|
||||
"request_url": requestURL,
|
||||
"client_ip": params.ClientIp,
|
||||
"client_region": "中国, 上海, 上海",
|
||||
"line_name": "默认线路",
|
||||
"ips": []string{"203.0.113.10", "203.0.113.11"},
|
||||
"records": []maps.Map{
|
||||
{
|
||||
"domain": params.Domain,
|
||||
"type": params.Qtype,
|
||||
"ip": "203.0.113.10",
|
||||
"ttl": 30,
|
||||
"region": "中国-上海-上海",
|
||||
"line": "默认线路",
|
||||
},
|
||||
{
|
||||
"domain": params.Domain,
|
||||
"type": params.Qtype,
|
||||
"ip": "203.0.113.11",
|
||||
"ttl": 30,
|
||||
"region": "中国-上海-上海",
|
||||
"line": "默认线路",
|
||||
},
|
||||
},
|
||||
"ttl": 30,
|
||||
"sni_policy": sniPolicy,
|
||||
"public_sni": publicSNI,
|
||||
"client_subnet": clientSubnet,
|
||||
"ecs_mode": ecsMode,
|
||||
"ecs_ipv4_prefix": ecsIPv4Prefix,
|
||||
"ecs_ipv6_prefix": ecsIPv6Prefix,
|
||||
"pinning_mode": pinningMode,
|
||||
"san_mode": sanMode,
|
||||
"fingerprint_algo": "sha256",
|
||||
"cert_fingerprints": []string{"8f41ab8d32fca7f9a2b0fdd09a0e2ccf31e5579cd3a0b65f1a9f2f2ec8f38d4a"},
|
||||
"verify_headers": maps.Map{
|
||||
"X-Edge-Resolve-AppId": params.AppId,
|
||||
"X-Edge-Resolve-Expire": "1768953600",
|
||||
"X-Edge-Resolve-Nonce": "mock-nonce-123456",
|
||||
"X-Edge-Resolve-Target": params.Domain,
|
||||
"X-Edge-Resolve-Sign": "mock-signature-base64url",
|
||||
},
|
||||
"client_ip": resp.GetClientIP(),
|
||||
"client_region": resp.GetClientRegion(),
|
||||
"line_name": lineName,
|
||||
"ips": ips,
|
||||
"records": rows,
|
||||
"ttl": resp.GetTtl(),
|
||||
},
|
||||
}
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func (this *TestAction) maskSubnet(ip string, ipv4Prefix int, ipv6Prefix int) string {
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed == nil {
|
||||
return ""
|
||||
func (this *TestAction) findAppSignConfig(appId string) (bool, string) {
|
||||
appId = strings.TrimSpace(appId)
|
||||
if len(appId) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
ipv4 := parsed.To4()
|
||||
if ipv4 != nil {
|
||||
mask := net.CIDRMask(ipv4Prefix, 32)
|
||||
return ipv4.Mask(mask).String() + "/" + strconv.Itoa(ipv4Prefix)
|
||||
resp, err := this.RPC().HTTPDNSAppRPC().FindAllHTTPDNSApps(this.AdminContext(), &pb.FindAllHTTPDNSAppsRequest{})
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
mask := net.CIDRMask(ipv6Prefix, 128)
|
||||
return parsed.Mask(mask).String() + "/" + strconv.Itoa(ipv6Prefix)
|
||||
for _, app := range resp.GetApps() {
|
||||
if strings.EqualFold(strings.TrimSpace(app.GetAppId()), appId) {
|
||||
return app.GetSignEnabled(), strings.TrimSpace(app.GetSignSecret())
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func buildSandboxResolveSign(signSecret string, appID string, domain string, qtype string, exp string, nonce string) string {
|
||||
raw := strings.TrimSpace(appID) + "|" + strings.ToLower(strings.TrimSpace(domain)) + "|" + strings.ToUpper(strings.TrimSpace(qtype)) + "|" + strings.TrimSpace(exp) + "|" + strings.TrimSpace(nonce)
|
||||
mac := hmac.New(sha256.New, []byte(strings.TrimSpace(signSecret)))
|
||||
_, _ = mac.Write([]byte(raw))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
@@ -141,9 +141,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/apps"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/clusters"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/ech"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/guide"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/clusters"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/sandbox"
|
||||
|
||||
Reference in New Issue
Block a user