feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -28,19 +28,20 @@ func (this *SdkCheckAction) RunGet(params struct {
return
}
version := strings.TrimSpace(params.Version)
t := strings.ToLower(strings.TrimSpace(params.Type))
if t == "doc" {
docPath := findUploadedSDKDocPath(platform, params.Version)
docPath := findUploadedSDKDocPath(platform, version)
if len(docPath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "Documentation is unavailable, please upload first"
this.Data["message"] = "当前平台/版本尚未上传集成文档"
this.Success()
return
}
downloadURL := "/httpdns/apps/sdk/doc?platform=" + url.QueryEscape(platform)
if len(strings.TrimSpace(params.Version)) > 0 {
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
if len(version) > 0 {
downloadURL += "&version=" + url.QueryEscape(version)
}
this.Data["exists"] = true
this.Data["url"] = downloadURL
@@ -48,17 +49,17 @@ func (this *SdkCheckAction) RunGet(params struct {
return
}
archivePath := findSDKArchivePath(filename, params.Version)
archivePath := findSDKArchivePath(filename, version)
if len(archivePath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "SDK package is unavailable, please upload first"
this.Data["message"] = "当前平台/版本尚未上传 SDK 安装包"
this.Success()
return
}
downloadURL := "/httpdns/apps/sdk/download?platform=" + url.QueryEscape(platform)
if len(strings.TrimSpace(params.Version)) > 0 {
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
if len(version) > 0 {
downloadURL += "&version=" + url.QueryEscape(version)
}
downloadURL += "&raw=1"
this.Data["exists"] = true

View File

@@ -3,7 +3,6 @@ package apps
import (
"os"
"path/filepath"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
@@ -20,7 +19,7 @@ func (this *SdkDocAction) RunGet(params struct {
Platform string
Version string
}) {
platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform)
platform, _, _, _, err := resolveSDKPlatform(params.Platform)
if err != nil {
this.Data["exists"] = false
this.Data["message"] = err.Error()
@@ -28,35 +27,25 @@ func (this *SdkDocAction) RunGet(params struct {
return
}
var data []byte
uploadedDocPath := findUploadedSDKDocPath(platform, params.Version)
if len(uploadedDocPath) > 0 {
data, err = os.ReadFile(uploadedDocPath)
}
sdkRoot, sdkRootErr := findSDKRoot()
if len(data) == 0 && sdkRootErr == nil {
readmePath := filepath.Join(sdkRoot, readmeRelativePath)
data, err = os.ReadFile(readmePath)
}
if len(data) == 0 {
localDocPath := findLocalSDKDocPath(platform)
if len(localDocPath) > 0 {
data, err = os.ReadFile(localDocPath)
}
}
if len(data) == 0 || err != nil {
docPath := findUploadedSDKDocPath(platform, params.Version)
if len(docPath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "SDK documentation is not found on server, please upload first"
this.Data["message"] = "当前平台/版本尚未上传集成文档"
this.Success()
return
}
downloadName := filepath.Base(uploadedDocPath)
data, err := os.ReadFile(docPath)
if err != nil || len(data) == 0 {
this.Data["exists"] = false
this.Data["message"] = "读取集成文档失败"
this.Success()
return
}
downloadName := filepath.Base(docPath)
if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) {
downloadName = "httpdns-sdk-" + strings.ToLower(platform) + ".md"
downloadName = "sdk-doc.md"
}
this.AddHeader("Content-Type", "text/markdown; charset=utf-8")

View File

@@ -32,7 +32,7 @@ func (this *SdkDownloadAction) RunGet(params struct {
archivePath := findSDKArchivePath(filename, params.Version)
if len(archivePath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "SDK archive not found on server, please upload first: " + filename
this.Data["message"] = "当前平台/版本尚未上传 SDK 安装包"
this.Success()
return
}
@@ -40,7 +40,7 @@ func (this *SdkDownloadAction) RunGet(params struct {
fp, err := os.Open(archivePath)
if err != nil {
this.Data["exists"] = false
this.Data["message"] = "failed to open SDK archive: " + err.Error()
this.Data["message"] = "打开 SDK 安装包失败: " + err.Error()
this.Success()
return
}

View File

@@ -1,16 +1,183 @@
package apps
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/iwind/TeaGo/Tea"
)
type sdkUploadMeta struct {
Platform string `json:"platform"`
Version string `json:"version"`
FileType string `json:"fileType"` // sdk | doc
Filename string `json:"filename"`
UpdatedAt int64 `json:"updatedAt"`
}
type sdkUploadMetaRecord struct {
Meta sdkUploadMeta
Dir string
FilePath string
}
func sdkUploadMetaFilename(platform string, version string, fileType string) string {
platform = strings.ToLower(strings.TrimSpace(platform))
version = strings.TrimSpace(version)
fileType = strings.ToLower(strings.TrimSpace(fileType))
return ".httpdns-sdk-meta-" + platform + "-v" + version + "-" + fileType + ".json"
}
func isSDKUploadMetaFile(name string) bool {
name = strings.ToLower(strings.TrimSpace(name))
return strings.HasPrefix(name, ".httpdns-sdk-meta-") && strings.HasSuffix(name, ".json")
}
func parseSDKPlatformFromDownloadFilename(downloadFilename string) string {
name := strings.ToLower(strings.TrimSpace(downloadFilename))
if !strings.HasPrefix(name, "httpdns-sdk-") || !strings.HasSuffix(name, ".zip") {
return ""
}
platform := strings.TrimSuffix(strings.TrimPrefix(name, "httpdns-sdk-"), ".zip")
switch platform {
case "android", "ios", "flutter":
return platform
default:
return ""
}
}
func listSDKUploadMetaRecords() []sdkUploadMetaRecord {
type wrapped struct {
record sdkUploadMetaRecord
modTime time.Time
}
byKey := map[string]wrapped{}
for _, dir := range sdkUploadSearchDirs() {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !isSDKUploadMetaFile(name) {
continue
}
metaPath := filepath.Join(dir, name)
data, err := os.ReadFile(metaPath)
if err != nil || len(data) == 0 {
continue
}
var meta sdkUploadMeta
if err = json.Unmarshal(data, &meta); err != nil {
continue
}
meta.Platform = strings.ToLower(strings.TrimSpace(meta.Platform))
meta.Version = strings.TrimSpace(meta.Version)
meta.FileType = strings.ToLower(strings.TrimSpace(meta.FileType))
meta.Filename = filepath.Base(strings.TrimSpace(meta.Filename))
if len(meta.Platform) == 0 || len(meta.Version) == 0 || len(meta.Filename) == 0 {
continue
}
if meta.FileType != "sdk" && meta.FileType != "doc" {
continue
}
if strings.Contains(meta.Filename, "..") || strings.Contains(meta.Filename, "/") || strings.Contains(meta.Filename, "\\") {
continue
}
filePath := filepath.Join(dir, meta.Filename)
fileStat, err := os.Stat(filePath)
if err != nil || fileStat.IsDir() || fileStat.Size() <= 0 {
continue
}
metaStat, err := os.Stat(metaPath)
if err != nil {
continue
}
if meta.UpdatedAt <= 0 {
meta.UpdatedAt = metaStat.ModTime().Unix()
}
key := meta.Platform + "|" + meta.Version + "|" + meta.FileType
current := wrapped{
record: sdkUploadMetaRecord{
Meta: meta,
Dir: dir,
FilePath: filePath,
},
modTime: metaStat.ModTime(),
}
old, ok := byKey[key]
if !ok ||
current.record.Meta.UpdatedAt > old.record.Meta.UpdatedAt ||
(current.record.Meta.UpdatedAt == old.record.Meta.UpdatedAt && current.modTime.After(old.modTime)) ||
(current.record.Meta.UpdatedAt == old.record.Meta.UpdatedAt && current.modTime.Equal(old.modTime) && current.record.FilePath > old.record.FilePath) {
byKey[key] = current
}
}
}
result := make([]sdkUploadMetaRecord, 0, len(byKey))
for _, item := range byKey {
result = append(result, item.record)
}
return result
}
func findSDKUploadFileByMeta(platform string, version string, fileType string) string {
platform = strings.ToLower(strings.TrimSpace(platform))
version = strings.TrimSpace(version)
fileType = strings.ToLower(strings.TrimSpace(fileType))
if len(platform) == 0 || len(version) == 0 {
return ""
}
for _, record := range listSDKUploadMetaRecords() {
if record.Meta.Platform == platform && record.Meta.Version == version && record.Meta.FileType == fileType {
return record.FilePath
}
}
return ""
}
func findNewestSDKUploadFileByMeta(platform string, fileType string) string {
platform = strings.ToLower(strings.TrimSpace(platform))
fileType = strings.ToLower(strings.TrimSpace(fileType))
if len(platform) == 0 {
return ""
}
var foundPath string
var foundUpdatedAt int64
for _, record := range listSDKUploadMetaRecords() {
if record.Meta.Platform != platform || record.Meta.FileType != fileType {
continue
}
if len(foundPath) == 0 || record.Meta.UpdatedAt > foundUpdatedAt || (record.Meta.UpdatedAt == foundUpdatedAt && record.FilePath > foundPath) {
foundPath = record.FilePath
foundUpdatedAt = record.Meta.UpdatedAt
}
}
return foundPath
}
func sdkUploadDir() string {
dirs := sdkUploadDirs()
if len(dirs) > 0 {
@@ -27,6 +194,7 @@ func sdkUploadDirs() []string {
filepath.Clean(Tea.Root + "/../edge-user/data/httpdns/sdk"),
filepath.Clean(Tea.Root + "/../../data/httpdns/sdk"),
}
results := make([]string, 0, len(candidates))
seen := map[string]bool{}
for _, dir := range candidates {
@@ -54,67 +222,6 @@ func sdkUploadSearchDirs() []string {
return results
}
func findFirstExistingDir(paths []string) string {
for _, path := range paths {
stat, err := os.Stat(path)
if err == nil && stat.IsDir() {
return path
}
}
return ""
}
func findFirstExistingFile(paths []string) string {
for _, path := range paths {
stat, err := os.Stat(path)
if err == nil && !stat.IsDir() && stat.Size() > 0 {
return path
}
}
return ""
}
func findNewestExistingFile(paths []string) string {
type fileInfo struct {
path string
modTime time.Time
}
result := fileInfo{}
for _, path := range paths {
stat, err := os.Stat(path)
if err != nil || stat.IsDir() {
continue
}
if stat.Size() <= 0 {
continue
}
if len(result.path) == 0 || stat.ModTime().After(result.modTime) || (stat.ModTime().Equal(result.modTime) && path > result.path) {
result.path = path
result.modTime = stat.ModTime()
}
}
return result.path
}
func findSDKRoot() (string, error) {
candidates := []string{
filepath.Clean(Tea.Root + "/EdgeHttpDNS/sdk"),
filepath.Clean(Tea.Root + "/edge-httpdns/sdk"),
filepath.Clean(Tea.Root + "/edge-httpdns/edge-httpdns/sdk"),
filepath.Clean(Tea.Root + "/../EdgeHttpDNS/sdk"),
filepath.Clean(Tea.Root + "/../../EdgeHttpDNS/sdk"),
filepath.Clean(Tea.Root + "/../edge-httpdns/sdk"),
filepath.Clean(Tea.Root + "/../../edge-httpdns/sdk"),
}
dir := findFirstExistingDir(candidates)
if len(dir) > 0 {
return dir, nil
}
return "", errors.New("SDK files are not found on current server")
}
func resolveSDKPlatform(platform string) (key string, relativeDir string, readmeRelativePath string, downloadFilename string, err error) {
switch strings.ToLower(strings.TrimSpace(platform)) {
case "android":
@@ -122,52 +229,23 @@ func resolveSDKPlatform(platform string) (key string, relativeDir string, readme
case "ios":
return "ios", "ios", "ios/README.md", "httpdns-sdk-ios.zip", nil
case "flutter":
return "flutter", "flutter/aliyun_httpdns", "flutter/aliyun_httpdns/README.md", "httpdns-sdk-flutter.zip", nil
return "flutter", "flutter/new_httpdns", "flutter/new_httpdns/README.md", "httpdns-sdk-flutter.zip", nil
default:
return "", "", "", "", errors.New("invalid platform, expected one of: android, ios, flutter")
return "", "", "", "", errors.New("不支持的平台,可选值:androidiosflutter")
}
}
func findSDKArchivePath(downloadFilename string, version string) string {
searchDirs := sdkUploadSearchDirs()
normalizedVersion := strings.TrimSpace(version)
base := strings.TrimSuffix(downloadFilename, ".zip")
if len(normalizedVersion) > 0 {
versionFiles := []string{}
for _, dir := range searchDirs {
versionFiles = append(versionFiles, filepath.Join(dir, base+"-v"+normalizedVersion+".zip"))
}
if path := findFirstExistingFile(versionFiles); len(path) > 0 {
return path
}
platform := parseSDKPlatformFromDownloadFilename(downloadFilename)
if len(platform) == 0 {
return ""
}
patternName := base + "-v*.zip"
matches := []string{}
for _, dir := range searchDirs {
found, _ := filepath.Glob(filepath.Join(dir, patternName))
for _, file := range found {
stat, err := os.Stat(file)
if err == nil && !stat.IsDir() {
matches = append(matches, file)
}
}
normalizedVersion := strings.TrimSpace(version)
if len(normalizedVersion) > 0 {
return findSDKUploadFileByMeta(platform, normalizedVersion, "sdk")
}
if len(matches) > 0 {
return findNewestExistingFile(matches)
}
exactFiles := []string{}
for _, dir := range searchDirs {
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
}
if path := findFirstExistingFile(exactFiles); len(path) > 0 {
return path
}
return ""
return findNewestSDKUploadFileByMeta(platform, "sdk")
}
func findUploadedSDKDocPath(platform string, version string) string {
@@ -176,42 +254,9 @@ func findUploadedSDKDocPath(platform string, version string) string {
return ""
}
searchDirs := sdkUploadSearchDirs()
normalizedVersion := strings.TrimSpace(version)
if len(normalizedVersion) > 0 {
exactVersion := []string{}
for _, dir := range searchDirs {
exactVersion = append(exactVersion, filepath.Join(dir, "httpdns-sdk-"+platform+"-v"+normalizedVersion+".md"))
}
if file := findFirstExistingFile(exactVersion); len(file) > 0 {
return file
}
return ""
return findSDKUploadFileByMeta(platform, normalizedVersion, "doc")
}
matches := []string{}
for _, dir := range searchDirs {
pattern := filepath.Join(dir, "httpdns-sdk-"+platform+"-v*.md")
found, _ := filepath.Glob(pattern)
matches = append(matches, found...)
}
if len(matches) > 0 {
sort.Strings(matches)
return findNewestExistingFile(matches)
}
exact := []string{}
for _, dir := range searchDirs {
exact = append(exact, filepath.Join(dir, "httpdns-sdk-"+platform+".md"))
}
return findFirstExistingFile(exact)
}
func findLocalSDKDocPath(platform string) string {
filename := strings.ToLower(strings.TrimSpace(platform)) + ".md"
candidates := []string{
filepath.Clean(Tea.Root + "/edge-admin/web/views/@default/httpdns/apps/docs/" + filename),
filepath.Clean(Tea.Root + "/EdgeAdmin/web/views/@default/httpdns/apps/docs/" + filename),
}
return findFirstExistingFile(candidates)
return findNewestSDKUploadFileByMeta(platform, "doc")
}

View File

@@ -1,6 +1,7 @@
package apps
import (
"encoding/json"
"errors"
"os"
"path/filepath"
@@ -14,6 +15,8 @@ import (
"github.com/iwind/TeaGo/actions"
)
const sdkUploadMaxFileSize = 20 * 1024 * 1024 // 20MB
type SdkUploadAction struct {
actionutils.ParentAction
}
@@ -52,7 +55,7 @@ func (this *SdkUploadAction) RunPost(params struct {
}) {
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
platform, _, _, downloadFilename, err := resolveSDKPlatform(params.Platform)
platform, _, _, _, err := resolveSDKPlatform(params.Platform)
if err != nil {
this.Fail(err.Error())
return
@@ -70,57 +73,21 @@ func (this *SdkUploadAction) RunPost(params struct {
}
uploadDir := sdkUploadDir()
err = os.MkdirAll(uploadDir, 0755)
if err != nil {
if err = os.MkdirAll(uploadDir, 0755); err != nil {
this.Fail("创建上传目录失败: " + err.Error())
return
}
if params.SdkFile != nil {
filename := strings.ToLower(strings.TrimSpace(params.SdkFile.Filename))
if !strings.HasSuffix(filename, ".zip") {
this.Fail("SDK 包仅支持 .zip 文件")
return
}
sdkData, readErr := params.SdkFile.Read()
if readErr != nil {
this.Fail("读取 SDK 包失败: " + readErr.Error())
return
}
if len(sdkData) == 0 {
this.Fail("SDK 包文件为空,请重新上传")
return
}
baseName := strings.TrimSuffix(downloadFilename, ".zip")
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
if err != nil {
this.Fail("保存 SDK 包失败: " + err.Error())
if err = this.saveUploadedItem(uploadDir, platform, version, "sdk", params.SdkFile); err != nil {
this.Fail(err.Error())
return
}
}
if params.DocFile != nil {
docName := strings.ToLower(strings.TrimSpace(params.DocFile.Filename))
if !strings.HasSuffix(docName, ".md") {
this.Fail("集成文档仅支持 .md 文件")
return
}
docData, readErr := params.DocFile.Read()
if readErr != nil {
this.Fail("读取集成文档失败: " + readErr.Error())
return
}
if len(docData) == 0 {
this.Fail("集成文档文件为空,请重新上传")
return
}
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
if err != nil {
this.Fail("保存集成文档失败: " + err.Error())
if err = this.saveUploadedItem(uploadDir, platform, version, "doc", params.DocFile); err != nil {
this.Fail(err.Error())
return
}
}
@@ -128,6 +95,52 @@ func (this *SdkUploadAction) RunPost(params struct {
this.Success()
}
func (this *SdkUploadAction) saveUploadedItem(uploadDir string, platform string, version string, fileType string, file *actions.File) error {
expectedExt := ".md"
displayType := "集成文档"
if fileType == "sdk" {
expectedExt = ".zip"
displayType = "SDK 包"
}
filename, err := normalizeUploadedFilename(file.Filename, expectedExt)
if err != nil {
return err
}
if file.Size > sdkUploadMaxFileSize {
return errors.New(displayType + "文件不能超过 20MB")
}
data, err := file.Read()
if err != nil {
return errors.New("读取" + displayType + "失败: " + err.Error())
}
if len(data) == 0 {
return errors.New(displayType + "文件为空,请重新上传")
}
if len(data) > sdkUploadMaxFileSize {
return errors.New(displayType + "文件不能超过 20MB")
}
if err = saveSDKUploadFile(uploadDir, filename, data); err != nil {
return errors.New("保存" + displayType + "失败: " + err.Error())
}
err = saveSDKUploadMetaRecord(uploadDir, sdkUploadMeta{
Platform: platform,
Version: version,
FileType: fileType,
Filename: filename,
UpdatedAt: time.Now().Unix(),
})
if err != nil {
return errors.New("保存上传元信息失败: " + err.Error())
}
return nil
}
func normalizeSDKVersion(version string) (string, error) {
version = strings.TrimSpace(version)
if len(version) == 0 {
@@ -142,6 +155,26 @@ func normalizeSDKVersion(version string) (string, error) {
return version, nil
}
func normalizeUploadedFilename(raw string, expectedExt string) (string, error) {
filename := filepath.Base(strings.TrimSpace(raw))
if len(filename) == 0 || filename == "." || filename == string(filepath.Separator) {
return "", errors.New("文件名不能为空")
}
if strings.Contains(filename, "/") || strings.Contains(filename, "\\") || strings.Contains(filename, "..") {
return "", errors.New("文件名不合法")
}
actualExt := strings.ToLower(filepath.Ext(filename))
if actualExt != strings.ToLower(expectedExt) {
if expectedExt == ".zip" {
return "", errors.New("SDK 包仅支持 .zip 文件")
}
return "", errors.New("集成文档仅支持 .md 文件")
}
return filename, nil
}
func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
targetPath := filepath.Join(baseDir, filename)
tmpPath := targetPath + ".tmp"
@@ -152,6 +185,35 @@ func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
return os.Rename(tmpPath, targetPath)
}
func saveSDKUploadMetaRecord(baseDir string, meta sdkUploadMeta) error {
meta.Platform = strings.ToLower(strings.TrimSpace(meta.Platform))
meta.Version = strings.TrimSpace(meta.Version)
meta.FileType = strings.ToLower(strings.TrimSpace(meta.FileType))
meta.Filename = filepath.Base(strings.TrimSpace(meta.Filename))
if len(meta.Platform) == 0 || len(meta.Version) == 0 || len(meta.FileType) == 0 || len(meta.Filename) == 0 {
return errors.New("上传元信息不完整")
}
metaFilename := sdkUploadMetaFilename(meta.Platform, meta.Version, meta.FileType)
metaPath := filepath.Join(baseDir, metaFilename)
if data, err := os.ReadFile(metaPath); err == nil && len(data) > 0 {
var oldMeta sdkUploadMeta
if json.Unmarshal(data, &oldMeta) == nil {
oldFile := filepath.Base(strings.TrimSpace(oldMeta.Filename))
if len(oldFile) > 0 && oldFile != meta.Filename {
_ = os.Remove(filepath.Join(baseDir, oldFile))
}
}
}
data, err := json.Marshal(meta)
if err != nil {
return err
}
return saveSDKUploadFile(baseDir, metaFilename, data)
}
func listUploadedSDKFiles() []map[string]interface{} {
type item struct {
Name string
@@ -162,45 +224,26 @@ func listUploadedSDKFiles() []map[string]interface{} {
UpdatedAt int64
}
byName := map[string]item{}
for _, dir := range sdkUploadSearchDirs() {
entries, err := os.ReadDir(dir)
if err != nil {
items := make([]item, 0)
for _, record := range listSDKUploadMetaRecords() {
stat, err := os.Stat(record.FilePath)
if err != nil || stat.IsDir() {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
platform, version, fileType, ok := parseSDKUploadFilename(name)
if !ok {
continue
}
info, statErr := entry.Info()
if statErr != nil {
continue
}
current := item{
Name: name,
Platform: platform,
FileType: fileType,
Version: version,
SizeBytes: info.Size(),
UpdatedAt: info.ModTime().Unix(),
}
old, exists := byName[name]
if !exists || current.UpdatedAt >= old.UpdatedAt {
byName[name] = current
}
fileType := "SDK 包"
if record.Meta.FileType == "doc" {
fileType = "集成文档"
}
}
items := make([]item, 0, len(byName))
for _, it := range byName {
items = append(items, it)
items = append(items, item{
Name: filepath.Base(record.FilePath),
Platform: record.Meta.Platform,
FileType: fileType,
Version: record.Meta.Version,
SizeBytes: stat.Size(),
UpdatedAt: stat.ModTime().Unix(),
})
}
sort.Slice(items, func(i, j int) bool {
@@ -224,55 +267,21 @@ func listUploadedSDKFiles() []map[string]interface{} {
return result
}
func parseSDKUploadFilename(filename string) (platform string, version string, fileType string, ok bool) {
if !strings.HasPrefix(filename, "httpdns-sdk-") {
return "", "", "", false
}
ext := ""
switch {
case strings.HasSuffix(filename, ".zip"):
ext = ".zip"
fileType = "SDK包"
case strings.HasSuffix(filename, ".md"):
ext = ".md"
fileType = "集成文档"
default:
return "", "", "", false
}
main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext)
version = ""
if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) {
version = main[idx+2:]
main = main[:idx]
}
main = strings.ToLower(strings.TrimSpace(main))
switch main {
case "android", "ios", "flutter":
platform = main
if len(version) == 0 {
version = "-"
}
return platform, version, fileType, true
default:
return "", "", "", false
}
}
func formatSDKFileSize(size int64) string {
if size < 1024 {
return strconv.FormatInt(size, 10) + " B"
}
sizeKB := float64(size) / 1024
if sizeKB < 1024 {
return strconv.FormatFloat(sizeKB, 'f', 1, 64) + " KB"
}
sizeMB := sizeKB / 1024
if sizeMB < 1024 {
return strconv.FormatFloat(sizeMB, 'f', 1, 64) + " MB"
}
sizeGB := sizeMB / 1024
return strconv.FormatFloat(sizeGB, 'f', 1, 64) + " GB"
}

View File

@@ -1,6 +1,7 @@
package apps
import (
"encoding/json"
"os"
"path/filepath"
"strings"
@@ -34,19 +35,16 @@ func (this *SdkUploadDeleteAction) RunPost(params struct {
this.Fail("文件名不合法")
return
}
if !strings.HasPrefix(filename, "httpdns-sdk-") {
this.Fail("不允许删除该文件")
return
}
if !(strings.HasSuffix(filename, ".zip") || strings.HasSuffix(filename, ".md")) {
this.Fail("不允许删除该文件")
lowName := strings.ToLower(filename)
if !strings.HasSuffix(lowName, ".zip") && !strings.HasSuffix(lowName, ".md") {
this.Fail("仅允许删除 .zip 或 .md 文件")
return
}
for _, dir := range sdkUploadDirs() {
fullPath := filepath.Join(dir, filename)
_, err := os.Stat(fullPath)
if err != nil {
stat, err := os.Stat(fullPath)
if err != nil || stat.IsDir() {
continue
}
if err = os.Remove(fullPath); err != nil {
@@ -55,5 +53,38 @@ func (this *SdkUploadDeleteAction) RunPost(params struct {
}
}
// 删除引用该文件的元数据
for _, dir := range sdkUploadDirs() {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
if !isSDKUploadMetaFile(entry.Name()) {
continue
}
metaPath := filepath.Join(dir, entry.Name())
data, err := os.ReadFile(metaPath)
if err != nil || len(data) == 0 {
continue
}
var meta sdkUploadMeta
if err = json.Unmarshal(data, &meta); err != nil {
continue
}
if filepath.Base(strings.TrimSpace(meta.Filename)) != filename {
continue
}
_ = os.Remove(metaPath)
}
}
this.Success()
}

View File

@@ -1,9 +1,11 @@
package node
import (
"strings"
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
)
@@ -50,7 +52,22 @@ func (this *IndexAction) RunGet(params struct {
this.Data["nodeDatetime"] = nodeDatetime
this.Data["nodeTimeDiff"] = nodeTimeDiff
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
osName := strings.TrimSpace(status.GetString("os"))
if len(osName) > 0 {
checkVersionResp, err := this.RPC().HTTPDNSNodeRPC().CheckHTTPDNSNodeLatestVersion(this.AdminContext(), &pb.CheckHTTPDNSNodeLatestVersionRequest{
Os: osName,
Arch: strings.TrimSpace(status.GetString("arch")),
CurrentVersion: strings.TrimSpace(status.GetString("buildVersion")),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["shouldUpgrade"] = checkVersionResp.GetHasNewVersion()
this.Data["newVersion"] = checkVersionResp.GetNewVersion()
} else {
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
}
this.Show()
}

View File

@@ -22,14 +22,14 @@ func findHTTPDNSClusterMap(parent *actionutils.ParentAction, clusterID int64) (m
return maps.Map{
"id": clusterID,
"name": "",
"installDir": "/opt/edge-httpdns",
"installDir": "/root/edge-httpdns",
}, nil
}
cluster := resp.GetCluster()
installDir := strings.TrimSpace(cluster.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
installDir = "/root/edge-httpdns"
}
return maps.Map{
"id": cluster.GetId(),
@@ -93,7 +93,7 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma
installDir := strings.TrimSpace(node.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
installDir = "/root/edge-httpdns"
}
clusterMap, err := findHTTPDNSClusterMap(parent, node.GetClusterId())
@@ -137,22 +137,22 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma
}
return maps.Map{
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"installDir": installDir,
"status": statusMap,
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"installDir": installDir,
"status": statusMap,
"installStatus": installStatusMap,
"cluster": clusterMap,
"login": loginMap,
"apiNodeAddrs": []string{},
"ipAddresses": ipAddresses,
"cluster": clusterMap,
"login": loginMap,
"apiNodeAddrs": []string{},
"ipAddresses": ipAddresses,
}, nil
}
@@ -165,21 +165,23 @@ func decodeNodeStatus(raw []byte) maps.Map {
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
return maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"os": status.OS,
"arch": status.Arch,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
}

View File

@@ -195,7 +195,7 @@ func (this *UpdateAction) RunPost(params struct {
installDir := strings.TrimSpace(node.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
installDir = "/root/edge-httpdns"
}
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(this.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{

View File

@@ -1,17 +1,20 @@
package clusters
package clusters
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"google.golang.org/grpc/metadata"
)
type ClusterSettingsAction struct {
@@ -41,14 +44,15 @@ func (this *ClusterSettingsAction) RunGet(params struct {
}
settings := maps.Map{
"name": cluster.GetString("name"),
"gatewayDomain": cluster.GetString("gatewayDomain"),
"cacheTtl": cluster.GetInt("defaultTTL"),
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
"installDir": cluster.GetString("installDir"),
"isOn": cluster.GetBool("isOn"),
"autoRemoteStart": cluster.GetBool("autoRemoteStart"),
"accessLogIsOn": cluster.GetBool("accessLogIsOn"),
"name": cluster.GetString("name"),
"gatewayDomain": cluster.GetString("gatewayDomain"),
"cacheTtl": cluster.GetInt("defaultTTL"),
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
"installDir": cluster.GetString("installDir"),
"isOn": cluster.GetBool("isOn"),
"autoRemoteStart": cluster.GetBool("autoRemoteStart"),
"accessLogIsOn": cluster.GetBool("accessLogIsOn"),
"timeZone": cluster.GetString("timeZone"),
}
if settings.GetInt("cacheTtl") <= 0 {
settings["cacheTtl"] = 60
@@ -57,7 +61,10 @@ func (this *ClusterSettingsAction) RunGet(params struct {
settings["fallbackTimeout"] = 300
}
if len(settings.GetString("installDir")) == 0 {
settings["installDir"] = "/opt/edge-httpdns"
settings["installDir"] = "/root/edge-httpdns"
}
if len(settings.GetString("timeZone")) == 0 {
settings["timeZone"] = "Asia/Shanghai"
}
listenAddresses := []*serverconfigs.NetworkAddressConfig{
@@ -101,19 +108,29 @@ func (this *ClusterSettingsAction) RunGet(params struct {
"listen": listenAddresses,
"sslPolicy": sslPolicy,
}
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
timeZoneStr := settings.GetString("timeZone")
if len(timeZoneStr) == 0 {
timeZoneStr = nodeconfigs.DefaultTimeZoneLocation
}
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(timeZoneStr)
this.Show()
}
func (this *ClusterSettingsAction) RunPost(params struct {
ClusterId int64
Name 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
AutoRemoteStart bool
AccessLogIsOn bool
AccessLogIsOn bool
TimeZone string
Addresses []byte
SslPolicyJSON []byte
@@ -129,6 +146,31 @@ func (this *ClusterSettingsAction) RunPost(params struct {
params.Must.Field("name", params.Name).Require("请输入集群名称")
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
// 开关项按请求值强制覆盖:未提交/空值都视为 false支持取消勾选
autoRemoteStartRaw := strings.ToLower(strings.TrimSpace(this.ParamString("autoRemoteStart")))
params.AutoRemoteStart = autoRemoteStartRaw == "1" || autoRemoteStartRaw == "true" || autoRemoteStartRaw == "on" || autoRemoteStartRaw == "yes" || autoRemoteStartRaw == "enabled"
accessLogIsOnRaw := strings.ToLower(strings.TrimSpace(this.ParamString("accessLogIsOn")))
params.AccessLogIsOn = accessLogIsOnRaw == "1" || accessLogIsOnRaw == "true" || accessLogIsOnRaw == "on" || accessLogIsOnRaw == "yes" || accessLogIsOnRaw == "enabled"
isOnRaw := strings.ToLower(strings.TrimSpace(this.ParamString("isOn")))
params.IsOn = isOnRaw == "1" || isOnRaw == "true" || isOnRaw == "on" || isOnRaw == "yes" || isOnRaw == "enabled"
// 时区为空时继承当前值,再兜底默认值
params.TimeZone = strings.TrimSpace(this.ParamString("timeZone"))
if len(params.TimeZone) == 0 {
params.TimeZone = strings.TrimSpace(cluster.GetString("timeZone"))
}
if len(params.TimeZone) == 0 {
params.TimeZone = "Asia/Shanghai"
}
if params.CacheTtl <= 0 {
params.CacheTtl = 60
}
@@ -136,20 +178,13 @@ func (this *ClusterSettingsAction) RunPost(params struct {
params.FallbackTimeout = 300
}
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
}
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
params.InstallDir = "/root/edge-httpdns"
}
tlsConfig := maps.Map{}
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
_ = json.Unmarshal([]byte(rawTLS), &tlsConfig)
}
if len(params.Addresses) > 0 {
var addresses []*serverconfigs.NetworkAddressConfig
if err := json.Unmarshal(params.Addresses, &addresses); err != nil {
@@ -158,7 +193,6 @@ func (this *ClusterSettingsAction) RunPost(params struct {
}
tlsConfig["listen"] = addresses
}
if len(params.SslPolicyJSON) > 0 {
sslPolicy := &sslconfigs.SSLPolicy{}
if err := json.Unmarshal(params.SslPolicyJSON, sslPolicy); err != nil {
@@ -177,7 +211,7 @@ func (this *ClusterSettingsAction) RunPost(params struct {
}
}
_, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(this.AdminContext(), &pb.UpdateHTTPDNSClusterRequest{
updateReq := &pb.UpdateHTTPDNSClusterRequest{
ClusterId: params.ClusterId,
Name: params.Name,
ServiceDomain: params.GatewayDomain,
@@ -189,7 +223,16 @@ func (this *ClusterSettingsAction) RunPost(params struct {
IsDefault: false,
AutoRemoteStart: params.AutoRemoteStart,
AccessLogIsOn: params.AccessLogIsOn,
})
TimeZone: params.TimeZone,
}
updateCtx := metadata.AppendToOutgoingContext(
this.AdminContext(),
"x-httpdns-auto-remote-start", fmt.Sprintf("%t", updateReq.GetAutoRemoteStart()),
"x-httpdns-access-log-is-on", fmt.Sprintf("%t", updateReq.GetAccessLogIsOn()),
"x-httpdns-time-zone", updateReq.GetTimeZone(),
)
_, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(updateCtx, updateReq)
if err != nil {
this.ErrorPage(err)
return

View File

@@ -1,6 +1,7 @@
package clusters
import (
"strconv"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
@@ -36,7 +37,7 @@ func (this *CreateAction) RunPost(params struct {
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
params.InstallDir = strings.TrimSpace(params.InstallDir)
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
params.InstallDir = "/root/edge-httpdns"
}
if params.CacheTtl <= 0 {
params.CacheTtl = 60
@@ -56,6 +57,9 @@ func (this *CreateAction) RunPost(params struct {
InstallDir: params.InstallDir,
IsOn: params.IsOn,
IsDefault: false,
AutoRemoteStart: true,
AccessLogIsOn: true,
TimeZone: "Asia/Shanghai",
})
if err != nil {
this.ErrorPage(err)
@@ -63,5 +67,12 @@ func (this *CreateAction) RunPost(params struct {
}
this.Data["clusterId"] = resp.GetClusterId()
// fallback: if frontend JS doesn't intercept form submit, redirect instead of showing raw JSON
if len(this.Request.Header.Get("X-Requested-With")) == 0 {
this.RedirectURL("/httpdns/clusters/cluster?clusterId=" + strconv.FormatInt(resp.GetClusterId(), 10))
return
}
this.Success()
}

View File

@@ -51,7 +51,7 @@ func (this *CreateNodeAction) RunPost(params struct {
params.InstallDir = strings.TrimSpace(cluster.GetString("installDir"))
}
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
params.InstallDir = "/root/edge-httpdns"
}
}

View File

@@ -22,7 +22,9 @@ func init() {
// Node level
GetPost("/createNode", new(CreateNodeAction)).
Post("/deleteNode", new(DeleteNodeAction)).
Get("/upgradeRemote", new(UpgradeRemoteAction)).
GetPost("/cluster/upgradeRemote", new(UpgradeRemoteAction)).
GetPost("/upgradeRemote", new(UpgradeRemoteAction)).
Post("/upgradeStatus", new(UpgradeStatusAction)).
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
Post("/checkPorts", new(CheckPortsAction)).

View File

@@ -9,6 +9,10 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
@@ -37,6 +41,11 @@ func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.M
}
}
countUpgradeNodes, err := countUpgradeHTTPDNSNodes(parent, cluster.GetId())
if err != nil {
return nil, err
}
port := "443"
if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 {
tlsConfig := maps.Map{}
@@ -56,32 +65,87 @@ func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.M
apiAddress := "https://" + cluster.GetServiceDomain() + ":" + port
result = append(result, maps.Map{
"id": cluster.GetId(),
"name": cluster.GetName(),
"gatewayDomain": cluster.GetServiceDomain(),
"apiAddress": apiAddress,
"defaultTTL": cluster.GetDefaultTTL(),
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
"installDir": cluster.GetInstallDir(),
"isOn": cluster.GetIsOn(),
"isDefault": cluster.GetIsDefault(),
"countAllNodes": countAllNodes,
"countActiveNodes": countActiveNodes,
"id": cluster.GetId(),
"name": cluster.GetName(),
"gatewayDomain": cluster.GetServiceDomain(),
"apiAddress": apiAddress,
"defaultTTL": cluster.GetDefaultTTL(),
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
"installDir": cluster.GetInstallDir(),
"timeZone": cluster.GetTimeZone(),
"isOn": cluster.GetIsOn(),
"isDefault": cluster.GetIsDefault(),
"autoRemoteStart": cluster.GetAutoRemoteStart(),
"accessLogIsOn": cluster.GetAccessLogIsOn(),
"tlsPolicyJSON": cluster.GetTlsPolicyJSON(),
"countAllNodes": countAllNodes,
"countActiveNodes": countActiveNodes,
"countUpgradeNodes": countUpgradeNodes,
})
}
return result, nil
}
func countUpgradeHTTPDNSNodes(parent *actionutils.ParentAction, clusterID int64) (int64, error) {
countResp, err := parent.RPC().HTTPDNSNodeRPC().CountAllUpgradeHTTPDNSNodesWithClusterId(parent.AdminContext(), &pb.CountAllUpgradeHTTPDNSNodesWithClusterIdRequest{
ClusterId: clusterID,
})
if err == nil {
return countResp.GetCount(), nil
}
grpcStatus, ok := status.FromError(err)
if !ok || grpcStatus.Code() != codes.Unimplemented {
return 0, err
}
// Compatibility fallback: old edge-api may not implement countAllUpgradeHTTPDNSNodesWithClusterId yet.
listResp, listErr := parent.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(parent.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{
ClusterId: clusterID,
})
if listErr == nil {
return int64(len(listResp.GetNodes())), nil
}
listStatus, ok := status.FromError(listErr)
if ok && listStatus.Code() == codes.Unimplemented {
// Compatibility fallback: both methods missing on old edge-api, don't block page rendering.
return 0, nil
}
return 0, listErr
}
func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
if clusterID > 0 {
var headerMD metadata.MD
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
ClusterId: clusterID,
})
}, grpc.Header(&headerMD))
if err != nil {
return nil, err
}
if resp.GetCluster() != nil {
cluster := resp.GetCluster()
autoRemoteStart := cluster.GetAutoRemoteStart()
accessLogIsOn := cluster.GetAccessLogIsOn()
timeZone := cluster.GetTimeZone()
// Compatibility fallback:
// Some deployed admin binaries may decode newly-added protobuf fields incorrectly.
// Read values from grpc response headers as a source of truth.
if values := headerMD.Get("x-httpdns-auto-remote-start"); len(values) > 0 {
autoRemoteStart = parseBoolLike(values[0], autoRemoteStart)
}
if values := headerMD.Get("x-httpdns-access-log-is-on"); len(values) > 0 {
accessLogIsOn = parseBoolLike(values[0], accessLogIsOn)
}
if values := headerMD.Get("x-httpdns-time-zone"); len(values) > 0 {
if tz := strings.TrimSpace(values[0]); len(tz) > 0 {
timeZone = tz
}
}
return maps.Map{
"id": cluster.GetId(),
"name": cluster.GetName(),
@@ -92,8 +156,9 @@ func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map
"isOn": cluster.GetIsOn(),
"isDefault": cluster.GetIsDefault(),
"tlsPolicyJSON": cluster.GetTlsPolicyJSON(),
"autoRemoteStart": cluster.GetAutoRemoteStart(),
"accessLogIsOn": cluster.GetAccessLogIsOn(),
"autoRemoteStart": autoRemoteStart,
"accessLogIsOn": accessLogIsOn,
"timeZone": timeZone,
}, nil
}
}
@@ -109,12 +174,25 @@ func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map
"gatewayDomain": "",
"defaultTTL": 60,
"fallbackTimeout": 300,
"installDir": "/opt/edge-httpdns",
"installDir": "/root/edge-httpdns",
"timeZone": "Asia/Shanghai",
}, nil
}
return clusters[0], nil
}
func parseBoolLike(raw string, defaultValue bool) bool {
s := strings.ToLower(strings.TrimSpace(raw))
switch s {
case "1", "true", "on", "yes", "enabled":
return true
case "0", "false", "off", "no", "disabled":
return false
default:
return defaultValue
}
}
func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map, error) {
resp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{
ClusterId: clusterID,
@@ -132,21 +210,21 @@ func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map
ip = parsed
}
nodeMap := maps.Map{
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"installDir": node.GetInstallDir(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"status": statusMap,
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"installDir": node.GetInstallDir(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"status": statusMap,
"installStatus": installStatusMap,
"region": nil,
"login": nil,
"apiNodeAddrs": []string{},
"region": nil,
"login": nil,
"apiNodeAddrs": []string{},
"cluster": maps.Map{
"id": node.GetClusterId(),
"installDir": node.GetInstallDir(),
@@ -227,21 +305,21 @@ func decodeNodeStatus(raw []byte) maps.Map {
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
return maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
}
}

View File

@@ -1,16 +1,108 @@
package clusters
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
import (
"encoding/json"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpgradeRemoteAction struct {
actionutils.ParentAction
}
func (this *UpgradeRemoteAction) Init() {
this.Nav("httpdns", "cluster", "index")
}
func (this *UpgradeRemoteAction) RunGet(params struct {
NodeId int64
ClusterId int64
}) {
this.Data["nodeId"] = params.NodeId
httpdnsutils.AddLeftMenu(this.Parent())
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
this.Data["clusterId"] = params.ClusterId
this.Data["cluster"] = cluster
resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{
ClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
nodes := make([]maps.Map, 0, len(resp.GetNodes()))
for _, upgradeNode := range resp.GetNodes() {
node := upgradeNode.Node
if node == nil {
continue
}
loginParams := maps.Map{}
if node.GetNodeLogin() != nil && len(node.GetNodeLogin().GetParams()) > 0 {
_ = json.Unmarshal(node.GetNodeLogin().GetParams(), &loginParams)
}
status := decodeNodeStatus(node.GetStatusJSON())
accessIP := strings.TrimSpace(status.GetString("hostIP"))
if len(accessIP) == 0 {
accessIP = strings.TrimSpace(node.GetName())
}
nodes = append(nodes, maps.Map{
"id": node.GetId(),
"name": node.GetName(),
"accessIP": accessIP,
"oldVersion": upgradeNode.OldVersion,
"newVersion": upgradeNode.NewVersion,
"login": node.GetNodeLogin(),
"loginParams": loginParams,
"installStatus": decodeUpgradeInstallStatus(node.GetInstallStatusJSON()),
})
}
this.Data["nodes"] = nodes
this.Show()
}
func (this *UpgradeRemoteAction) RunPost(params struct {
NodeId int64
Must *actions.Must
}) {
_, err := this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}
func decodeUpgradeInstallStatus(raw []byte) maps.Map {
result := maps.Map{
"isRunning": false,
"isFinished": false,
"isOk": false,
"error": "",
"errorCode": "",
}
if len(raw) == 0 {
return result
}
_ = json.Unmarshal(raw, &result)
return result
}

View File

@@ -0,0 +1,48 @@
package clusters
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpgradeStatusAction struct {
actionutils.ParentAction
}
func (this *UpgradeStatusAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.GetNode() == nil {
this.Data["status"] = nil
this.Success()
return
}
this.Data["status"] = decodeUpgradeInstallStatusMap(resp.GetNode().GetInstallStatusJSON())
this.Success()
}
func decodeUpgradeInstallStatusMap(raw []byte) map[string]interface{} {
result := map[string]interface{}{
"isRunning": false,
"isFinished": false,
"isOk": false,
"error": "",
"errorCode": "",
}
if len(raw) == 0 {
return result
}
_ = json.Unmarshal(raw, &result)
return result
}

View File

@@ -39,6 +39,7 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool)
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminServer), "", "/settings/server", "", this.tab == "server")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminUI), "", "/settings/ui", "", this.tab == "ui")
tabbar.Add("升级设置", "", "/settings/upgrade", "", this.tab == "upgrade")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security")
if teaconst.IsPlus {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary")

View File

@@ -42,6 +42,7 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool)
if teaconst.IsPlus {
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabUserUI), "", "/settings/user-ui", "", this.tab == "userUI")
}
tabbar.Add("升级设置", "", "/settings/upgrade", "", this.tab == "upgrade")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security")
tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary")
}

View File

@@ -56,9 +56,6 @@ func (this *IndexAction) RunPost(params struct {
TimeZone string
DnsResolverType string
SupportModuleCDN bool
SupportModuleNS bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -93,13 +90,7 @@ func (this *IndexAction) RunPost(params struct {
config.DefaultPageSize = 10
}
config.Modules = []userconfigs.UserModule{}
if params.SupportModuleCDN {
config.Modules = append(config.Modules, userconfigs.UserModuleCDN)
}
if params.SupportModuleNS {
config.Modules = append(config.Modules, userconfigs.UserModuleNS)
}
config.Modules = []userconfigs.UserModule{userconfigs.UserModuleCDN, userconfigs.UserModuleNS}
// 上传Favicon文件
if params.FaviconFile != nil {

View File

@@ -0,0 +1,23 @@
//go:build !plus
package upgrade
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
)
// loadDNSUpgradeModules 非Plus版本不支持DNS模块
func loadDNSUpgradeModules(parent *actionutils.ParentAction) []maps.Map {
return nil
}
// upgradeDNSNode 非Plus版本不支持DNS节点升级
func upgradeDNSNode(parent *actionutils.ParentAction, nodeId int64) error {
return nil
}
// loadDNSNodeStatus 非Plus版本不支持DNS节点状态查询
func loadDNSNodeStatus(parent *actionutils.ParentAction, nodeIds []int64) []maps.Map {
return nil
}

View File

@@ -0,0 +1,100 @@
//go:build plus
package upgrade
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// loadDNSUpgradeModules 加载DNS模块的待升级节点信息
func loadDNSUpgradeModules(parent *actionutils.ParentAction) []maps.Map {
clustersResp, err := parent.RPC().NSClusterRPC().ListNSClusters(parent.AdminContext(), &pb.ListNSClustersRequest{
Offset: 0,
Size: 10000,
})
if err != nil {
return nil
}
var clusterMaps []maps.Map
for _, cluster := range clustersResp.NsClusters {
nodesResp, err := parent.RPC().NSNodeRPC().FindAllUpgradeNSNodesWithNSClusterId(parent.AdminContext(), &pb.FindAllUpgradeNSNodesWithNSClusterIdRequest{
NsClusterId: cluster.Id,
})
if err != nil {
continue
}
var nodeMaps []maps.Map
for _, nodeUpgrade := range nodesResp.Nodes {
if nodeUpgrade.NsNode == nil {
continue
}
installStatusMap := decodeInstallStatusFromPB(nodeUpgrade.NsNode.InstallStatus)
accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.NsNode.StatusJSON, nodeUpgrade.NsNode.NodeLogin, nodeUpgrade.NsNode.Name)
nodeMaps = append(nodeMaps, maps.Map{
"id": nodeUpgrade.NsNode.Id,
"name": nodeUpgrade.NsNode.Name,
"os": nodeUpgrade.Os,
"arch": nodeUpgrade.Arch,
"oldVersion": nodeUpgrade.OldVersion,
"newVersion": nodeUpgrade.NewVersion,
"isOn": nodeUpgrade.NsNode.IsOn,
"isUp": nodeUpgrade.NsNode.IsUp,
"accessIP": accessIP,
"login": login,
"loginParams": loginParams,
"installStatus": installStatusMap,
})
}
if len(nodeMaps) == 0 {
continue
}
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"nodes": nodeMaps,
"count": len(nodeMaps),
})
}
return clusterMaps
}
// upgradeDNSNode 升级DNS节点
func upgradeDNSNode(parent *actionutils.ParentAction, nodeId int64) error {
_, err := parent.RPC().NSNodeRPC().UpgradeNSNode(parent.AdminContext(), &pb.UpgradeNSNodeRequest{
NsNodeId: nodeId,
})
return err
}
// loadDNSNodeStatus 加载DNS节点安装状态
func loadDNSNodeStatus(parent *actionutils.ParentAction, nodeIds []int64) []maps.Map {
var result []maps.Map
for _, nodeId := range nodeIds {
resp, err := parent.RPC().NSNodeRPC().FindNSNode(parent.AdminContext(), &pb.FindNSNodeRequest{
NsNodeId: nodeId,
})
if err != nil || resp.NsNode == nil {
continue
}
var installStatusMap maps.Map
if resp.NsNode.InstallStatus != nil {
installStatusMap = maps.Map{
"isRunning": resp.NsNode.InstallStatus.IsRunning,
"isFinished": resp.NsNode.InstallStatus.IsFinished,
"isOk": resp.NsNode.InstallStatus.IsOk,
"error": resp.NsNode.InstallStatus.Error,
"errorCode": resp.NsNode.InstallStatus.ErrorCode,
"updatedAt": resp.NsNode.InstallStatus.UpdatedAt,
}
}
result = append(result, maps.Map{
"id": nodeId,
"installStatus": installStatusMap,
})
}
return result
}

View File

@@ -1,6 +1,15 @@
package upgrade
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
import (
"encoding/json"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
@@ -11,5 +20,264 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct{}) {
// 加载升级配置
config, err := configloaders.LoadUpgradeConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 模块列表
modules := []maps.Map{}
// 1. 边缘节点 (EdgeNode)
nodeModule := this.loadEdgeNodeModule()
if nodeModule != nil {
modules = append(modules, nodeModule)
}
// 2. DNS节点 (EdgeDNS) — 仅Plus版本
if teaconst.IsPlus {
dnsClusters := loadDNSUpgradeModules(&this.ParentAction)
if len(dnsClusters) > 0 {
totalCount := 0
for _, c := range dnsClusters {
totalCount += c.GetInt("count")
}
modules = append(modules, maps.Map{
"name": "DNS节点",
"code": "dns",
"clusters": dnsClusters,
"count": totalCount,
})
}
}
// 3. HTTPDNS节点
httpdnsModule := this.loadHTTPDNSModule()
if httpdnsModule != nil {
modules = append(modules, httpdnsModule)
}
this.Data["modules"] = modules
this.Show()
}
func (this *IndexAction) RunPost(params struct {
AutoUpgrade bool
}) {
config, err := configloaders.LoadUpgradeConfig()
if err != nil {
this.ErrorPage(err)
return
}
config.AutoUpgrade = params.AutoUpgrade
err = configloaders.UpdateUpgradeConfig(config)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}
// loadEdgeNodeModule 加载边缘节点模块的待升级信息
func (this *IndexAction) loadEdgeNodeModule() maps.Map {
// 获取所有集群
clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{
Offset: 0,
Size: 10000,
})
if err != nil {
return nil
}
var clusterMaps []maps.Map
totalCount := 0
for _, cluster := range clustersResp.NodeClusters {
resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithNodeClusterIdRequest{
NodeClusterId: cluster.Id,
})
if err != nil {
continue
}
if len(resp.Nodes) == 0 {
continue
}
var nodeMaps []maps.Map
for _, nodeUpgrade := range resp.Nodes {
if nodeUpgrade.Node == nil {
continue
}
installStatusMap := decodeInstallStatusFromPB(nodeUpgrade.Node.InstallStatus)
accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.Node.StatusJSON, nodeUpgrade.Node.NodeLogin, nodeUpgrade.Node.Name)
nodeMaps = append(nodeMaps, maps.Map{
"id": nodeUpgrade.Node.Id,
"name": nodeUpgrade.Node.Name,
"os": nodeUpgrade.Os,
"arch": nodeUpgrade.Arch,
"oldVersion": nodeUpgrade.OldVersion,
"newVersion": nodeUpgrade.NewVersion,
"isOn": nodeUpgrade.Node.IsOn,
"isUp": nodeUpgrade.Node.IsUp,
"accessIP": accessIP,
"login": login,
"loginParams": loginParams,
"installStatus": installStatusMap,
})
}
totalCount += len(nodeMaps)
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"nodes": nodeMaps,
"count": len(nodeMaps),
})
}
if len(clusterMaps) == 0 {
return nil
}
return maps.Map{
"name": "边缘节点",
"code": "node",
"clusters": clusterMaps,
"count": totalCount,
}
}
// loadHTTPDNSModule 加载HTTPDNS模块的待升级信息
func (this *IndexAction) loadHTTPDNSModule() maps.Map {
clustersResp, err := this.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(this.AdminContext(), &pb.ListHTTPDNSClustersRequest{
Offset: 0,
Size: 10000,
})
if err != nil {
return nil
}
var clusterMaps []maps.Map
totalCount := 0
for _, cluster := range clustersResp.Clusters {
resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{
ClusterId: cluster.Id,
})
if err != nil {
continue
}
if len(resp.Nodes) == 0 {
continue
}
var nodeMaps []maps.Map
for _, nodeUpgrade := range resp.Nodes {
if nodeUpgrade.Node == nil {
continue
}
installStatusMap := decodeInstallStatusFromJSON(nodeUpgrade.Node.InstallStatusJSON)
accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.Node.StatusJSON, nodeUpgrade.Node.NodeLogin, nodeUpgrade.Node.Name)
nodeMaps = append(nodeMaps, maps.Map{
"id": nodeUpgrade.Node.Id,
"name": nodeUpgrade.Node.Name,
"os": nodeUpgrade.Os,
"arch": nodeUpgrade.Arch,
"oldVersion": nodeUpgrade.OldVersion,
"newVersion": nodeUpgrade.NewVersion,
"isOn": nodeUpgrade.Node.IsOn,
"isUp": nodeUpgrade.Node.IsUp,
"accessIP": accessIP,
"login": login,
"loginParams": loginParams,
"installStatus": installStatusMap,
})
}
totalCount += len(nodeMaps)
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"nodes": nodeMaps,
"count": len(nodeMaps),
})
}
if len(clusterMaps) == 0 {
return nil
}
return maps.Map{
"name": "HTTPDNS节点",
"code": "httpdns",
"clusters": clusterMaps,
"count": totalCount,
}
}
// decodeInstallStatusFromPB 从 protobuf InstallStatus 解码安装状态
func decodeInstallStatusFromPB(status *pb.NodeInstallStatus) maps.Map {
if status == nil {
return nil
}
// 历史成功状态,在待升级列表中忽略
if status.IsFinished && status.IsOk {
return nil
}
return maps.Map{
"isRunning": status.IsRunning,
"isFinished": status.IsFinished,
"isOk": status.IsOk,
"error": status.Error,
"errorCode": status.ErrorCode,
"updatedAt": status.UpdatedAt,
}
}
// decodeInstallStatusFromJSON 从 JSON 字节解码安装状态
func decodeInstallStatusFromJSON(raw []byte) maps.Map {
if len(raw) == 0 {
return nil
}
result := maps.Map{}
_ = json.Unmarshal(raw, &result)
isFinished, _ := result["isFinished"].(bool)
isOk, _ := result["isOk"].(bool)
if isFinished && isOk {
return nil
}
return result
}
// decodeNodeAccessInfo 从节点状态和登录信息中提取 accessIP、login、loginParams
func decodeNodeAccessInfo(statusJSON []byte, nodeLogin *pb.NodeLogin, nodeName string) (accessIP string, login maps.Map, loginParams maps.Map) {
// 从 statusJSON 中提取 hostIP 作为 accessIP
if len(statusJSON) > 0 {
statusMap := maps.Map{}
_ = json.Unmarshal(statusJSON, &statusMap)
accessIP = strings.TrimSpace(statusMap.GetString("hostIP"))
}
if len(accessIP) == 0 {
accessIP = strings.TrimSpace(nodeName)
}
// 解码 login 信息
if nodeLogin != nil {
login = maps.Map{
"id": nodeLogin.Id,
"name": nodeLogin.Name,
"type": nodeLogin.Type,
}
if len(nodeLogin.Params) > 0 {
loginParams = maps.Map{}
_ = json.Unmarshal(nodeLogin.Params, &loginParams)
}
}
return
}

View File

@@ -13,7 +13,9 @@ func init() {
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewHelper("upgrade")).
Prefix("/settings/upgrade").
Get("", new(IndexAction)).
GetPost("", new(IndexAction)).
Post("/upgradeNode", new(UpgradeNodeAction)).
Post("/status", new(StatusAction)).
EndAll()
})
}

View File

@@ -0,0 +1,80 @@
package upgrade
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type StatusAction struct {
actionutils.ParentAction
}
func (this *StatusAction) RunPost(params struct {
NodeIdsJSON []byte // JSON: {"node": [1,2], "dns": [3], "httpdns": [4,5]}
}) {
var nodeIdsMap map[string][]int64
if len(params.NodeIdsJSON) > 0 {
_ = json.Unmarshal(params.NodeIdsJSON, &nodeIdsMap)
}
result := maps.Map{}
// EdgeNode 状态
if nodeIds, ok := nodeIdsMap["node"]; ok && len(nodeIds) > 0 {
var nodeStatuses []maps.Map
for _, nodeId := range nodeIds {
resp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil || resp.Node == nil {
continue
}
var installStatusMap maps.Map
if resp.Node.InstallStatus != nil {
installStatusMap = maps.Map{
"isRunning": resp.Node.InstallStatus.IsRunning,
"isFinished": resp.Node.InstallStatus.IsFinished,
"isOk": resp.Node.InstallStatus.IsOk,
"error": resp.Node.InstallStatus.Error,
"errorCode": resp.Node.InstallStatus.ErrorCode,
"updatedAt": resp.Node.InstallStatus.UpdatedAt,
}
}
nodeStatuses = append(nodeStatuses, maps.Map{
"id": nodeId,
"installStatus": installStatusMap,
})
}
result["node"] = nodeStatuses
}
// DNS 状态
if nodeIds, ok := nodeIdsMap["dns"]; ok && len(nodeIds) > 0 {
result["dns"] = loadDNSNodeStatus(&this.ParentAction, nodeIds)
}
// HTTPDNS 状态
if nodeIds, ok := nodeIdsMap["httpdns"]; ok && len(nodeIds) > 0 {
var nodeStatuses []maps.Map
for _, nodeId := range nodeIds {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{NodeId: nodeId})
if err != nil || resp.Node == nil {
continue
}
var installStatusMap maps.Map
if len(resp.Node.InstallStatusJSON) > 0 {
installStatusMap = maps.Map{}
_ = json.Unmarshal(resp.Node.InstallStatusJSON, &installStatusMap)
}
nodeStatuses = append(nodeStatuses, maps.Map{
"id": nodeId,
"installStatus": installStatusMap,
})
}
result["httpdns"] = nodeStatuses
}
this.Data["statuses"] = result
this.Success()
}

View File

@@ -0,0 +1,150 @@
package upgrade
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UpgradeNodeAction struct {
actionutils.ParentAction
}
func (this *UpgradeNodeAction) RunPost(params struct {
Module string // node, dns, httpdns
Scope string // all, module, cluster, node
ClusterId int64
NodeId int64
}) {
switch params.Scope {
case "node":
err := this.upgradeSingleNode(params.Module, params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
case "cluster":
err := this.upgradeCluster(params.Module, params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
case "module":
err := this.upgradeModule(params.Module)
if err != nil {
this.ErrorPage(err)
return
}
case "all":
_ = this.upgradeModule("node")
_ = this.upgradeModule("dns")
_ = this.upgradeModule("httpdns")
}
this.Success()
}
func (this *UpgradeNodeAction) upgradeSingleNode(module string, nodeId int64) error {
switch module {
case "node":
_, err := this.RPC().NodeRPC().UpgradeNode(this.AdminContext(), &pb.UpgradeNodeRequest{NodeId: nodeId})
return err
case "dns":
return upgradeDNSNode(&this.ParentAction, nodeId)
case "httpdns":
_, err := this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{NodeId: nodeId})
return err
}
return nil
}
func (this *UpgradeNodeAction) upgradeCluster(module string, clusterId int64) error {
switch module {
case "node":
resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithNodeClusterIdRequest{
NodeClusterId: clusterId,
})
if err != nil {
return err
}
for _, nodeUpgrade := range resp.Nodes {
if nodeUpgrade.Node == nil {
continue
}
_, _ = this.RPC().NodeRPC().UpgradeNode(this.AdminContext(), &pb.UpgradeNodeRequest{NodeId: nodeUpgrade.Node.Id})
}
case "dns":
this.upgradeDNSCluster(clusterId)
case "httpdns":
resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{
ClusterId: clusterId,
})
if err != nil {
return err
}
for _, nodeUpgrade := range resp.Nodes {
if nodeUpgrade.Node == nil {
continue
}
_, _ = this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{NodeId: nodeUpgrade.Node.Id})
}
}
return nil
}
func (this *UpgradeNodeAction) upgradeModule(module string) error {
switch module {
case "node":
clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{
Offset: 0,
Size: 10000,
})
if err != nil {
return err
}
for _, cluster := range clustersResp.NodeClusters {
_ = this.upgradeCluster("node", cluster.Id)
}
case "dns":
dnsClusters := loadDNSUpgradeModules(&this.ParentAction)
for _, c := range dnsClusters {
this.upgradeDNSClusterFromMap(c)
}
case "httpdns":
clustersResp, err := this.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(this.AdminContext(), &pb.ListHTTPDNSClustersRequest{
Offset: 0,
Size: 10000,
})
if err != nil {
return err
}
for _, cluster := range clustersResp.Clusters {
_ = this.upgradeCluster("httpdns", cluster.Id)
}
}
return nil
}
// upgradeDNSCluster 根据集群ID升级DNS节点
func (this *UpgradeNodeAction) upgradeDNSCluster(clusterId int64) {
dnsClusters := loadDNSUpgradeModules(&this.ParentAction)
for _, c := range dnsClusters {
if c.GetInt64("id") == clusterId {
this.upgradeDNSClusterFromMap(c)
break
}
}
}
// upgradeDNSClusterFromMap 从maps.Map中提取节点ID并升级
func (this *UpgradeNodeAction) upgradeDNSClusterFromMap(c maps.Map) {
nodesVal := c.Get("nodes")
if nodeMaps, ok := nodesVal.([]maps.Map); ok {
for _, n := range nodeMaps {
nodeId := n.GetInt64("id")
if nodeId > 0 {
_ = upgradeDNSNode(&this.ParentAction, nodeId)
}
}
}
}

View File

@@ -25,20 +25,16 @@ func (this *CreatePopupAction) Init() {
func (this *CreatePopupAction) RunGet(params struct{}) {
// 检查是否启用了 HTTPDNS 功能(全局用户注册设置中)
var hasHTTPDNSFeature = false
var defaultHttpdnsClusterId int64 = 0
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserRegisterConfig})
if err == nil && len(resp.ValueJSON) > 0 {
var config = userconfigs.DefaultUserRegisterConfig()
if json.Unmarshal(resp.ValueJSON, config) == nil {
hasHTTPDNSFeature = config.HTTPDNSIsOn
if len(config.HTTPDNSDefaultClusterIds) > 0 {
defaultHttpdnsClusterId = config.HTTPDNSDefaultClusterIds[0]
}
}
}
this.Data["hasHTTPDNSFeature"] = hasHTTPDNSFeature
this.Data["httpdnsClusterId"] = defaultHttpdnsClusterId
this.Data["httpdnsClusterId"] = 0
// 加载所有 HTTPDNS 集群
var httpdnsClusters = []maps.Map{}