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
}