管理端全部功能跑通

This commit is contained in:
robin
2026-02-27 10:35:22 +08:00
parent 4d275c921d
commit 150799f41d
263 changed files with 22664 additions and 4053 deletions

View File

@@ -2,8 +2,6 @@ package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
)
type CertsAction struct {
@@ -15,7 +13,5 @@ func (this *CertsAction) Init() {
}
func (this *CertsAction) RunGet(params struct{}) {
httpdnsutils.AddLeftMenu(this.Parent())
this.Data["certs"] = policies.LoadPublicSNICertificates()
this.Show()
this.RedirectURL("/httpdns/clusters")
}

View File

@@ -1,8 +1,11 @@
package clusters
import (
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/iwind/TeaGo/maps"
)
type ClusterAction struct {
@@ -20,19 +23,75 @@ func (this *ClusterAction) RunGet(params struct {
Keyword string
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
nodes, err := listNodeMaps(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
nodes = filterClusterNodes(nodes, params.InstalledState, params.ActiveState, params.Keyword)
this.Data["clusterId"] = params.ClusterId
this.Data["cluster"] = cluster
this.Data["installState"] = params.InstalledState
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
nodes := mockNodes(params.ClusterId, params.InstalledState, params.ActiveState, params.Keyword)
this.Data["nodes"] = nodes
this.Data["hasNodes"] = len(nodes) > 0
this.Data["page"] = ""
this.Show()
}
func filterClusterNodes(nodes []maps.Map, installedState int, activeState int, keyword string) []maps.Map {
keyword = strings.ToLower(strings.TrimSpace(keyword))
result := make([]maps.Map, 0, len(nodes))
for _, node := range nodes {
isInstalled := node.GetBool("isInstalled")
if installedState == 1 && !isInstalled {
continue
}
if installedState == 2 && isInstalled {
continue
}
status := node.GetMap("status")
isOnline := node.GetBool("isOn") && node.GetBool("isUp") && status.GetBool("isActive")
if activeState == 1 && !isOnline {
continue
}
if activeState == 2 && isOnline {
continue
}
if len(keyword) > 0 {
hit := strings.Contains(strings.ToLower(node.GetString("name")), keyword)
if !hit {
ipAddresses, ok := node["ipAddresses"].([]maps.Map)
if ok {
for _, ipAddr := range ipAddresses {
if strings.Contains(strings.ToLower(ipAddr.GetString("ip")), keyword) {
hit = true
break
}
}
}
}
if !hit {
continue
}
}
result = append(result, node)
}
return result
}

View File

@@ -1,8 +1,10 @@
package node
import (
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
@@ -18,51 +20,37 @@ func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
this.Data["nodeDatetime"] = "2026-02-22 12:00:00"
this.Data["nodeTimeDiff"] = 0
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
this.Data["node"] = maps.Map{
"id": params.NodeId,
"name": "Mock HTTPDNS Node",
"ipAddresses": []maps.Map{{"ip": "100.200.100.200", "name": "Public IP", "canAccess": true, "isOn": true, "isUp": true}},
"cluster": maps.Map{"id": params.ClusterId, "name": "Mock Cluster", "installDir": "/opt/edge-httpdns"},
"installDir": "/opt/edge-httpdns",
"isInstalled": true,
"uniqueId": "m-1234567890",
"secret": "mock-secret-key",
"isOn": true,
"isUp": true,
"apiNodeAddrs": []string{"192.168.1.100:8001"},
"login": nil,
"status": maps.Map{
"isActive": true,
"updatedAt": 1670000000,
"hostname": "node-01.local",
"cpuUsage": 0.15,
"cpuUsageText": "15.00%",
"memUsage": 0.45,
"memUsageText": "45.00%",
"connectionCount": 100,
"buildVersion": "1.0.0",
"cpuPhysicalCount": 4,
"cpuLogicalCount": 8,
"load1m": "0.50",
"load5m": "0.60",
"load15m": "0.70",
"cacheTotalDiskSize": "10G",
"cacheTotalMemorySize": "2G",
"exePath": "/opt/edge-httpdns/bin/edge-httpdns",
"apiSuccessPercent": 100.0,
"apiAvgCostSeconds": 0.05,
},
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["node"] = node
this.Data["currentCluster"] = cluster
status := node.GetMap("status")
updatedAt := status.GetInt64("updatedAt")
nodeDatetime := ""
nodeTimeDiff := int64(0)
if updatedAt > 0 {
nodeDatetime = timeutil.FormatTime("Y-m-d H:i:s", updatedAt)
nodeTimeDiff = time.Now().Unix() - updatedAt
if nodeTimeDiff < 0 {
nodeTimeDiff = -nodeTimeDiff
}
}
this.Data["nodeDatetime"] = nodeDatetime
this.Data["nodeTimeDiff"] = nodeTimeDiff
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
this.Show()
}

View File

@@ -1,8 +1,12 @@
package node
package node
import (
"encoding/json"
"strings"
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type InstallAction struct {
@@ -14,28 +18,87 @@ func (this *InstallAction) Init() {
this.SecondMenu("nodes")
}
func (this *InstallAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
func (this *InstallAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
this.Data["currentCluster"] = cluster
this.Data["node"] = node
this.Data["installStatus"] = node.GetMap("installStatus")
this.Data["apiEndpoints"] = "\"http://127.0.0.1:7788\""
this.Data["sshAddr"] = "192.168.1.100:22"
this.Data["node"] = maps.Map{
"id": params.NodeId,
"name": "Mock Node",
"isInstalled": false,
"uniqueId": "m-1234567890",
"secret": "mock-secret-key",
"installDir": "/opt/edge-httpdns",
"cluster": maps.Map{"installDir": "/opt/edge-httpdns"},
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["installStatus"] = nil
apiEndpoints := make([]string, 0, 8)
for _, apiNode := range apiNodesResp.GetApiNodes() {
if !apiNode.GetIsOn() {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.GetAccessAddrs()...)
}
if len(apiEndpoints) == 0 {
apiEndpoints = []string{"http://127.0.0.1:7788"}
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
this.Data["sshAddr"] = ""
this.Show()
}
func (this *InstallAction) RunPost(params struct{ NodeId int64 }) {
func (this *InstallAction) RunPost(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.GetNode()
if node == nil {
this.Fail("节点不存在")
return
}
existingStatus := map[string]interface{}{}
if len(node.GetInstallStatusJSON()) > 0 {
_ = json.Unmarshal(node.GetInstallStatusJSON(), &existingStatus)
}
existingStatus["isRunning"] = true
existingStatus["isFinished"] = false
existingStatus["isOk"] = false
existingStatus["error"] = ""
existingStatus["errorCode"] = ""
existingStatus["updatedAt"] = time.Now().Unix()
installStatusJSON, _ := json.Marshal(existingStatus)
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: node.GetIsUp(),
IsInstalled: false,
IsActive: node.GetIsActive(),
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: installStatusJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,7 +1,11 @@
package node
package node
import (
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/iwind/TeaGo/maps"
)
@@ -14,18 +18,65 @@ func (this *LogsAction) Init() {
this.SecondMenu("nodes")
}
func (this *LogsAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
func (this *LogsAction) RunGet(params struct {
ClusterId int64
NodeId int64
DayFrom string
DayTo string
Level string
Keyword string
}) {
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
this.Data["currentCluster"] = cluster
this.Data["node"] = node
this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo
this.Data["level"] = params.Level
this.Data["keyword"] = params.Keyword
this.Data["dayFrom"] = ""
this.Data["dayTo"] = ""
this.Data["keyword"] = ""
this.Data["level"] = ""
this.Data["logs"] = []maps.Map{}
day := strings.TrimSpace(params.DayFrom)
if len(day) == 0 {
day = strings.TrimSpace(params.DayTo)
}
resp, err := this.RPC().HTTPDNSRuntimeLogRPC().ListHTTPDNSRuntimeLogs(this.AdminContext(), &pb.ListHTTPDNSRuntimeLogsRequest{
Day: day,
ClusterId: params.ClusterId,
NodeId: params.NodeId,
Level: strings.TrimSpace(params.Level),
Keyword: strings.TrimSpace(params.Keyword),
Offset: 0,
Size: 100,
})
if err != nil {
this.ErrorPage(err)
return
}
logs := make([]maps.Map, 0, len(resp.GetLogs()))
for _, item := range resp.GetLogs() {
logs = append(logs, maps.Map{
"level": item.GetLevel(),
"tag": item.GetType(),
"description": item.GetDescription(),
"createdAt": item.GetCreatedAt(),
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", item.GetCreatedAt()),
"count": item.GetCount(),
})
}
this.Data["logs"] = logs
this.Data["page"] = ""
this.Data["node"] = maps.Map{"id": params.NodeId, "name": "Mock Node"}
this.Show()
}

View File

@@ -0,0 +1,183 @@
package node
import (
"encoding/json"
"fmt"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func findHTTPDNSClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
ClusterId: clusterID,
})
if err != nil {
return nil, err
}
if resp.GetCluster() == nil {
return maps.Map{
"id": clusterID,
"name": "",
"installDir": "/opt/edge-httpdns",
}, nil
}
cluster := resp.GetCluster()
installDir := strings.TrimSpace(cluster.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
}
return maps.Map{
"id": cluster.GetId(),
"name": cluster.GetName(),
"installDir": installDir,
}, nil
}
func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Map, error) {
resp, err := parent.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(parent.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: nodeID,
})
if err != nil {
return nil, err
}
if resp.GetNode() == nil {
return maps.Map{}, nil
}
node := resp.GetNode()
statusMap := decodeNodeStatus(node.GetStatusJSON())
installStatusMap := decodeInstallStatus(node.GetInstallStatusJSON())
var ipAddresses = []maps.Map{}
if installStatusMap.Has("ipAddresses") {
for _, addr := range installStatusMap.GetSlice("ipAddresses") {
if addrMap, ok := addr.(map[string]interface{}); ok {
ipAddresses = append(ipAddresses, maps.Map(addrMap))
}
}
} else {
ip := node.GetName()
if savedIP := strings.TrimSpace(installStatusMap.GetString("ipAddr")); len(savedIP) > 0 {
ip = savedIP
} else if hostIP := strings.TrimSpace(statusMap.GetString("hostIP")); len(hostIP) > 0 {
ip = hostIP
}
ipAddresses = append(ipAddresses, maps.Map{
"id": node.GetId(),
"name": "Public IP",
"ip": ip,
"canAccess": true,
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
})
}
installDir := strings.TrimSpace(node.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
}
clusterMap, err := findHTTPDNSClusterMap(parent, node.GetClusterId())
if err != nil {
return nil, err
}
// 构造 node.login 用于 index.html 展示 SSH 信息
var loginMap maps.Map = nil
sshInfo := installStatusMap.GetMap("ssh")
if sshInfo != nil {
grantId := sshInfo.GetInt64("grantId")
var grantMap maps.Map = nil
if grantId > 0 {
grantResp, grantErr := parent.RPC().NodeGrantRPC().FindEnabledNodeGrant(parent.AdminContext(), &pb.FindEnabledNodeGrantRequest{
NodeGrantId: grantId,
})
if grantErr == nil && grantResp.GetNodeGrant() != nil {
g := grantResp.GetNodeGrant()
grantMap = maps.Map{
"id": g.Id,
"name": g.Name,
"methodName": g.Method,
"username": g.Username,
}
}
}
loginMap = maps.Map{
"params": maps.Map{
"host": sshInfo.GetString("host"),
"port": sshInfo.GetInt("port"),
},
"grant": grantMap,
}
}
return maps.Map{
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"installDir": installDir,
"status": statusMap,
"installStatus": installStatusMap,
"cluster": clusterMap,
"login": loginMap,
"apiNodeAddrs": []string{},
"ipAddresses": ipAddresses,
}, nil
}
func decodeNodeStatus(raw []byte) maps.Map {
status := &nodeconfigs.NodeStatus{}
if len(raw) > 0 {
_ = json.Unmarshal(raw, status)
}
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
return maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
}
}
func decodeInstallStatus(raw []byte) maps.Map {
result := maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
"errorCode": "",
}
if len(raw) == 0 {
return result
}
_ = json.Unmarshal(raw, &result)
return result
}

View File

@@ -1,13 +1,41 @@
package node
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StartAction struct {
actionutils.ParentAction
}
func (this *StartAction) RunPost(params struct{ NodeId int64 }) {
func (this *StartAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := resp.GetNode()
if node == nil {
this.Fail("节点不存在")
return
}
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: true,
IsInstalled: node.GetIsInstalled(),
IsActive: true,
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: node.GetInstallStatusJSON(),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -2,7 +2,7 @@ package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/maps"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StatusAction struct {
@@ -14,14 +14,28 @@ func (this *StatusAction) Init() {
this.SecondMenu("nodes")
}
func (this *StatusAction) RunPost(params struct{ NodeId int64 }) {
this.Data["installStatus"] = maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
"errorCode": "",
func (this *StatusAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isInstalled"] = true
if resp.GetNode() == nil {
this.Fail("节点不存在")
return
}
nodeMap, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["installStatus"] = nodeMap.GetMap("installStatus")
this.Data["isInstalled"] = nodeMap.GetBool("isInstalled")
this.Success()
}

View File

@@ -1,13 +1,41 @@
package node
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StopAction struct {
actionutils.ParentAction
}
func (this *StopAction) RunPost(params struct{ NodeId int64 }) {
func (this *StopAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := resp.GetNode()
if node == nil {
this.Fail("节点不存在")
return
}
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: node.GetIsUp(),
IsInstalled: node.GetIsInstalled(),
IsActive: false,
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: node.GetInstallStatusJSON(),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,7 +1,14 @@
package node
package node
import (
"encoding/json"
"net"
"regexp"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
@@ -14,28 +21,210 @@ func (this *UpdateAction) Init() {
this.SecondMenu("nodes")
}
func (this *UpdateAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
func (this *UpdateAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
this.Data["clusters"] = []maps.Map{{"id": params.ClusterId, "name": "Mock Cluster"}}
this.Data["loginId"] = 0
this.Data["sshHost"] = "192.168.1.100"
this.Data["sshPort"] = 22
this.Data["grant"] = nil
this.Data["currentCluster"] = cluster
this.Data["clusters"] = []maps.Map{cluster}
this.Data["apiNodeAddrs"] = []string{}
this.Data["node"] = node
this.Data["node"] = maps.Map{
"id": params.NodeId,
"name": "Mock Node",
"isOn": true,
"ipAddresses": []maps.Map{},
sshHost := ""
sshPort := 22
ipAddresses := []maps.Map{}
var grantId int64
this.Data["grant"] = nil
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err == nil && resp.GetNode() != nil {
if len(resp.GetNode().GetInstallStatusJSON()) > 0 {
installStatus := maps.Map{}
_ = json.Unmarshal(resp.GetNode().GetInstallStatusJSON(), &installStatus)
sshInfo := installStatus.GetMap("ssh")
if sshInfo != nil {
if h := strings.TrimSpace(sshInfo.GetString("host")); len(h) > 0 {
sshHost = h
}
if p := sshInfo.GetInt("port"); p > 0 {
sshPort = p
}
grantId = sshInfo.GetInt64("grantId")
}
if installStatus.Has("ipAddresses") {
for _, addr := range installStatus.GetSlice("ipAddresses") {
if addrMap, ok := addr.(map[string]interface{}); ok {
ipAddresses = append(ipAddresses, maps.Map(addrMap))
}
}
} else if ip := strings.TrimSpace(installStatus.GetString("ipAddr")); len(ip) > 0 {
ipAddresses = append(ipAddresses, maps.Map{
"ip": ip,
"name": "",
"canAccess": true,
"isOn": true,
"isUp": true,
})
}
}
}
this.Data["sshHost"] = sshHost
this.Data["sshPort"] = sshPort
this.Data["ipAddresses"] = ipAddresses
if grantId > 0 {
grantResp, grantErr := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{
NodeGrantId: grantId,
})
if grantErr == nil && grantResp.GetNodeGrant() != nil {
g := grantResp.GetNodeGrant()
this.Data["grant"] = maps.Map{
"id": g.Id,
"name": g.Name,
"methodName": g.Method,
}
}
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct{ NodeId int64 }) {
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
ClusterId int64
IsOn bool
SshHost string
SshPort int
GrantId int64
IpAddressesJSON []byte
Must *actions.Must
}) {
params.Name = strings.TrimSpace(params.Name)
params.Must.Field("name", params.Name).Require("请输入节点名称")
params.SshHost = strings.TrimSpace(params.SshHost)
hasSSHUpdate := len(params.SshHost) > 0 || params.SshPort > 0 || params.GrantId > 0
if hasSSHUpdate {
if len(params.SshHost) == 0 {
this.Fail("请输入 SSH 主机地址")
}
if params.SshPort <= 0 || params.SshPort > 65535 {
this.Fail("SSH 端口范围必须在 1-65535 之间")
}
if params.GrantId <= 0 {
this.Fail("请选择 SSH 登录认证")
}
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
this.Fail("SSH 主机地址格式错误")
}
}
ipAddresses := []maps.Map{}
if len(params.IpAddressesJSON) > 0 {
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
if err != nil {
this.ErrorPage(err)
return
}
}
for _, addr := range ipAddresses {
ip := addr.GetString("ip")
if net.ParseIP(ip) == nil {
this.Fail("IP地址格式错误: " + ip)
}
}
needUpdateInstallStatus := hasSSHUpdate || len(ipAddresses) > 0
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.GetNode() == nil {
this.Fail("节点不存在")
return
}
node := resp.GetNode()
installDir := strings.TrimSpace(node.GetInstallDir())
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
}
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(this.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{
NodeId: params.NodeId,
Name: params.Name,
InstallDir: installDir,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
if needUpdateInstallStatus {
installStatus := maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": node.GetIsInstalled(),
"error": "",
"errorCode": "",
}
if len(node.GetInstallStatusJSON()) > 0 {
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
}
if hasSSHUpdate {
installStatus["ssh"] = maps.Map{
"host": params.SshHost,
"port": params.SshPort,
"grantId": params.GrantId,
}
}
if len(ipAddresses) > 0 {
installStatus["ipAddresses"] = ipAddresses
} else {
delete(installStatus, "ipAddresses")
delete(installStatus, "ipAddr") // Cleanup legacy
}
installStatusJSON, _ := json.Marshal(installStatus)
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: node.GetIsUp(),
IsInstalled: node.GetIsInstalled(),
IsActive: node.GetIsActive(),
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: installStatusJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -1,13 +1,58 @@
package node
package node
import (
"encoding/json"
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateInstallStatusAction struct {
actionutils.ParentAction
}
func (this *UpdateInstallStatusAction) RunPost(params struct{ NodeId int64 }) {
func (this *UpdateInstallStatusAction) RunPost(params struct {
NodeId int64
IsInstalled bool
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := resp.GetNode()
if node == nil {
this.Fail("节点不存在")
return
}
installStatus := map[string]interface{}{}
if len(node.GetInstallStatusJSON()) > 0 {
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
}
installStatus["isRunning"] = false
installStatus["isFinished"] = true
installStatus["isOk"] = params.IsInstalled
installStatus["error"] = ""
installStatus["errorCode"] = ""
installStatus["updatedAt"] = time.Now().Unix()
installStatusJSON, _ := json.Marshal(installStatus)
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: node.GetIsUp(),
IsInstalled: params.IsInstalled,
IsActive: node.GetIsActive(),
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: installStatusJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
@@ -27,80 +27,79 @@ func (this *ClusterSettingsAction) RunGet(params struct {
Section string
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
settings := loadClusterSettings(cluster)
cluster["name"] = settings.GetString("name")
// 构建顶部 tabbar
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "setting")
// 当前选中的 section
section := params.Section
section := strings.TrimSpace(params.Section)
if len(section) == 0 {
section = "basic"
}
this.Data["activeSection"] = section
// 左侧菜单
settings := maps.Map{
"name": cluster.GetString("name"),
"gatewayDomain": cluster.GetString("gatewayDomain"),
"cacheTtl": cluster.GetInt("defaultTTL"),
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
"installDir": cluster.GetString("installDir"),
"isOn": cluster.GetBool("isOn"),
"isDefaultCluster": cluster.GetBool("isDefault"),
}
if settings.GetInt("cacheTtl") <= 0 {
settings["cacheTtl"] = 30
}
if settings.GetInt("fallbackTimeout") <= 0 {
settings["fallbackTimeout"] = 300
}
if len(settings.GetString("installDir")) == 0 {
settings["installDir"] = "/opt/edge-httpdns"
}
listenAddresses := []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTPS,
Host: "",
PortRange: "443",
},
}
sslPolicy := &sslconfigs.SSLPolicy{
IsOn: true,
MinVersion: "TLS 1.1",
}
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
tlsConfig := maps.Map{}
if err := json.Unmarshal([]byte(rawTLS), &tlsConfig); err == nil {
if listenRaw := tlsConfig.Get("listen"); listenRaw != nil {
if data, err := json.Marshal(listenRaw); err == nil {
_ = json.Unmarshal(data, &listenAddresses)
}
}
if sslRaw := tlsConfig.Get("sslPolicy"); sslRaw != nil {
if data, err := json.Marshal(sslRaw); err == nil {
_ = json.Unmarshal(data, sslPolicy)
}
}
}
}
this.Data["activeSection"] = section
cid := strconv.FormatInt(params.ClusterId, 10)
this.Data["leftMenuItems"] = []map[string]interface{}{
{"name": "基础设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "&section=basic", "isActive": section == "basic"},
{"name": "端口设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "&section=tls", "isActive": section == "tls"},
{"name": "TLS", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "&section=tls", "isActive": section == "tls"},
}
settings["isDefaultCluster"] = (policies.LoadDefaultClusterID() == cluster.GetInt64("id"))
this.Data["cluster"] = cluster
// 构造前端需要的 tlsConfig 格式
var listenAddresses []*serverconfigs.NetworkAddressConfig
listenAddrsRaw := settings.GetString("listenAddrsJSON")
if len(listenAddrsRaw) > 0 {
_ = json.Unmarshal([]byte(listenAddrsRaw), &listenAddresses)
} else {
// 默认 443 端口
listenAddresses = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTPS,
Host: "",
PortRange: "443",
},
}
}
// 构造前端需要的 SSLPolicy
var sslPolicy *sslconfigs.SSLPolicy
originCertPem := settings.GetString("originCertPem")
originKeyPem := settings.GetString("originKeyPem")
if len(originCertPem) > 0 && len(originKeyPem) > 0 {
sslPolicy = &sslconfigs.SSLPolicy{
IsOn: true,
MinVersion: settings.GetString("tlsMinVersion"),
CipherSuitesIsOn: settings.GetBool("tlsCipherSuitesOn"),
OCSPIsOn: settings.GetBool("tlsOcspOn"),
ClientAuthType: int(settings.GetInt32("tlsClientAuthType")),
Certs: []*sslconfigs.SSLCertConfig{
{
IsOn: true,
CertData: []byte(originCertPem),
KeyData: []byte(originKeyPem),
},
},
}
} else {
sslPolicy = &sslconfigs.SSLPolicy{
IsOn: true,
MinVersion: "TLS 1.1",
}
}
this.Data["settings"] = settings
this.Data["tlsConfig"] = maps.Map{
"isOn": true,
"listen": listenAddresses,
"sslPolicy": sslPolicy,
}
this.Data["cluster"] = cluster
this.Data["settings"] = settings
this.Show()
}
@@ -120,88 +119,80 @@ func (this *ClusterSettingsAction) RunPost(params struct {
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.Field("clusterId", params.ClusterId).Gt(0, "please select cluster")
params.Must.Field("name", params.Name).Require("please input cluster name")
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("please input service domain")
params.Must.Field("cacheTtl", params.CacheTtl).Gt(0, "cache ttl should be greater than 0")
params.Must.Field("fallbackTimeout", params.FallbackTimeout).Gt(0, "fallback timeout should be greater than 0")
params.Must.Field("installDir", params.InstallDir).Require("please input install dir")
params.Name = strings.TrimSpace(params.Name)
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
params.InstallDir = strings.TrimSpace(params.InstallDir)
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
params.Must.Field("name", params.Name).Require("请输入集群名称")
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
if params.CacheTtl <= 0 {
params.CacheTtl = 30
}
if params.FallbackTimeout <= 0 {
params.FallbackTimeout = 300
}
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
}
if params.IsDefaultCluster && !params.IsOn {
this.Fail("默认集群必须保持启用状态")
return
}
cluster := pickCluster(params.ClusterId)
settings := loadClusterSettings(cluster)
settings["name"] = strings.TrimSpace(params.Name)
settings["gatewayDomain"] = strings.TrimSpace(params.GatewayDomain)
settings["cacheTtl"] = int(params.CacheTtl)
settings["fallbackTimeout"] = int(params.FallbackTimeout)
settings["installDir"] = strings.TrimSpace(params.InstallDir)
settings["isOn"] = params.IsOn
// 处理地址
var addresses = []*serverconfigs.NetworkAddressConfig{}
if len(params.Addresses) > 0 {
err := json.Unmarshal(params.Addresses, &addresses)
if err != nil {
this.Fail("端口地址解析失败:" + err.Error())
}
addressesJSON, _ := json.Marshal(addresses)
settings["listenAddrsJSON"] = string(addressesJSON)
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
// 处理 SSL 配置
var originCertPem = ""
var originKeyPem = ""
var tlsMinVersion = "TLS 1.1"
var tlsCipherSuitesOn = false
var tlsOcspOn = false
var tlsClientAuthType = sslconfigs.SSLClientAuthType(0)
tlsConfig := maps.Map{}
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
_ = json.Unmarshal([]byte(rawTLS), &tlsConfig)
}
if len(params.Addresses) > 0 {
var addresses []*serverconfigs.NetworkAddressConfig
if err := json.Unmarshal(params.Addresses, &addresses); err != nil {
this.Fail("监听端口配置格式不正确")
return
}
tlsConfig["listen"] = addresses
}
if len(params.SslPolicyJSON) > 0 {
sslPolicy := &sslconfigs.SSLPolicy{}
err := json.Unmarshal(params.SslPolicyJSON, sslPolicy)
if err == nil {
tlsMinVersion = sslPolicy.MinVersion
tlsCipherSuitesOn = sslPolicy.CipherSuitesIsOn
tlsOcspOn = sslPolicy.OCSPIsOn
tlsClientAuthType = sslconfigs.SSLClientAuthType(sslPolicy.ClientAuthType)
if err := json.Unmarshal(params.SslPolicyJSON, sslPolicy); err != nil {
this.Fail("TLS 配置格式不正确")
return
}
tlsConfig["sslPolicy"] = sslPolicy
}
if len(sslPolicy.Certs) > 0 {
cert := sslPolicy.Certs[0]
originCertPem = string(cert.CertData)
originKeyPem = string(cert.KeyData)
}
var tlsPolicyJSON []byte
if len(tlsConfig) > 0 {
tlsPolicyJSON, err = json.Marshal(tlsConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(originCertPem) == 0 || len(originKeyPem) == 0 {
this.Fail("请上传或选择证书")
}
settings["originHttps"] = true
settings["originCertPem"] = originCertPem
settings["originKeyPem"] = originKeyPem
if len(tlsMinVersion) == 0 {
tlsMinVersion = "TLS 1.1"
}
settings["tlsMinVersion"] = tlsMinVersion
settings["tlsCipherSuitesOn"] = tlsCipherSuitesOn
settings["tlsOcspOn"] = tlsOcspOn
settings["tlsClientAuthType"] = int(tlsClientAuthType)
settings["lastModifiedAt"] = nowDateTime()
settings["certUpdatedAt"] = nowDateTime()
saveClusterSettings(params.ClusterId, settings)
currentDefaultClusterId := policies.LoadDefaultClusterID()
if params.IsDefaultCluster {
policies.SaveDefaultClusterID(params.ClusterId)
} else if currentDefaultClusterId == params.ClusterId {
policies.SaveDefaultClusterID(0)
_, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(this.AdminContext(), &pb.UpdateHTTPDNSClusterRequest{
ClusterId: params.ClusterId,
Name: params.Name,
ServiceDomain: params.GatewayDomain,
DefaultTTL: params.CacheTtl,
FallbackTimeoutMs: params.FallbackTimeout,
InstallDir: params.InstallDir,
TlsPolicyJSON: tlsPolicyJSON,
IsOn: params.IsOn,
IsDefault: params.IsDefaultCluster,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()

View File

@@ -1,110 +0,0 @@
package clusters
import (
"strings"
"sync"
"time"
"github.com/iwind/TeaGo/maps"
)
var clusterSettingsStore = struct {
sync.RWMutex
data map[int64]maps.Map
}{
data: map[int64]maps.Map{},
}
func defaultClusterSettings(cluster maps.Map) maps.Map {
installDir := strings.TrimSpace(cluster.GetString("installDir"))
if len(installDir) == 0 {
installDir = "/opt/edge-httpdns"
}
return maps.Map{
"name": cluster.GetString("name"),
"gatewayDomain": strings.TrimSpace(cluster.GetString("gatewayDomain")),
"cacheTtl": cluster.GetInt("cacheTtl"),
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
"installDir": installDir,
"isOn": cluster.GetBool("isOn"),
"originHttps": true,
"originCertPem": "",
"originKeyPem": "",
"tlsMinVersion": "TLS 1.1",
"tlsCipherSuitesOn": false,
"tlsOcspOn": false,
"tlsClientAuthType": 0,
"certUpdatedAt": "",
"lastModifiedAt": "",
}
}
func cloneClusterSettings(settings maps.Map) maps.Map {
return maps.Map{
"name": settings.GetString("name"),
"gatewayDomain": settings.GetString("gatewayDomain"),
"cacheTtl": settings.GetInt("cacheTtl"),
"fallbackTimeout": settings.GetInt("fallbackTimeout"),
"installDir": settings.GetString("installDir"),
"isOn": settings.GetBool("isOn"),
"originHttps": true,
"originCertPem": settings.GetString("originCertPem"),
"originKeyPem": settings.GetString("originKeyPem"),
"tlsMinVersion": settings.GetString("tlsMinVersion"),
"tlsCipherSuitesOn": settings.GetBool("tlsCipherSuitesOn"),
"tlsOcspOn": settings.GetBool("tlsOcspOn"),
"tlsClientAuthType": settings.GetInt("tlsClientAuthType"),
"certUpdatedAt": settings.GetString("certUpdatedAt"),
"lastModifiedAt": settings.GetString("lastModifiedAt"),
}
}
func loadClusterSettings(cluster maps.Map) maps.Map {
clusterId := cluster.GetInt64("id")
clusterSettingsStore.RLock()
settings, ok := clusterSettingsStore.data[clusterId]
clusterSettingsStore.RUnlock()
if ok {
if len(settings.GetString("tlsMinVersion")) == 0 {
settings["tlsMinVersion"] = "TLS 1.1"
}
return cloneClusterSettings(settings)
}
settings = defaultClusterSettings(cluster)
saveClusterSettings(clusterId, settings)
return cloneClusterSettings(settings)
}
func saveClusterSettings(clusterId int64, settings maps.Map) {
clusterSettingsStore.Lock()
clusterSettingsStore.data[clusterId] = cloneClusterSettings(settings)
clusterSettingsStore.Unlock()
}
func applyClusterSettingsOverrides(cluster maps.Map) {
clusterId := cluster.GetInt64("id")
if clusterId <= 0 {
return
}
clusterSettingsStore.RLock()
settings, ok := clusterSettingsStore.data[clusterId]
clusterSettingsStore.RUnlock()
if !ok {
return
}
cluster["name"] = settings.GetString("name")
cluster["gatewayDomain"] = settings.GetString("gatewayDomain")
cluster["cacheTtl"] = settings.GetInt("cacheTtl")
cluster["fallbackTimeout"] = settings.GetInt("fallbackTimeout")
cluster["installDir"] = settings.GetString("installDir")
cluster["isOn"] = settings.GetBool("isOn")
}
func nowDateTime() string {
return time.Now().Format("2006-01-02 15:04:05")
}

View File

@@ -1,8 +1,12 @@
package clusters
import (
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreateAction struct {
@@ -17,3 +21,48 @@ func (this *CreateAction) RunGet(params struct{}) {
httpdnsutils.AddLeftMenu(this.Parent())
this.Show()
}
func (this *CreateAction) RunPost(params struct {
Name string
GatewayDomain string
CacheTtl int32
FallbackTimeout int32
InstallDir string
IsOn bool
IsDefault bool
Must *actions.Must
}) {
params.Name = strings.TrimSpace(params.Name)
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
params.InstallDir = strings.TrimSpace(params.InstallDir)
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
}
if params.CacheTtl <= 0 {
params.CacheTtl = 30
}
if params.FallbackTimeout <= 0 {
params.FallbackTimeout = 300
}
params.Must.Field("name", params.Name).Require("请输入集群名称")
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
resp, err := this.RPC().HTTPDNSClusterRPC().CreateHTTPDNSCluster(this.AdminContext(), &pb.CreateHTTPDNSClusterRequest{
Name: params.Name,
ServiceDomain: params.GatewayDomain,
DefaultTTL: params.CacheTtl,
FallbackTimeoutMs: params.FallbackTimeout,
InstallDir: params.InstallDir,
IsOn: params.IsOn,
IsDefault: params.IsDefault,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = resp.GetClusterId()
this.Success()
}

View File

@@ -1,8 +1,11 @@
package clusters
import (
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/iwind/TeaGo/actions"
)
type CreateNodeAction struct {
@@ -13,13 +16,18 @@ func (this *CreateNodeAction) Init() {
this.Nav("httpdns", "cluster", "createNode")
}
func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
func (this *CreateNodeAction) RunGet(params struct {
ClusterId int64
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
this.Data["clusterId"] = params.ClusterId
this.Data["cluster"] = cluster
this.Show()
@@ -28,6 +36,28 @@ func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
func (this *CreateNodeAction) RunPost(params struct {
ClusterId int64
Name string
InstallDir string
Must *actions.Must
}) {
params.Name = strings.TrimSpace(params.Name)
params.InstallDir = strings.TrimSpace(params.InstallDir)
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
params.Must.Field("name", params.Name).Require("请输入节点名称")
if len(params.InstallDir) == 0 {
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err == nil {
params.InstallDir = strings.TrimSpace(cluster.GetString("installDir"))
}
if len(params.InstallDir) == 0 {
params.InstallDir = "/opt/edge-httpdns"
}
}
if err := createNode(this.Parent(), params.ClusterId, params.Name, params.InstallDir); err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -3,6 +3,7 @@ package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
@@ -17,11 +18,14 @@ func (this *DeleteAction) RunGet(params struct {
ClusterId int64
}) {
httpdnsutils.AddLeftMenu(this.Parent())
cluster := pickCluster(params.ClusterId)
// 构建顶部 tabbar
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "delete")
this.Data["cluster"] = cluster
this.Show()
}
@@ -29,6 +33,12 @@ func (this *DeleteAction) RunGet(params struct {
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
}) {
_ = params.ClusterId
_, err := this.RPC().HTTPDNSClusterRPC().DeleteHTTPDNSCluster(this.AdminContext(), &pb.DeleteHTTPDNSClusterRequest{
ClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,6 +1,18 @@
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type DeleteNodeAction struct { actionutils.ParentAction }
func (this *DeleteNodeAction) RunPost(params struct{ ClusterId int64; NodeId int64 }) { this.Success() }
package clusters
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type DeleteNodeAction struct {
actionutils.ParentAction
}
func (this *DeleteNodeAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
if err := deleteNode(this.Parent(), params.NodeId); err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -13,8 +13,27 @@ func (this *IndexAction) Init() {
this.Nav("httpdns", "cluster", "")
}
func (this *IndexAction) RunGet(params struct{}) {
func (this *IndexAction) RunGet(params struct {
Keyword string
}) {
httpdnsutils.AddLeftMenu(this.Parent())
this.Data["clusters"] = mockClusters()
this.Data["keyword"] = params.Keyword
clusters, err := listClusterMaps(this.Parent(), params.Keyword)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusters"] = clusters
hasErrorLogs := false
for _, cluster := range clusters {
if cluster.GetInt("countAllNodes") > cluster.GetInt("countActiveNodes") {
hasErrorLogs = true
break
}
}
this.Data["hasErrorLogs"] = hasErrorLogs
this.Data["page"] = ""
this.Show()
}

View File

@@ -15,7 +15,7 @@ func init() {
Data("teaSubMenu", "cluster").
Prefix("/httpdns/clusters").
Get("", new(IndexAction)).
Get("/create", new(CreateAction)).
GetPost("/create", new(CreateAction)).
Get("/cluster", new(ClusterAction)).
GetPost("/cluster/settings", new(ClusterSettingsAction)).
GetPost("/delete", new(DeleteAction)).

View File

@@ -1,132 +0,0 @@
package clusters
import "github.com/iwind/TeaGo/maps"
func mockClusters() []maps.Map {
clusters := []maps.Map{
{
"id": int64(1),
"name": "gateway-cn-hz",
"region": "cn-hangzhou",
"gatewayDomain": "gw-hz.httpdns.example.com",
"installDir": "/opt/edge-httpdns",
"countAllNodes": 3,
"countActiveNodes": 3,
"countApps": 5,
"cacheTtl": 30,
"fallbackTimeout": 300,
"isOn": true,
},
{
"id": int64(2),
"name": "gateway-cn-bj",
"region": "cn-beijing",
"gatewayDomain": "gw-bj.httpdns.example.com",
"installDir": "/opt/edge-httpdns",
"countAllNodes": 3,
"countActiveNodes": 2,
"countApps": 2,
"cacheTtl": 30,
"fallbackTimeout": 300,
"isOn": true,
},
}
for _, cluster := range clusters {
applyClusterSettingsOverrides(cluster)
}
return clusters
}
func pickCluster(clusterId int64) maps.Map {
clusters := mockClusters()
if clusterId <= 0 {
return clusters[0]
}
for _, c := range clusters {
if c.GetInt64("id") == clusterId {
return c
}
}
return clusters[0]
}
func mockNodes(clusterId int64, installState int, activeState int, keyword string) []maps.Map {
_ = clusterId
return []maps.Map{
{
"id": int64(101),
"name": "45.250.184.56",
"isInstalled": true,
"isOn": true,
"isUp": true,
"installStatus": maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
},
"status": maps.Map{
"isActive": true,
"updatedAt": 1700000000,
"hostname": "node-01",
"cpuUsage": 0.0253,
"cpuUsageText": "2.53%",
"memUsage": 0.5972,
"memUsageText": "59.72%",
"load1m": 0.02,
},
"ipAddresses": []maps.Map{
{
"id": 1,
"name": "",
"ip": "45.250.184.56",
"canAccess": true,
},
},
},
{
"id": int64(102),
"name": "45.250.184.53",
"isInstalled": true,
"isOn": true,
"isUp": true,
"installStatus": maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
},
"status": maps.Map{
"isActive": true,
"updatedAt": 1700000000,
"hostname": "node-02",
"cpuUsage": 0.0039,
"cpuUsageText": "0.39%",
"memUsage": 0.0355,
"memUsageText": "3.55%",
"load1m": 0.0,
},
"ipAddresses": []maps.Map{
{
"id": 2,
"name": "",
"ip": "45.250.184.53",
"canAccess": true,
},
},
},
}
}
func mockCerts() []maps.Map {
return []maps.Map{
{
"id": int64(11),
"domain": "resolve.edge.example.com",
"issuer": "Mock CA",
"expiresAt": "2026-12-31 23:59:59",
},
}
}

View File

@@ -0,0 +1,248 @@
package clusters
import (
"encoding/json"
"fmt"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
resp, err := parent.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(parent.AdminContext(), &pb.ListHTTPDNSClustersRequest{
Offset: 0,
Size: 10_000,
Keyword: strings.TrimSpace(keyword),
})
if err != nil {
return nil, err
}
result := make([]maps.Map, 0, len(resp.GetClusters()))
for _, cluster := range resp.GetClusters() {
nodesResp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{
ClusterId: cluster.GetId(),
})
if err != nil {
return nil, err
}
countAllNodes := len(nodesResp.GetNodes())
countActiveNodes := 0
for _, node := range nodesResp.GetNodes() {
if node.GetIsOn() && node.GetIsUp() && node.GetIsActive() {
countActiveNodes++
}
}
result = append(result, maps.Map{
"id": cluster.GetId(),
"name": cluster.GetName(),
"gatewayDomain": cluster.GetServiceDomain(),
"defaultTTL": cluster.GetDefaultTTL(),
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
"installDir": cluster.GetInstallDir(),
"isOn": cluster.GetIsOn(),
"isDefault": cluster.GetIsDefault(),
"countAllNodes": countAllNodes,
"countActiveNodes": countActiveNodes,
})
}
return result, nil
}
func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
if clusterID > 0 {
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
ClusterId: clusterID,
})
if err != nil {
return nil, err
}
if resp.GetCluster() != nil {
cluster := resp.GetCluster()
return maps.Map{
"id": cluster.GetId(),
"name": cluster.GetName(),
"gatewayDomain": cluster.GetServiceDomain(),
"defaultTTL": cluster.GetDefaultTTL(),
"fallbackTimeout": cluster.GetFallbackTimeoutMs(),
"installDir": cluster.GetInstallDir(),
"isOn": cluster.GetIsOn(),
"isDefault": cluster.GetIsDefault(),
"tlsPolicyJSON": cluster.GetTlsPolicyJSON(),
}, nil
}
}
clusters, err := listClusterMaps(parent, "")
if err != nil {
return nil, err
}
if len(clusters) == 0 {
return maps.Map{
"id": int64(0),
"name": "",
"gatewayDomain": "",
"defaultTTL": 30,
"fallbackTimeout": 300,
"installDir": "/opt/edge-httpdns",
}, nil
}
return clusters[0], nil
}
func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map, error) {
resp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{
ClusterId: clusterID,
})
if err != nil {
return nil, err
}
result := make([]maps.Map, 0, len(resp.GetNodes()))
for _, node := range resp.GetNodes() {
statusMap := decodeNodeStatus(node.GetStatusJSON())
installStatusMap := decodeInstallStatus(node.GetInstallStatusJSON())
ip := node.GetName()
if parsed := strings.TrimSpace(statusMap.GetString("hostIP")); len(parsed) > 0 {
ip = parsed
}
nodeMap := maps.Map{
"id": node.GetId(),
"clusterId": node.GetClusterId(),
"name": node.GetName(),
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
"isInstalled": node.GetIsInstalled(),
"isActive": node.GetIsActive(),
"installDir": node.GetInstallDir(),
"uniqueId": node.GetUniqueId(),
"secret": node.GetSecret(),
"status": statusMap,
"installStatus": installStatusMap,
"region": nil,
"login": nil,
"apiNodeAddrs": []string{},
"cluster": maps.Map{
"id": node.GetClusterId(),
"installDir": node.GetInstallDir(),
},
"ipAddresses": []maps.Map{
{
"id": node.GetId(),
"name": "Public IP",
"ip": ip,
"canAccess": true,
"isOn": node.GetIsOn(),
"isUp": node.GetIsUp(),
},
},
}
result = append(result, nodeMap)
}
return result, nil
}
func findNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Map, error) {
resp, err := parent.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(parent.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: nodeID,
})
if err != nil {
return nil, err
}
if resp.GetNode() == nil {
return maps.Map{}, nil
}
nodes, err := listNodeMaps(parent, resp.GetNode().GetClusterId())
if err != nil {
return nil, err
}
for _, node := range nodes {
if node.GetInt64("id") == nodeID {
return node, nil
}
}
return maps.Map{
"id": resp.GetNode().GetId(),
"name": resp.GetNode().GetName(),
}, nil
}
func createNode(parent *actionutils.ParentAction, clusterID int64, name string, installDir string) error {
_, err := parent.RPC().HTTPDNSNodeRPC().CreateHTTPDNSNode(parent.AdminContext(), &pb.CreateHTTPDNSNodeRequest{
ClusterId: clusterID,
Name: strings.TrimSpace(name),
InstallDir: strings.TrimSpace(installDir),
IsOn: true,
})
return err
}
func updateNode(parent *actionutils.ParentAction, nodeID int64, name string, installDir string, isOn bool) error {
_, err := parent.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(parent.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{
NodeId: nodeID,
Name: strings.TrimSpace(name),
InstallDir: strings.TrimSpace(installDir),
IsOn: isOn,
})
return err
}
func deleteNode(parent *actionutils.ParentAction, nodeID int64) error {
_, err := parent.RPC().HTTPDNSNodeRPC().DeleteHTTPDNSNode(parent.AdminContext(), &pb.DeleteHTTPDNSNodeRequest{
NodeId: nodeID,
})
return err
}
func decodeNodeStatus(raw []byte) maps.Map {
status := &nodeconfigs.NodeStatus{}
if len(raw) > 0 {
_ = json.Unmarshal(raw, status)
}
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
return maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"hostIP": status.HostIP,
"cpuUsage": status.CPUUsage,
"cpuUsageText": cpuText,
"memUsage": status.MemoryUsage,
"memUsageText": memText,
"load1m": status.Load1m,
"load5m": status.Load5m,
"load15m": status.Load15m,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"exePath": status.ExePath,
}
}
func decodeInstallStatus(raw []byte) maps.Map {
if len(raw) == 0 {
return maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
"errorCode": "",
}
}
result := maps.Map{}
if err := json.Unmarshal(raw, &result); err != nil {
return maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": true,
"error": "",
"errorCode": "",
}
}
return result
}

View File

@@ -1,7 +1,14 @@
package clusters
import (
"encoding/json"
"net"
"regexp"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
@@ -12,18 +19,52 @@ type UpdateNodeSSHAction struct {
func (this *UpdateNodeSSHAction) RunGet(params struct {
NodeId int64
}) {
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
clusterId := int64(0)
nodeName := ""
if resp.GetNode() != nil {
clusterId = resp.GetNode().GetClusterId()
nodeName = resp.GetNode().GetName()
}
this.Data["nodeId"] = params.NodeId
this.Data["clusterId"] = 0
this.Data["clusterId"] = clusterId
this.Data["node"] = maps.Map{
"id": params.NodeId,
"name": "Mock Node",
"name": nodeName,
}
this.Data["loginId"] = 0
this.Data["params"] = maps.Map{
"host": "1.2.3.4",
loginParams := maps.Map{
"host": "",
"port": 22,
"grantId": 0,
}
this.Data["loginId"] = 0
if resp.GetNode() != nil && len(resp.GetNode().GetInstallStatusJSON()) > 0 {
installStatus := maps.Map{}
_ = json.Unmarshal(resp.GetNode().GetInstallStatusJSON(), &installStatus)
sshInfo := installStatus.GetMap("ssh")
if sshInfo != nil {
if host := strings.TrimSpace(sshInfo.GetString("host")); len(host) > 0 {
loginParams["host"] = host
}
if port := sshInfo.GetInt("port"); port > 0 {
loginParams["port"] = port
}
if grantID := sshInfo.GetInt64("grantId"); grantID > 0 {
loginParams["grantId"] = grantID
}
}
}
this.Data["params"] = loginParams
this.Data["grant"] = nil
this.Show()
}
@@ -34,6 +75,66 @@ func (this *UpdateNodeSSHAction) RunPost(params struct {
SshHost string
SshPort int
GrantId int64
Must *actions.Must
}) {
params.SshHost = strings.TrimSpace(params.SshHost)
params.Must.
Field("sshHost", params.SshHost).
Require("请输入 SSH 主机地址").
Field("sshPort", params.SshPort).
Gt(0, "SSH 端口必须大于 0").
Lt(65535, "SSH 端口必须小于 65535")
if params.GrantId <= 0 {
this.Fail("请选择节点登录认证信息")
}
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
this.Fail("SSH 主机地址 IP 格式错误")
}
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := resp.GetNode()
if node == nil {
this.Fail("节点不存在")
return
}
installStatus := maps.Map{
"isRunning": false,
"isFinished": true,
"isOk": node.GetIsInstalled(),
"error": "",
"errorCode": "",
}
if len(node.GetInstallStatusJSON()) > 0 {
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
}
installStatus["ssh"] = maps.Map{
"host": params.SshHost,
"port": params.SshPort,
"grantId": params.GrantId,
}
installStatusJSON, _ := json.Marshal(installStatus)
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: params.NodeId,
IsUp: node.GetIsUp(),
IsInstalled: node.GetIsInstalled(),
IsActive: node.GetIsActive(),
StatusJSON: node.GetStatusJSON(),
InstallStatusJSON: installStatusJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}