前端页面

This commit is contained in:
robin
2026-02-24 19:10:27 +08:00
parent 60dc87e0f2
commit 2eb32b9f1f
59 changed files with 1537 additions and 890 deletions

View File

@@ -0,0 +1,123 @@
package httpdns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
"strings"
)
type AddPortPopupAction struct {
actionutils.ParentAction
}
func (this *AddPortPopupAction) Init() {
this.Nav("", "", "")
}
func (this *AddPortPopupAction) RunGet(params struct {
Protocol string
From string
SupportRange bool
}) {
this.Data["from"] = params.From
var protocols = serverconfigs.FindAllServerProtocols()
if len(params.Protocol) > 0 {
result := []maps.Map{}
for _, p := range protocols {
if p.GetString("code") == params.Protocol {
result = append(result, p)
}
}
protocols = result
}
this.Data["protocols"] = protocols
this.Data["supportRange"] = params.SupportRange
this.Show()
}
func (this *AddPortPopupAction) RunPost(params struct {
SupportRange bool
Protocol string
Address string
Must *actions.Must
}) {
// 校验地址
var addr = maps.Map{
"protocol": params.Protocol,
"host": "",
"portRange": "",
"minPort": 0,
"maxPort": 0,
}
var portRegexp = regexp.MustCompile(`^\d+$`)
if portRegexp.MatchString(params.Address) { // 单个端口
addr["portRange"] = this.checkPort(params.Address)
} else if params.SupportRange && regexp.MustCompile(`^\d+\s*-\s*\d+$`).MatchString(params.Address) { // Port1-Port2
addr["portRange"], addr["minPort"], addr["maxPort"] = this.checkPortRange(params.Address)
} else if strings.Contains(params.Address, ":") { // IP:Port
index := strings.LastIndex(params.Address, ":")
addr["host"] = strings.TrimSpace(params.Address[:index])
port := strings.TrimSpace(params.Address[index+1:])
if portRegexp.MatchString(port) {
addr["portRange"] = this.checkPort(port)
} else if params.SupportRange && regexp.MustCompile(`^\d+\s*-\s*\d+$`).MatchString(port) { // Port1-Port2
addr["portRange"], addr["minPort"], addr["maxPort"] = this.checkPortRange(port)
} else {
this.FailField("address", "请输入正确的端口或者网络地址")
}
} else {
this.FailField("address", "请输入正确的端口或者网络地址")
}
this.Data["address"] = addr
this.Success()
}
func (this *AddPortPopupAction) checkPort(port string) (portRange string) {
var intPort = types.Int(port)
if intPort < 1 {
this.FailField("address", "端口号不能小于1")
}
if intPort > 65535 {
this.FailField("address", "端口号不能大于65535")
}
return port
}
func (this *AddPortPopupAction) checkPortRange(port string) (portRange string, minPort int, maxPort int) {
var pieces = strings.Split(port, "-")
var piece1 = strings.TrimSpace(pieces[0])
var piece2 = strings.TrimSpace(pieces[1])
var port1 = types.Int(piece1)
var port2 = types.Int(piece2)
if port1 < 1 {
this.FailField("address", "端口号不能小于1")
}
if port1 > 65535 {
this.FailField("address", "端口号不能大于65535")
}
if port2 < 1 {
this.FailField("address", "端口号不能小于1")
}
if port2 > 65535 {
this.FailField("address", "端口号不能大于65535")
}
if port1 > port2 {
port1, port2 = port2, port1
}
return types.String(port1) + "-" + types.String(port2), port1, port2
}

View File

@@ -1,8 +1,11 @@
package apps
import (
"strconv"
"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/iwind/TeaGo/actions"
)
@@ -11,15 +14,41 @@ type AppSettingsAction struct {
}
func (this *AppSettingsAction) Init() {
this.Nav("httpdns", "app", "")
this.Nav("httpdns", "app", "settings")
}
func (this *AppSettingsAction) RunGet(params struct {
AppId int64
AppId int64
Section string
}) {
httpdnsutils.AddLeftMenu(this.Parent())
app := pickApp(params.AppId)
// 顶部 tabbar
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "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{}{
{
"name": "基础配置",
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "&section=basic",
"isActive": section == "basic",
},
{
"name": "认证与密钥",
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "&section=auth",
"isActive": section == "auth",
},
}
settings := loadAppSettings(app)
this.Data["clusters"] = policies.LoadAvailableDeployClusters()
this.Data["app"] = app
this.Data["settings"] = settings
this.Show()
@@ -28,16 +57,24 @@ func (this *AppSettingsAction) RunGet(params struct {
func (this *AppSettingsAction) RunPost(params struct {
AppId int64
AppStatus bool
AppStatus bool
PrimaryClusterId int64
BackupClusterId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.Field("appId", params.AppId).Gt(0, "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")
}
app := pickApp(params.AppId)
settings := loadAppSettings(app)
settings["appStatus"] = params.AppStatus
settings["primaryClusterId"] = params.PrimaryClusterId
settings["backupClusterId"] = params.BackupClusterId
// SNI strategy is fixed to level2 empty.
settings["sniPolicy"] = "level2"

View File

@@ -20,6 +20,8 @@ func defaultAppSettings(app maps.Map) maps.Map {
aesSecretPlain := randomPlainSecret("as")
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",
@@ -45,6 +47,8 @@ func defaultAppSettings(app maps.Map) maps.Map {
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"),
@@ -90,6 +94,12 @@ func saveAppSettings(appId int64, settings maps.Map) {
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")
@@ -144,6 +154,19 @@ func maskSecret(secret string) string {
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")

View File

@@ -19,31 +19,45 @@ func (this *CreatePopupAction) RunGet(params struct{}) {
clusters := policies.LoadAvailableDeployClusters()
this.Data["clusters"] = clusters
defaultClusterID := policies.LoadDefaultClusterID()
if defaultClusterID <= 0 && len(clusters) > 0 {
defaultClusterID = clusters[0].GetInt64("id")
defaultPrimaryClusterId := policies.LoadDefaultClusterID()
if defaultPrimaryClusterId <= 0 && len(clusters) > 0 {
defaultPrimaryClusterId = clusters[0].GetInt64("id")
}
this.Data["defaultClusterId"] = defaultClusterID
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": "张三", "username": "zhangsan"},
{"id": int64(2), "name": "李四", "username": "lisi"},
{"id": int64(3), "name": "王五", "username": "wangwu"},
{"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
ClusterId int64
UserId int64
Name string
PrimaryClusterId int64
BackupClusterId int64
UserId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.Field("name", params.Name).Require("请输入应用名称")
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择所属集群")
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()
}

View File

@@ -21,6 +21,9 @@ 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")
domains := mockDomains(app.GetInt64("id"))
domain := pickDomainFromDomains(domains, params.DomainId)
domainName := domain.GetString("name")
@@ -35,7 +38,6 @@ func (this *CustomRecordsAction) RunGet(params struct {
for _, record := range records {
record["lineText"] = buildLineText(record)
record["paramsText"] = buildParamsText(record)
record["recordValueText"] = buildRecordValueText(record)
}

View File

@@ -42,7 +42,6 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
"weightEnabled": false,
"ttl": 30,
"isOn": true,
"sdnsParamsJson": "[]",
"recordItemsJson": `[{"type":"A","value":"","weight":100}]`,
}
@@ -65,9 +64,6 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
record["ttl"] = existing.GetInt("ttl")
record["isOn"] = existing.GetBool("isOn")
sdnsParams, _ := existing["sdnsParams"].([]maps.Map)
record["sdnsParamsJson"] = marshalJSON(sdnsParams, "[]")
recordItems := make([]maps.Map, 0)
recordType := strings.ToUpper(strings.TrimSpace(existing.GetString("recordType")))
values, _ := existing["recordValues"].([]maps.Map)
@@ -140,7 +136,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
LineCountry string
RuleName string
SDNSParamsJSON string
RecordItemsJSON string
WeightEnabled bool
TTL int
@@ -154,7 +149,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
params.Domain = strings.TrimSpace(params.Domain)
params.LineScope = strings.ToLower(strings.TrimSpace(params.LineScope))
params.RuleName = strings.TrimSpace(params.RuleName)
params.SDNSParamsJSON = strings.TrimSpace(params.SDNSParamsJSON)
params.RecordItemsJSON = strings.TrimSpace(params.RecordItemsJSON)
domain := maps.Map{}
@@ -181,16 +175,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
return
}
sdnsParams, err := parseSDNSParamsJSON(params.SDNSParamsJSON)
if err != nil {
this.Fail(err.Error())
return
}
if len(sdnsParams) > 10 {
this.Fail("sdns params should be <= 10")
return
}
recordValues, err := parseRecordItemsJSON(params.RecordItemsJSON, params.WeightEnabled)
if err != nil {
this.Fail(err.Error())
@@ -250,7 +234,7 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
"lineContinent": lineContinent,
"lineCountry": lineCountry,
"ruleName": params.RuleName,
"sdnsParams": sdnsParams,
"sdnsParams": []maps.Map{},
"recordType": recordType,
"recordValues": recordValues,
"weightEnabled": params.WeightEnabled,
@@ -261,38 +245,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
this.Success()
}
func parseSDNSParamsJSON(raw string) ([]maps.Map, error) {
if len(raw) == 0 {
return []maps.Map{}, nil
}
list := []maps.Map{}
if err := json.Unmarshal([]byte(raw), &list); err != nil {
return nil, fmt.Errorf("sdns params json is invalid")
}
result := make([]maps.Map, 0, len(list))
for _, item := range list {
name := strings.TrimSpace(item.GetString("name"))
value := strings.TrimSpace(item.GetString("value"))
if len(name) == 0 && len(value) == 0 {
continue
}
if len(name) < 2 || len(name) > 64 {
return nil, fmt.Errorf("sdns param name length should be in 2-64")
}
if len(value) < 1 || len(value) > 64 {
return nil, fmt.Errorf("sdns param value length should be in 1-64")
}
result = append(result, maps.Map{
"name": name,
"value": value,
})
}
return result, nil
}
func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
if len(raw) == 0 {
return []maps.Map{}, nil

View File

@@ -18,19 +18,14 @@ var customRecordStore = struct {
data: map[int64][]maps.Map{
1: {
{
"id": int64(1001),
"domain": "api.business.com",
"lineScope": "china",
"lineCarrier": "电信",
"lineRegion": "华东",
"lineProvince": "上海",
"ruleName": "上海电信灰度-v2",
"sdnsParams": []maps.Map{
{
"name": "app_ver",
"value": "2.3.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,
@@ -134,6 +129,12 @@ func deleteCustomRecord(appID int64, recordID int64) {
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()
@@ -207,27 +208,6 @@ func buildLineText(record maps.Map) string {
return strings.Join(finalParts, " / ")
}
func buildParamsText(record maps.Map) string {
params, ok := record["sdnsParams"].([]maps.Map)
if !ok || len(params) == 0 {
return "-"
}
parts := make([]string, 0, len(params))
for _, param := range params {
name := strings.TrimSpace(param.GetString("name"))
value := strings.TrimSpace(param.GetString("value"))
if len(name) == 0 || len(value) == 0 {
continue
}
parts = append(parts, name+"="+value)
}
if len(parts) == 0 {
return "-"
}
return strings.Join(parts, "; ")
}
func buildRecordValueText(record maps.Map) string {
values, ok := record["recordValues"].([]maps.Map)
if !ok || len(values) == 0 {

View File

@@ -0,0 +1,38 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) Init() {
this.Nav("httpdns", "app", "delete")
}
func (this *DeleteAction) RunGet(params struct {
AppId int64
}) {
httpdnsutils.AddLeftMenu(this.Parent())
app := pickApp(params.AppId)
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "delete")
this.Data["app"] = app
this.Data["domainCount"] = len(mockDomains(app.GetInt64("id")))
this.Show()
}
func (this *DeleteAction) RunPost(params struct {
AppId int64
}) {
if params.AppId > 0 {
if deleteApp(params.AppId) {
deleteAppSettings(params.AppId)
deleteCustomRecordsByApp(params.AppId)
}
}
this.Success()
}

View File

@@ -10,7 +10,7 @@ type DomainsAction struct {
}
func (this *DomainsAction) Init() {
this.Nav("httpdns", "app", "")
this.Nav("httpdns", "app", "domains")
}
func (this *DomainsAction) RunGet(params struct {
@@ -19,6 +19,9 @@ func (this *DomainsAction) RunGet(params struct {
httpdnsutils.AddLeftMenu(this.Parent())
app := pickApp(params.AppId)
// 构建顶部 tabbar
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
domains := mockDomains(app.GetInt64("id"))
for _, domain := range domains {
domainName := domain.GetString("name")

View File

@@ -15,6 +15,7 @@ func init() {
Prefix("/httpdns/apps").
Get("", new(IndexAction)).
Get("/app", new(AppAction)).
Get("/sdk", new(SDKAction)).
GetPost("/app/settings", new(AppSettingsAction)).
Post("/app/settings/toggleSignEnabled", new(AppSettingsToggleSignEnabledAction)).
Post("/app/settings/resetSignSecret", new(AppSettingsResetSignSecretAction)).
@@ -22,6 +23,7 @@ func init() {
Get("/domains", new(DomainsAction)).
Get("/customRecords", new(CustomRecordsAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/delete", new(DeleteAction)).
GetPost("/domains/createPopup", new(DomainsCreatePopupAction)).
Post("/domains/delete", new(DomainsDeleteAction)).
GetPost("/customRecords/createPopup", new(CustomRecordsCreatePopupAction)).

View File

@@ -2,15 +2,23 @@ package apps
import (
"strings"
"sync"
"github.com/iwind/TeaGo/maps"
)
func mockApps() []maps.Map {
var appStore = struct {
sync.RWMutex
data []maps.Map
}{
data: defaultMockApps(),
}
func defaultMockApps() []maps.Map {
return []maps.Map{
{
"id": int64(1),
"name": "主站移动业务",
"name": "\u4e3b\u7ad9\u79fb\u52a8\u4e1a\u52a1",
"appId": "ab12xc34s2",
"clusterId": int64(1),
"domainCount": 3,
@@ -20,12 +28,12 @@ func mockApps() []maps.Map {
"pinningMode": "report",
"sanMode": "strict",
"riskLevel": "medium",
"riskSummary": "Pinning 处于观察模式",
"riskSummary": "Pinning \u5904\u4e8e\u89c2\u5bdf\u6a21\u5f0f",
"secretVersion": "v2026.02.20",
},
{
"id": int64(2),
"name": "视频网关业务",
"name": "\u89c6\u9891\u7f51\u5173\u4e1a\u52a1",
"appId": "vd8992ksm1",
"clusterId": int64(2),
"domainCount": 1,
@@ -35,12 +43,12 @@ func mockApps() []maps.Map {
"pinningMode": "enforce",
"sanMode": "strict",
"riskLevel": "low",
"riskSummary": "已启用强校验",
"riskSummary": "\u5df2\u542f\u7528\u5f3a\u6821\u9a8c",
"secretVersion": "v2026.02.18",
},
{
"id": int64(3),
"name": "海外灰度测试",
"name": "\u6d77\u5916\u7070\u5ea6\u6d4b\u8bd5",
"appId": "ov7711hkq9",
"clusterId": int64(1),
"domainCount": 2,
@@ -50,12 +58,57 @@ func mockApps() []maps.Map {
"pinningMode": "off",
"sanMode": "report",
"riskLevel": "high",
"riskSummary": "应用关闭且证书策略偏弱",
"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 {
@@ -89,6 +142,15 @@ func filterApps(keyword string, riskLevel string, ecsMode string, pinningMode st
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]
}

View File

@@ -0,0 +1,27 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
)
type SDKAction struct {
actionutils.ParentAction
}
func (this *SDKAction) Init() {
this.Nav("httpdns", "app", "sdk")
}
func (this *SDKAction) RunGet(params struct {
AppId int64
}) {
httpdnsutils.AddLeftMenu(this.Parent())
app := pickApp(params.AppId)
// 构建顶部 tabbar
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
this.Data["app"] = app
this.Show()
}

View File

@@ -21,6 +21,10 @@ func (this *ClusterAction) RunGet(params struct {
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
this.Data["clusterId"] = params.ClusterId
this.Data["cluster"] = cluster
this.Data["installState"] = params.InstalledState

View File

@@ -1,8 +1,17 @@
package clusters
import (
"encoding/json"
"strconv"
"strings"
"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/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type ClusterSettingsAction struct {
@@ -15,35 +24,184 @@ func (this *ClusterSettingsAction) Init() {
func (this *ClusterSettingsAction) RunGet(params struct {
ClusterId int64
Section string
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
installDir := cluster.GetString("installDir")
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
settings := loadClusterSettings(cluster)
cluster["name"] = settings.GetString("name")
// 构建顶部 tabbar
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "setting")
// 当前选中的 section
section := params.Section
if len(section) == 0 {
section = "basic"
}
this.Data["activeSection"] = section
// 左侧菜单
cid := strconv.FormatInt(params.ClusterId, 10)
this.Data["leftMenuItems"] = []map[string]interface{}{
{"name": "基础设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "&section=basic", "isActive": section == "basic"},
{"name": "TLS", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "&section=tls", "isActive": section == "tls"},
}
settings["isDefaultCluster"] = (policies.LoadDefaultClusterID() == cluster.GetInt64("id"))
this.Data["cluster"] = cluster
this.Data["settings"] = map[string]interface{}{
"region": cluster.GetString("region"),
"gatewayDomain": cluster.GetString("gatewayDomain"),
"cacheTtl": cluster.GetInt("cacheTtl"),
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
"installDir": installDir,
"isOn": cluster.GetBool("isOn"),
// 构造前端需要的 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,
}
}
this.Data["tlsConfig"] = maps.Map{
"isOn": true,
"listen": listenAddresses,
"sslPolicy": sslPolicy,
}
this.Data["cluster"] = cluster
this.Data["settings"] = settings
this.Show()
}
func (this *ClusterSettingsAction) RunPost(params struct {
ClusterId int64
Name string
Region string
GatewayDomain string
CacheTtl int32
FallbackTimeout int32
InstallDir string
IsOn bool
ClusterId int64
Name string
GatewayDomain string
CacheTtl int32
FallbackTimeout int32
InstallDir string
IsOn bool
IsDefaultCluster bool
Addresses []byte
SslPolicyJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// Mock successful save
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")
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)
}
// 处理 SSL 配置
var originCertPem = ""
var originKeyPem = ""
var tlsMinVersion = "TLS 1.1"
var tlsCipherSuitesOn = false
var tlsOcspOn = false
var tlsClientAuthType = sslconfigs.SSLClientAuthType(0)
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 len(sslPolicy.Certs) > 0 {
cert := sslPolicy.Certs[0]
originCertPem = string(cert.CertData)
originKeyPem = string(cert.KeyData)
}
}
}
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)
}
this.Success()
}

View File

@@ -0,0 +1,110 @@
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")
}

View File

@@ -3,7 +3,6 @@ package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/iwind/TeaGo/maps"
)
type CreateNodeAction struct {
@@ -11,14 +10,18 @@ type CreateNodeAction struct {
}
func (this *CreateNodeAction) Init() {
this.Nav("", "node", "createNode")
this.SecondMenu("nodes")
this.Nav("httpdns", "cluster", "createNode")
}
func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
this.Data["clusterId"] = params.ClusterId
this.Data["cluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
this.Data["cluster"] = cluster
this.Show()
}

View File

@@ -18,6 +18,10 @@ func (this *DeleteAction) RunGet(params struct {
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "delete")
this.Data["cluster"] = cluster
this.Show()
}

View File

@@ -3,7 +3,7 @@ package clusters
import "github.com/iwind/TeaGo/maps"
func mockClusters() []maps.Map {
return []maps.Map{
clusters := []maps.Map{
{
"id": int64(1),
"name": "gateway-cn-hz",
@@ -31,6 +31,12 @@ func mockClusters() []maps.Map {
"isOn": true,
},
}
for _, cluster := range clusters {
applyClusterSettingsOverrides(cluster)
}
return clusters
}
func pickCluster(clusterId int64) maps.Map {

View File

@@ -1,66 +1,15 @@
package guide
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
httpdnsApps "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/apps"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
httpdnsPolicies "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
"github.com/iwind/TeaGo/maps"
)
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("httpdns", "guide", "")
this.Nav("httpdns", "app", "")
}
func (this *IndexAction) RunGet(params struct{}) {
httpdnsutils.AddLeftMenu(this.Parent())
apps := []maps.Map{
{
"id": int64(1),
"name": "主站移动业务",
"appId": "ab12xc34s2",
"clusterId": int64(1),
},
{
"id": int64(2),
"name": "视频网关业务",
"appId": "vd8992ksm1",
"clusterId": int64(2),
},
{
"id": int64(3),
"name": "海外灰度测试",
"appId": "ov7711hkq9",
"clusterId": int64(1),
},
}
for _, app := range apps {
clusterID := app.GetInt64("clusterId")
app["gatewayDomain"] = httpdnsPolicies.LoadClusterGatewayByID(clusterID)
settings := httpdnsApps.LoadAppSettingsByAppID(app.GetString("appId"))
if settings == nil {
app["signSecret"] = ""
app["signSecretMasked"] = ""
app["aesSecret"] = ""
app["aesSecretMasked"] = ""
app["signEnabled"] = false
continue
}
app["signSecret"] = settings.GetString("signSecretPlain")
app["signSecretMasked"] = settings.GetString("signSecretMasked")
app["aesSecret"] = settings.GetString("aesSecretPlain")
app["aesSecretMasked"] = settings.GetString("aesSecretMasked")
app["signEnabled"] = settings.GetBool("signEnabled")
}
this.Data["apps"] = apps
this.Show()
this.RedirectURL("/httpdns/apps")
}

View File

@@ -1,6 +1,8 @@
package httpdnsutils
import (
"strconv"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
)
@@ -11,39 +13,55 @@ func AddLeftMenu(action *actionutils.ParentAction) {
action.Data["teaSubMenu"] = tab
action.Data["leftMenuItems"] = []maps.Map{
{
"name": "集群管理",
"name": "\u96c6\u7fa4\u7ba1\u7406",
"url": "/httpdns/clusters",
"isActive": tab == "cluster",
},
{
"name": "全局配置",
"url": "/httpdns/policies",
"isActive": tab == "policy",
},
{
"name": "应用管理",
"name": "\u5e94\u7528\u7ba1\u7406",
"url": "/httpdns/apps",
"isActive": tab == "app",
},
{
"name": "SDK接入引导",
"url": "/httpdns/guide",
"isActive": tab == "guide",
},
{
"name": "解析日志",
"name": "\u8bbf\u95ee\u65e5\u5fd7",
"url": "/httpdns/resolveLogs",
"isActive": tab == "resolveLogs",
},
{
"name": "运行日志",
"name": "\u8fd0\u884c\u65e5\u5fd7",
"url": "/httpdns/runtimeLogs",
"isActive": tab == "runtimeLogs",
},
{
"name": "解析测试",
"name": "\u89e3\u6790\u6d4b\u8bd5",
"url": "/httpdns/sandbox",
"isActive": tab == "sandbox",
},
}
}
// AddClusterTabbar builds the top tabbar on cluster pages.
func AddClusterTabbar(action *actionutils.ParentAction, clusterName string, clusterId int64, selectedTab string) {
cid := strconv.FormatInt(clusterId, 10)
tabbar := actionutils.NewTabbar()
tabbar.Add("", "", "/httpdns/clusters", "arrow left", false)
titleItem := tabbar.Add(clusterName, "", "/httpdns/clusters/cluster?clusterId="+cid, "angle right", true)
titleItem.IsTitle = true
tabbar.Add("\u8282\u70b9\u5217\u8868", "", "/httpdns/clusters/cluster?clusterId="+cid, "server", selectedTab == "node")
tabbar.Add("\u96c6\u7fa4\u8bbe\u7f6e", "", "/httpdns/clusters/cluster/settings?clusterId="+cid, "setting", selectedTab == "setting")
tabbar.Add("\u5220\u9664\u96c6\u7fa4", "", "/httpdns/clusters/delete?clusterId="+cid, "trash", selectedTab == "delete")
actionutils.SetTabbar(action, tabbar)
}
// AddAppTabbar builds the top tabbar on app pages.
func AddAppTabbar(action *actionutils.ParentAction, appName string, appId int64, selectedTab string) {
aid := strconv.FormatInt(appId, 10)
tabbar := actionutils.NewTabbar()
tabbar.Add("", "", "/httpdns/apps", "arrow left", false)
titleItem := tabbar.Add(appName, "", "/httpdns/apps/domains?appId="+aid, "angle right", true)
titleItem.IsTitle = true
tabbar.Add("\u57df\u540d\u5217\u8868", "", "/httpdns/apps/domains?appId="+aid, "list", selectedTab == "domains")
tabbar.Add("\u5e94\u7528\u8bbe\u7f6e", "", "/httpdns/apps/app/settings?appId="+aid, "setting", selectedTab == "settings")
tabbar.Add("\u5220\u9664\u5e94\u7528", "", "/httpdns/apps/delete?appId="+aid, "trash", selectedTab == "delete")
actionutils.SetTabbar(action, tabbar)
}

View File

@@ -14,6 +14,7 @@ func init() {
Data("teaSubMenu", "cluster").
Prefix("/httpdns").
Get("", new(IndexAction)).
GetPost("/addPortPopup", new(AddPortPopupAction)).
EndAll()
})
}

View File

@@ -23,10 +23,9 @@ func (this *IndexAction) RunGet(params struct{}) {
}
func (this *IndexAction) RunPost(params struct {
DefaultClusterId int64
EnableUserDomainVerify bool
DefaultTTL int
DefaultFallbackMs int
DefaultClusterId int64
DefaultTTL int
DefaultFallbackMs int
Must *actions.Must
CSRF *actionutils.CSRF
@@ -50,7 +49,7 @@ func (this *IndexAction) RunPost(params struct {
saveGlobalPolicies(maps.Map{
"defaultClusterId": params.DefaultClusterId,
"enableUserDomainVerify": params.EnableUserDomainVerify,
"enableUserDomainVerify": true,
"defaultTTL": params.DefaultTTL,
"defaultFallbackMs": params.DefaultFallbackMs,
})

View File

@@ -51,7 +51,7 @@ func loadGlobalPolicies() maps.Map {
return maps.Map{
"defaultClusterId": globalPoliciesStore.data.GetInt64("defaultClusterId"),
"enableUserDomainVerify": globalPoliciesStore.data.GetBool("enableUserDomainVerify"),
"enableUserDomainVerify": true,
"defaultTTL": globalPoliciesStore.data.GetInt("defaultTTL"),
"defaultFallbackMs": globalPoliciesStore.data.GetInt("defaultFallbackMs"),
}
@@ -61,7 +61,7 @@ func saveGlobalPolicies(policies maps.Map) {
globalPoliciesStore.Lock()
globalPoliciesStore.data = maps.Map{
"defaultClusterId": policies.GetInt64("defaultClusterId"),
"enableUserDomainVerify": policies.GetBool("enableUserDomainVerify"),
"enableUserDomainVerify": true,
"defaultTTL": policies.GetInt("defaultTTL"),
"defaultFallbackMs": policies.GetInt("defaultFallbackMs"),
}
@@ -110,6 +110,14 @@ func LoadDefaultClusterID() int64 {
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()

View File

@@ -25,8 +25,16 @@ func (this *IndexAction) RunGet(params struct {
}) {
httpdnsutils.AddLeftMenu(this.Parent())
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
if params.ClusterId > 0 {
this.Data["clusterId"] = params.ClusterId
} else {
this.Data["clusterId"] = ""
}
if params.NodeId > 0 {
this.Data["nodeId"] = params.NodeId
} else {
this.Data["nodeId"] = ""
}
this.Data["appId"] = params.AppId
this.Data["domain"] = params.Domain
this.Data["status"] = params.Status

View File

@@ -25,8 +25,16 @@ func (this *IndexAction) RunGet(params struct {
}) {
httpdnsutils.AddLeftMenu(this.Parent())
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
if params.ClusterId > 0 {
this.Data["clusterId"] = params.ClusterId
} else {
this.Data["clusterId"] = ""
}
if params.NodeId > 0 {
this.Data["nodeId"] = params.NodeId
} else {
this.Data["nodeId"] = ""
}
this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo
this.Data["level"] = params.Level
@@ -123,7 +131,6 @@ func (this *IndexAction) RunGet(params struct {
}
if len(keyword) > 0 {
if !strings.Contains(strings.ToLower(log["tag"].(string)), keyword) &&
!strings.Contains(strings.ToLower(log["module"].(string)), keyword) &&
!strings.Contains(strings.ToLower(log["description"].(string)), keyword) &&
!strings.Contains(strings.ToLower(log["nodeName"].(string)), keyword) {
continue

View File

@@ -1,12 +1,9 @@
package sandbox
import (
"encoding/json"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
@@ -17,22 +14,14 @@ type TestAction struct {
}
func (this *TestAction) RunPost(params struct {
AppId string
Domain string
ClientIp string
Qtype string
SDNSParamsJSON string
AppId string
Domain string
ClientIp string
Qtype string
}) {
if len(params.ClientIp) == 0 {
params.ClientIp = "203.0.113.100"
}
params.SDNSParamsJSON = strings.TrimSpace(params.SDNSParamsJSON)
sdnsParams, err := parseSDNSParamsJSON(params.SDNSParamsJSON)
if err != nil {
this.Fail(err.Error())
return
}
clientSubnet := this.maskSubnet(params.ClientIp, 24, 56)
if len(clientSubnet) == 0 {
@@ -51,9 +40,6 @@ func (this *TestAction) RunPost(params struct {
query.Set("dn", params.Domain)
query.Set("cip", params.ClientIp)
query.Set("qtype", params.Qtype)
for _, item := range sdnsParams {
query.Add("sdns_"+item.GetString("name"), item.GetString("value"))
}
requestURL := "https://api.httpdns.example.com/resolve?" + query.Encode()
this.Data["result"] = maps.Map{
@@ -61,12 +47,11 @@ func (this *TestAction) RunPost(params struct {
"message": "ok (mock)",
"requestId": "mock-rid-20260221-001",
"data": maps.Map{
"request_url": requestURL,
"client_ip": params.ClientIp,
"client_region": "中国, 上海, 上海",
"line_name": "默认线路",
"sdns_params": sdnsParams,
"ips": []string{"203.0.113.10", "203.0.113.11"},
"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,
@@ -108,44 +93,6 @@ func (this *TestAction) RunPost(params struct {
this.Success()
}
func parseSDNSParamsJSON(raw string) ([]maps.Map, error) {
if len(raw) == 0 {
return []maps.Map{}, nil
}
list := []maps.Map{}
if err := json.Unmarshal([]byte(raw), &list); err != nil {
return nil, err
}
if len(list) > 10 {
return nil, fmt.Errorf("sdns params should be <= 10")
}
result := make([]maps.Map, 0, len(list))
for _, item := range list {
name := strings.TrimSpace(item.GetString("name"))
value := strings.TrimSpace(item.GetString("value"))
if len(name) == 0 && len(value) == 0 {
continue
}
if len(name) == 0 || len(name) > 64 {
return nil, fmt.Errorf("sdns param name should be in 1-64")
}
if len(value) == 0 || len(value) > 64 {
return nil, fmt.Errorf("sdns param value should be in 1-64")
}
result = append(result, maps.Map{
"name": name,
"value": value,
})
}
if len(result) > 10 {
return nil, fmt.Errorf("sdns params should be <= 10")
}
return result, nil
}
func (this *TestAction) maskSubnet(ip string, ipv4Prefix int, ipv6Prefix int) string {
parsed := net.ParseIP(ip)
if parsed == nil {