diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/addPortPopup.go b/EdgeAdmin/internal/web/actions/default/httpdns/addPortPopup.go new file mode 100644 index 0000000..d652e1b --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/httpdns/addPortPopup.go @@ -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 +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings.go index 4797a52..7a1e314 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings.go @@ -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 + "§ion=basic", + "isActive": section == "basic", + }, + { + "name": "认证与密钥", + "url": "/httpdns/apps/app/settings?appId=" + appIdStr + "§ion=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" diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings_store.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings_store.go index 8610b9f..6026936 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings_store.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/appSettings_store.go @@ -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") diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/createPopup.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/createPopup.go index b5aa52b..a71a42c 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/createPopup.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/createPopup.go @@ -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() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecords.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecords.go index d269339..1257bf1 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecords.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecords.go @@ -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) } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecordsCreatePopup.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecordsCreatePopup.go index f3762e4..22d6d63 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecordsCreatePopup.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/customRecordsCreatePopup.go @@ -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 diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/custom_records_store.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/custom_records_store.go index a29f554..d0419d1 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/custom_records_store.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/custom_records_store.go @@ -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 { diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/delete.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/delete.go new file mode 100644 index 0000000..6ce6c19 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/delete.go @@ -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() +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/domains.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/domains.go index 2833df8..08ef685 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/domains.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/domains.go @@ -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") diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/init.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/init.go index 28bcb4c..152b4c9 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/init.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/init.go @@ -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)). diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/mock.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/mock.go index fe383c5..0587dd5 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/mock.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/mock.go @@ -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] } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk.go new file mode 100644 index 0000000..ddeda94 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk.go @@ -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() +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster.go index de5cf5d..94bbb37 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster.go @@ -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 diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go index f025b0f..21c1223 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go @@ -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 + "§ion=basic", "isActive": section == "basic"}, + {"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 - 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() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings_store.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings_store.go new file mode 100644 index 0000000..f835484 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings_store.go @@ -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") +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go index 65dc880..78f375e 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go @@ -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() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/delete.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/delete.go index 57c246b..9081f33 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/delete.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/delete.go @@ -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() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/mock.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/mock.go index 092eb23..f8e91e7 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/mock.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/mock.go @@ -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 { diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/guide/index.go b/EdgeAdmin/internal/web/actions/default/httpdns/guide/index.go index cc2b57c..251ce5e 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/guide/index.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/guide/index.go @@ -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") } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils/helper.go b/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils/helper.go index 9d5ce32..dc0b27d 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils/helper.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils/helper.go @@ -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) +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/init.go b/EdgeAdmin/internal/web/actions/default/httpdns/init.go index aabd270..78dc22b 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/init.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/init.go @@ -14,6 +14,7 @@ func init() { Data("teaSubMenu", "cluster"). Prefix("/httpdns"). Get("", new(IndexAction)). + GetPost("/addPortPopup", new(AddPortPopupAction)). EndAll() }) } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/policies/index.go b/EdgeAdmin/internal/web/actions/default/httpdns/policies/index.go index 9ac3052..102e538 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/policies/index.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/policies/index.go @@ -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, }) diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/policies/store.go b/EdgeAdmin/internal/web/actions/default/httpdns/policies/store.go index 68cc1e0..b4f4080 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/policies/store.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/policies/store.go @@ -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() diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs/index.go b/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs/index.go index e7e8505..eac62f1 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs/index.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs/index.go @@ -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 diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs/index.go b/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs/index.go index cd7b9b6..9ccb264 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs/index.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs/index.go @@ -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 diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/sandbox/test.go b/EdgeAdmin/internal/web/actions/default/httpdns/sandbox/test.go index 5811a0f..e234028 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/sandbox/test.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/sandbox/test.go @@ -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 { diff --git a/EdgeAdmin/internal/web/helpers/menu.go b/EdgeAdmin/internal/web/helpers/menu.go index e7a3108..b50237b 100644 --- a/EdgeAdmin/internal/web/helpers/menu.go +++ b/EdgeAdmin/internal/web/helpers/menu.go @@ -113,50 +113,6 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i }, }, }, - { - "code": "httpdns", - "module": configloaders.AdminModuleCodeHttpDNS, - "name": "HTTPDNS", - "subtitle": "", - "icon": "shield alternate", - "subItems": []maps.Map{ - { - "name": "集群管理", - "url": "/httpdns/clusters", - "code": "cluster", - }, - { - "name": "全局配置", - "url": "/httpdns/policies", - "code": "policy", - }, - { - "name": "应用管理", - "url": "/httpdns/apps", - "code": "app", - }, - { - "name": "SDK接入引导", - "url": "/httpdns/guide", - "code": "guide", - }, - { - "name": "解析日志", - "url": "/httpdns/resolveLogs", - "code": "resolveLogs", - }, - { - "name": "运行日志", - "url": "/httpdns/runtimeLogs", - "code": "runtimeLogs", - }, - { - "name": "解析测试", - "url": "/httpdns/sandbox", - "code": "sandbox", - }, - }, - }, { "code": "dns", "module": configloaders.AdminModuleCodeDNS, @@ -181,6 +137,40 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i }, }, }, + { + "code": "httpdns", + "module": configloaders.AdminModuleCodeHttpDNS, + "name": "HTTPDNS", + "subtitle": "", + "icon": "shield alternate", + "subItems": []maps.Map{ + { + "name": "集群管理", + "url": "/httpdns/clusters", + "code": "cluster", + }, + { + "name": "应用管理", + "url": "/httpdns/apps", + "code": "app", + }, + { + "name": "访问日志", + "url": "/httpdns/resolveLogs", + "code": "resolveLogs", + }, + { + "name": "运行日志", + "url": "/httpdns/runtimeLogs", + "code": "runtimeLogs", + }, + { + "name": "解析测试", + "url": "/httpdns/sandbox", + "code": "sandbox", + }, + }, + }, { "code": "users", "module": configloaders.AdminModuleCodeUser, diff --git a/EdgeAdmin/internal/web/helpers/menu_plus.go b/EdgeAdmin/internal/web/helpers/menu_plus.go index c792b6d..a178d61 100644 --- a/EdgeAdmin/internal/web/helpers/menu_plus.go +++ b/EdgeAdmin/internal/web/helpers/menu_plus.go @@ -178,50 +178,6 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i }, }, }, - { - "code": "httpdns", - "module": configloaders.AdminModuleCodeHttpDNS, - "name": "HTTPDNS", - "subtitle": "", - "icon": "shield alternate", - "subItems": []maps.Map{ - { - "name": "集群管理", - "url": "/httpdns/clusters", - "code": "cluster", - }, - { - "name": "全局配置", - "url": "/httpdns/policies", - "code": "policy", - }, - { - "name": "应用管理", - "url": "/httpdns/apps", - "code": "app", - }, - { - "name": "SDK接入引导", - "url": "/httpdns/guide", - "code": "guide", - }, - { - "name": "解析日志", - "url": "/httpdns/resolveLogs", - "code": "resolveLogs", - }, - { - "name": "运行日志", - "url": "/httpdns/runtimeLogs", - "code": "runtimeLogs", - }, - { - "name": "解析测试", - "url": "/httpdns/sandbox", - "code": "sandbox", - }, - }, - }, { "code": "dns", "module": configloaders.AdminModuleCodeDNS, @@ -319,6 +275,40 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i }, }, }, + { + "code": "httpdns", + "module": configloaders.AdminModuleCodeHttpDNS, + "name": "HTTPDNS", + "subtitle": "", + "icon": "shield alternate", + "subItems": []maps.Map{ + { + "name": "集群管理", + "url": "/httpdns/clusters", + "code": "cluster", + }, + { + "name": "应用管理", + "url": "/httpdns/apps", + "code": "app", + }, + { + "name": "访问日志", + "url": "/httpdns/resolveLogs", + "code": "resolveLogs", + }, + { + "name": "运行日志", + "url": "/httpdns/runtimeLogs", + "code": "runtimeLogs", + }, + { + "name": "解析测试", + "url": "/httpdns/sandbox", + "code": "sandbox", + }, + }, + }, { "code": "users", "module": configloaders.AdminModuleCodeUser, @@ -477,3 +467,4 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i }, } } + diff --git a/EdgeAdmin/web/views/@default/httpdns/apps/appSettings.html b/EdgeAdmin/web/views/@default/httpdns/apps/appSettings.html index e8112e6..ae8e38d 100644 --- a/EdgeAdmin/web/views/@default/httpdns/apps/appSettings.html +++ b/EdgeAdmin/web/views/@default/httpdns/apps/appSettings.html @@ -1,116 +1,141 @@ {$layout} {$template "menu"} +{$template "/left_menu_with_menu"} -
当主集群不可用时,可切换到备集群。
| 线路 | +规则名称 | +解析记录 | +TTL | +状态 | +操作 | +
|---|---|---|---|---|---|
| {{record.lineText}} | +{{record.ruleName}} | +{{record.recordValueText}} | +{{record.ttl}}s | ++ 已启用 + 已停用 + | ++ 编辑 | + {{record.isOn ? "停用" : "启用"}} | + 删除 + | +
| 线路 | -规则名称 | -SDNS 参数 | -解析记录 | -TTL | -状态 | -操作 | -
|---|---|---|---|---|---|---|
| {{record.lineText}} | -{{record.ruleName}} | -{{record.paramsText}} | -- {{record.recordValueText}} - | -{{record.ttl}}s | -- 已启用 - 已停用 - | -- 编辑 | - {{record.isOn ? "停用" : "启用"}} | - 删除 - | -
| SDNS 参数配置 | -
-
-
-
- 添加参数
-
- {{sdnsParams.length}}/10
-
- |
-
| 解析记录值 * |
diff --git a/EdgeAdmin/web/views/@default/httpdns/apps/customRecordsCreatePopup.js b/EdgeAdmin/web/views/@default/httpdns/apps/customRecordsCreatePopup.js
index b588cb1..f6863fd 100644
--- a/EdgeAdmin/web/views/@default/httpdns/apps/customRecordsCreatePopup.js
+++ b/EdgeAdmin/web/views/@default/httpdns/apps/customRecordsCreatePopup.js
@@ -75,11 +75,6 @@ Tea.context(function () {
vm.record.weightEnabled = vm.normalizeBoolean(vm.record.weightEnabled, false);
vm.record.isOn = vm.normalizeBoolean(vm.record.isOn, true);
- vm.sdnsParams = vm.parseJSONList(vm.record.sdnsParamsJson);
- if (vm.sdnsParams.length == 0) {
- vm.sdnsParams.push({name: "", value: ""});
- }
-
vm.recordItems = vm.parseJSONList(vm.record.recordItemsJson);
if (vm.recordItems.length == 0) {
vm.recordItems.push({type: "A", value: "", weight: 100});
@@ -139,23 +134,6 @@ Tea.context(function () {
}
};
- vm.addSDNSParam = function () {
- if (vm.sdnsParams.length >= 10) {
- return;
- }
- vm.sdnsParams.push({name: "", value: ""});
- };
-
- vm.removeSDNSParam = function (index) {
- if (index < 0 || index >= vm.sdnsParams.length) {
- return;
- }
- vm.sdnsParams.splice(index, 1);
- if (vm.sdnsParams.length == 0) {
- vm.sdnsParams.push({name: "", value: ""});
- }
- };
-
vm.addRecordItem = function () {
if (vm.recordItems.length >= 10) {
return;
diff --git a/EdgeAdmin/web/views/@default/httpdns/apps/delete.html b/EdgeAdmin/web/views/@default/httpdns/apps/delete.html
new file mode 100644
index 0000000..b374ce3
--- /dev/null
+++ b/EdgeAdmin/web/views/@default/httpdns/apps/delete.html
@@ -0,0 +1,12 @@
+{$layout}
+
+ 删除应用
+
-
+ 删除集群
+ |
| 集群 | -节点 | -域名 | -类型 | -概要 | -
|---|---|---|---|---|
| {{log.clusterName}} | -{{log.nodeName}} | -{{log.domain}} | -{{log.query}} | -
-
- {{log.time}}
- | {{log.appName}}(
- {{log.appId}})
- | {{log.clientIp}}
- | {{log.os}}/{{log.sdkVersion}}
- | {{log.query}} {{log.domain}} ->
- {{log.ips}}
- [无记录]
- |
- 成功
- 失败
- ({{log.errorCode}})
- | {{log.costMs}}ms
- |
-
| 集群 | +节点 | +域名 | +类型 | +概要 | +
|---|---|---|---|---|
| {{log.clusterName}} | +{{log.nodeName}} | +{{log.domain}} | +{{log.query}} | +
+
+ {{log.time}}
+ | {{log.appName}} (
+ {{log.appId}})
+ | {{log.clientIp}}
+ | {{log.os}}/{{log.sdkVersion}}
+ | {{log.query}} {{log.domain}} ->
+ {{log.ips}}
+ [无记录]
+ |
+ 成功
+ 失败
+ ({{log.errorCode}})
+ | {{log.costMs}}ms
+ |
+
暂时还没有运行日志。
+ + +| 时间 | -集群 | -节点 | -级别 | -类型 | -模块 | -详情 | -次数 | -请求ID | -
|---|---|---|---|---|---|---|---|---|
| 时间 | +集群 | +节点 | +级别 | +类型 | +详情 | +次数 | +||
| {{log.createdTime}} | -{{log.clusterName}} | -{{log.nodeName}} | -- error - warning - info - success - | -{{log.tag}} |
- {{log.module}} | -{{log.description}} | -{{log.count}} | -{{log.requestId}} |
-
| {{log.createdTime}} | +{{log.clusterName}} | +{{log.nodeName}} | ++ {{log.level}} + | +{{log.tag}} |
+ {{log.description}} | +{{log.count}} | +
{{response.data.client_ip || request.clientIp || '-'}}{{response.data.client_ip || request.clientIp || '-'}}{{row.ip}}