This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type DomainStatsAction struct {
actionutils.ParentAction
}
func (this *DomainStatsAction) RunPost(params struct {
ClusterId int64
}) {
var hourFrom = timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
var hourTo = timeutil.Format("YmdH")
resp, err := this.RPC().ServerDomainHourlyStatRPC().ListTopServerDomainStatsWithServerId(this.AdminContext(), &pb.ListTopServerDomainStatsWithServerIdRequest{
NodeClusterId: params.ClusterId,
HourFrom: hourFrom,
HourTo: hourTo,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.DomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
})
}
this.Data["topDomainStats"] = statMaps
}
this.Success()
}

View File

@@ -0,0 +1,200 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "board", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
}) {
if !teaconst.IsPlus {
this.Fail("only for commercial users")
}
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeClusterBoard(this.AdminContext(), &pb.ComposeServerStatNodeClusterBoardRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countUsers": resp.CountUsers,
"countActiveNodes": resp.CountActiveNodes,
"countInactiveNodes": resp.CountInactiveNodes,
"countServers": resp.CountServers,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// 当月流量
this.Data["monthlyTraffic"] = numberutils.FormatBytes(resp.MonthlyTrafficBytes)
// 今日流量
this.Data["todayTraffic"] = numberutils.FormatBytes(resp.DailyTrafficBytes)
// 昨日流量
this.Data["yesterdayTraffic"] = numberutils.FormatBytes(resp.LastDailyTrafficBytes)
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNodeStats {
statMaps = append(statMaps, maps.Map{
"nodeId": stat.NodeId,
"nodeName": stat.NodeName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// CPU
{
var statMaps = []maps.Map{}
for _, stat := range resp.CpuNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["cpuValues"] = statMaps
}
// Memory
{
var statMaps = []maps.Map{}
for _, stat := range resp.MemoryNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["memoryValues"] = statMaps
}
// Load
{
var statMaps = []maps.Map{}
for _, stat := range resp.LoadNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("load1m"),
})
}
this.Data["loadValues"] = statMaps
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Success()
}

View File

@@ -0,0 +1,121 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"net"
"strconv"
"strings"
)
type CreateBatchAction struct {
actionutils.ParentAction
}
func (this *CreateBatchAction) Init() {
this.Nav("", "node", "create")
this.SecondMenu("nodes")
}
func (this *CreateBatchAction) RunGet(params struct {
ClusterId int64
}) {
leftMenuItems := []maps.Map{
{
"name": this.Lang(codes.NodeMenu_CreateSingleNode),
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": false,
},
{
"name": this.Lang(codes.NodeMenu_CreateMultipleNodes),
"url": "/clusters/cluster/createBatch?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": true,
},
}
this.Data["leftMenuItems"] = leftMenuItems
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}
func (this *CreateBatchAction) RunPost(params struct {
ClusterId int64
GroupId int64
RegionId int64
IpList string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
if params.ClusterId <= 0 {
this.Fail("请选择正确的集群")
}
// 校验
// TODO 支持IP范围比如192.168.1.[100-105]
realIPList := []string{}
for _, ip := range strings.Split(params.IpList, "\n") {
ip = strings.TrimSpace(ip)
if len(ip) == 0 {
continue
}
ip = strings.ReplaceAll(ip, " ", "")
if net.ParseIP(ip) == nil {
this.Fail("发现错误的IP地址" + ip)
}
if lists.ContainsString(realIPList, ip) {
continue
}
realIPList = append(realIPList, ip)
}
// 保存
for _, ip := range realIPList {
resp, err := this.RPC().NodeRPC().CreateNode(this.AdminContext(), &pb.CreateNodeRequest{
Name: ip,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
NodeLogin: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
nodeId := resp.NodeId
_, err = this.RPC().NodeIPAddressRPC().CreateNodeIPAddress(this.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: "IP地址",
Ip: ip,
CanAccess: true,
IsUp: true,
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogCreateNodeBatch)
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateBatchAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package cluster
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
func (this *CreateBatchAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
quotaResp, err := this.RPC().AuthorityKeyRPC().FindAuthorityQuota(this.AdminContext(), &pb.FindAuthorityQuotaRequest{})
if err != nil {
return 0, 0, err
}
leftNodes = quotaResp.MaxNodes - quotaResp.CountNodes
if leftNodes < 0 {
leftNodes = 0
}
return quotaResp.MaxNodes, leftNodes, nil
}

View File

@@ -0,0 +1,346 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
"regexp"
"strconv"
"strings"
)
// CreateNodeAction 创建节点
type CreateNodeAction struct {
actionutils.ParentAction
}
func (this *CreateNodeAction) Init() {
this.Nav("", "node", "create")
this.SecondMenu("nodes")
}
func (this *CreateNodeAction) RunGet(params struct {
ClusterId int64
}) {
if params.ClusterId <= 0 {
this.RedirectURL("/clusters")
return
}
var leftMenuItems = []maps.Map{
{
"name": this.Lang(codes.NodeMenu_CreateSingleNode),
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": true,
},
{
"name": this.Lang(codes.NodeMenu_CreateMultipleNodes),
"url": "/clusters/cluster/createBatch?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": false,
},
}
this.Data["leftMenuItems"] = leftMenuItems
// DNS线路
clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var dnsRouteMaps = []maps.Map{}
this.Data["dnsDomainId"] = 0
if clusterDNSResp.Domain != nil {
domainId := clusterDNSResp.Domain.Id
this.Data["dnsDomainId"] = domainId
if domainId > 0 {
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: domainId})
if err != nil {
this.ErrorPage(err)
return
}
for _, route := range routesResp.Routes {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"domainId": domainId,
"domainName": clusterDNSResp.Domain.Name,
"name": route.Name,
"code": route.Code,
})
}
}
}
this.Data["dnsRoutes"] = dnsRouteMaps
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodes = apiNodesResp.ApiNodes
var apiEndpoints = []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
// 安装文件下载
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}
func (this *CreateNodeAction) RunPost(params struct {
Name string
IpAddressesJSON []byte
ClusterId int64
GroupId int64
RegionId int64
GrantId int64
SshHost string
SshPort int
DnsDomainId int64
DnsRoutesJSON []byte
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入节点名称")
if len(params.IpAddressesJSON) == 0 {
this.Fail("请至少添加一个IP地址")
}
// TODO 检查cluster
if params.ClusterId <= 0 {
this.Fail("请选择所在集群")
}
// IP地址
var ipAddresses = []maps.Map{}
if len(params.IpAddressesJSON) > 0 {
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(ipAddresses) == 0 {
// 检查Name中是否包含IP
var ipv4Reg = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)
var ipMatches = ipv4Reg.FindStringSubmatch(params.Name)
if len(ipMatches) > 0 {
var nodeIP = ipMatches[0]
if net.ParseIP(nodeIP) != nil {
ipAddresses = []maps.Map{
{
"ip": nodeIP,
"canAccess": true,
"isOn": true,
"isUp": true,
},
}
}
}
if len(ipAddresses) == 0 {
this.Fail("请至少输入一个IP地址")
}
}
var dnsRouteCodes = []string{}
if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &dnsRouteCodes)
if err != nil {
this.ErrorPage(err)
return
}
}
// TODO 检查登录授权
var loginInfo = &pb.NodeLogin{
Id: 0,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
}
// 保存
createResp, err := this.RPC().NodeRPC().CreateNode(this.AdminContext(), &pb.CreateNodeRequest{
Name: params.Name,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
NodeLogin: loginInfo,
DnsDomainId: params.DnsDomainId,
DnsRoutes: dnsRouteCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
var nodeId = createResp.NodeId
// IP地址
var resultIPAddresses = []string{}
for _, addr := range ipAddresses {
var resultAddrIds = []int64{}
addrId := addr.GetInt64("id")
if addrId > 0 {
resultAddrIds = append(resultAddrIds, addrId)
_, err = this.RPC().NodeIPAddressRPC().UpdateNodeIPAddressNodeId(this.AdminContext(), &pb.UpdateNodeIPAddressNodeIdRequest{
NodeIPAddressId: addrId,
NodeId: nodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
resultIPAddresses = append(resultIPAddresses, addr.GetString("ip"))
} else {
var ipStrings = addr.GetString("ip")
result, err := utils.ExtractIP(ipStrings)
if err != nil {
this.Fail("节点创建成功但是保存IP失败" + err.Error())
}
resultIPAddresses = append(resultIPAddresses, result...)
if len(result) == 1 {
// 单个创建
createResp, err := this.RPC().NodeIPAddressRPC().CreateNodeIPAddress(this.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: addr.GetString("name"),
Ip: result[0],
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
})
if err != nil {
this.ErrorPage(err)
return
}
addrId = createResp.NodeIPAddressId
resultAddrIds = append(resultAddrIds, addrId)
} else if len(result) > 1 {
// 批量创建
createResp, err := this.RPC().NodeIPAddressRPC().CreateNodeIPAddresses(this.AdminContext(), &pb.CreateNodeIPAddressesRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: addr.GetString("name"),
IpList: result,
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
GroupValue: ipStrings,
})
if err != nil {
this.ErrorPage(err)
return
}
resultAddrIds = append(resultAddrIds, createResp.NodeIPAddressIds...)
}
}
// 阈值
var thresholds = addr.GetSlice("thresholds")
if len(thresholds) > 0 {
thresholdsJSON, err := json.Marshal(thresholds)
if err != nil {
this.ErrorPage(err)
return
}
for _, addrId := range resultAddrIds {
_, err = this.RPC().NodeIPAddressThresholdRPC().UpdateAllNodeIPAddressThresholds(this.AdminContext(), &pb.UpdateAllNodeIPAddressThresholdsRequest{
NodeIPAddressId: addrId,
NodeIPAddressThresholdsJSON: thresholdsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
}
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogCreateNode, nodeId)
// 响应数据
this.Data["nodeId"] = nodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
this.ErrorPage(err)
return
}
if nodeResp.Node != nil {
var grantMap maps.Map = nil
grantId := params.GrantId
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil && grantResp.NodeGrant.Id > 0 {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method, this.LangCode()),
"username": grantResp.NodeGrant.Username,
}
}
}
this.Data["node"] = maps.Map{
"id": nodeResp.Node.Id,
"name": nodeResp.Node.Name,
"uniqueId": nodeResp.Node.UniqueId,
"secret": nodeResp.Node.Secret,
"addresses": resultIPAddresses,
"login": maps.Map{
"id": 0,
"name": "SSH",
"type": "ssh",
"params": maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
},
},
"grant": grantMap,
}
}
this.Success()
}

View File

@@ -0,0 +1,77 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreateNodeInstallAction struct {
actionutils.ParentAction
}
func (this *CreateNodeInstallAction) RunPost(params struct {
NodeId int64
SshHost string
SshPort int
GrantId int64
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.NodeSSH_LogUpdateNodeSSH, params.NodeId)
params.Must.
Field("sshHost2", params.SshHost).
Require("请填写SSH主机地址").
Field("sshPort2", params.SshPort).
Gt(0, "请填写SSH主机端口").
Lt(65535, "SSH主机端口需要小于65535").
Field("grantId", params.GrantId).
Gt(0, "请选择SSH登录认证")
// 查询login
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.Fail("找不到要修改的节点")
}
var loginId int64
if node.NodeLogin != nil {
loginId = node.NodeLogin.Id
}
// 修改节点信息
_, err = this.RPC().NodeRPC().UpdateNodeLogin(this.AdminContext(), &pb.UpdateNodeLoginRequest{
NodeId: params.NodeId,
NodeLogin: &pb.NodeLogin{
Id: loginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
},
})
if err != nil {
this.ErrorPage(err)
return
}
// 开始安装
_, err = this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.Fail("安装失败:" + err.Error())
}
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateNodeAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package cluster
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
func (this *CreateNodeAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
quotaResp, err := this.RPC().AuthorityKeyRPC().FindAuthorityQuota(this.AdminContext(), &pb.FindAuthorityQuotaRequest{})
if err != nil {
return 0, 0, err
}
leftNodes = quotaResp.MaxNodes - quotaResp.CountNodes
if leftNodes < 0 {
leftNodes = 0
}
return quotaResp.MaxNodes, leftNodes, nil
}

View File

@@ -0,0 +1,46 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) Init() {
this.Nav("", "delete", "index")
this.SecondMenu("nodes")
}
func (this *DeleteAction) RunGet(params struct{}) {
this.Show()
}
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
}) {
// 检查有无服务正在使用
countResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("有代理服务正在使用此集群,请修改这些代理服务后再删除")
}
// 删除
_, err = this.RPC().NodeClusterRPC().DeleteNodeCluster(this.AdminContext(), &pb.DeleteNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeCluster_LogDeleteCluster, params.ClusterId)
this.Success()
}

View File

@@ -0,0 +1,70 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"os"
"regexp"
)
type DownloadInstallerAction struct {
actionutils.ParentAction
}
func (this *DownloadInstallerAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadInstallerAction) RunGet(params struct {
Name string
}) {
if len(params.Name) == 0 {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
// 检查文件名
// 以防止路径穿越等风险
if !regexp.MustCompile(`^[a-zA-Z0-9.-]+$`).MatchString(params.Name) {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
var zipFile = Tea.Root + "/edge-api/deploy/" + params.Name
fp, err := os.OpenFile(zipFile, os.O_RDWR, 0444)
if err != nil {
if os.IsNotExist(err) {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.WriteString("file can not be opened")
return
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.WriteString("file can not be opened")
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\""+params.Name+"\";")
this.AddHeader("Content-Type", "application/zip")
this.AddHeader("Content-Length", types.String(stat.Size()))
_, _ = io.Copy(this.ResponseWriter, fp)
}

View File

@@ -0,0 +1,54 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
Name string
Must *actions.Must
}) {
if params.ClusterId <= 0 {
this.Fail("请选择集群")
}
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
createResp, err := this.RPC().NodeGroupRPC().CreateNodeGroup(this.AdminContext(), &pb.CreateNodeGroupRequest{
NodeClusterId: params.ClusterId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["group"] = maps.Map{
"id": createResp.NodeGroupId,
"name": params.Name,
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogCreateNodeGroup, createResp.NodeGroupId)
this.Success()
}

View File

@@ -0,0 +1,37 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
GroupId int64
}) {
// 检查是否正在使用
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("此分组正在被使用不能删除,请修改节点后再删除")
}
_, err = this.RPC().NodeGroupRPC().DeleteNodeGroup(this.AdminContext(), &pb.DeleteNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogDeleteNodeGroup, params.GroupId)
this.Success()
}

View File

@@ -0,0 +1,47 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "group")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.NodeGroups {
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countResp.Count
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"countNodes": countNodes,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,64 @@
package groups
import (
"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"
)
type SelectPopupAction struct {
actionutils.ParentAction
}
func (this *SelectPopupAction) Init() {
this.Nav("", "", "")
}
func (this *SelectPopupAction) RunGet(params struct {
ClusterId int64
}) {
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.NodeGroups {
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
})
}
this.Data["groups"] = groupMaps
this.Show()
}
func (this *SelectPopupAction) RunPost(params struct {
GroupId int64
Must *actions.Must
}) {
if params.GroupId <= 0 {
this.Fail("请选择要使用的分组")
}
groupResp, err := this.RPC().NodeGroupRPC().FindEnabledNodeGroup(this.AdminContext(), &pb.FindEnabledNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.NodeGroup
if group == nil {
this.NotFound("nodeGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
}
this.Success()
}

View File

@@ -0,0 +1,26 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SortAction struct {
actionutils.ParentAction
}
func (this *SortAction) RunPost(params struct {
GroupIds []int64
}) {
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroupOrders(this.AdminContext(), &pb.UpdateNodeGroupOrdersRequest{NodeGroupIds: params.GroupIds})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogSortNodeGroups)
this.Success()
}

View File

@@ -0,0 +1,63 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().NodeGroupRPC().FindEnabledNodeGroup(this.AdminContext(), &pb.FindEnabledNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.NodeGroup
if group == nil {
this.NotFound("nodeGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroup(this.AdminContext(), &pb.UpdateNodeGroupRequest{
NodeGroupId: params.GroupId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogUpdateNodeGroup, params.GroupId)
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
if teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/boards?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
} else {
this.RedirectURL("/clusters/cluster/nodes?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
}
}

View File

@@ -0,0 +1,75 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/cache"
ddosProtection "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/ddos-protection"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/dns"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/ssh"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/system"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Data("teaMenu", "clusters").
Data("teaSubMenu", "cluster").
Prefix("/clusters/cluster").
Get("", new(IndexAction)).
Get("/nodes", new(NodesAction)).
GetPost("/installNodes", new(InstallNodesAction)).
GetPost("/installRemote", new(InstallRemoteAction)).
Post("/installStatus", new(InstallStatusAction)).
GetPost("/upgradeRemote", new(UpgradeRemoteAction)).
Post("/upgradeStatus", new(UpgradeStatusAction)).
GetPost("/delete", new(DeleteAction)).
GetPost("/createNode", new(CreateNodeAction)).
Post("/createNodeInstall", new(CreateNodeInstallAction)).
GetPost("/createBatch", new(CreateBatchAction)).
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
GetPost("/installManual", new(InstallManualAction)).
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
Get("/downloadInstaller", new(DownloadInstallerAction)).
// 节点相关
Prefix("/clusters/cluster/node").
Get("", new(node.IndexAction)).
GetPost("/update", new(node.UpdateAction)).
GetPost("/install", new(node.InstallAction)).
Post("/updateInstallStatus", new(node.UpdateInstallStatusAction)).
Post("/status", new(node.StatusAction)).
Get("/logs", new(node.LogsAction)).
Post("/start", new(node.StartAction)).
Post("/stop", new(node.StopAction)).
Post("/uninstall", new(node.UninstallAction)).
Post("/up", new(node.UpAction)).
Post("/updateIsOn", new(node.UpdateIsOnAction)).
Get("/detail", new(node.DetailAction)).
GetPost("/updateDNSPopup", new(node.UpdateDNSPopupAction)).
Post("/syncDomain", new(node.SyncDomainAction)).
GetPost("/settings/cache", new(cache.IndexAction)).
GetPost("/settings/dns", new(dns.IndexAction)).
GetPost("/settings/system", new(system.IndexAction)).
GetPost("/settings/ssh", new(ssh.IndexAction)).
GetPost("/settings/ssh/test", new(ssh.TestAction)).
GetPost("/settings/ddos-protection", new(ddosProtection.IndexAction)).
Post("/settings/ddos-protection/status", new(ddosProtection.StatusAction)).
// 分组相关
Prefix("/clusters/cluster/groups").
Get("", new(groups.IndexAction)).
GetPost("/createPopup", new(groups.CreatePopupAction)).
GetPost("/updatePopup", new(groups.UpdatePopupAction)).
Post("/delete", new(groups.DeleteAction)).
Post("/sort", new(groups.SortAction)).
GetPost("/selectPopup", new(groups.SelectPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,73 @@
//go:build plus
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/boards"
nodeboards "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/boards"
nodeschedule "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/schedule"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/schedule/actions"
nodethresholds "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/http3"
networksecurity "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/network-security"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/pages"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/uam"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Data("teaMenu", "clusters").
Data("teaSubMenu", "cluster").
// 节点相关
Prefix("/clusters/cluster/node").
GetPost("/boards", new(nodeboards.IndexAction)).
Post("/boards/data", new(nodeboards.DataAction)).
Post("/boards/domainStats", new(nodeboards.DomainStatsAction)).
// 看板相关
Prefix("/clusters/cluster/boards").
GetPost("", new(boards.IndexAction)).
Post("/domainStats", new(boards.DomainStatsAction)).
// 集群设置相关
Prefix("/clusters/cluster/settings").
GetPost("/uam", new(uam.IndexAction)).
GetPost("/cc", new(cc.IndexAction)).
GetPost("/pages", new(pages.IndexAction)).
//
GetPost("/http3", new(http3.IndexAction)).
GetPost("/http3/test", new(http3.TestAction)).
//
GetPost("/thresholds", new(thresholds.IndexAction)).
//
GetPost("/network-security", new(networksecurity.IndexAction)).
// 节点设置相关
Prefix("/clusters/cluster/node/settings").
Get("/thresholds", new(nodethresholds.IndexAction)).
GetPost("/schedule", new(nodeschedule.IndexAction)).
Post("/schedule/resetActionStatus", new(nodeschedule.ResetActionStatusAction)).
GetPost("/schedule/actions/createPopup", new(scheduleactions.CreatePopupAction)).
GetPost("/schedule/actions/updatePopup", new(scheduleactions.UpdatePopupAction)).
Post("/schedule/actions/delete", new(scheduleactions.DeleteAction)).
Post("/schedule/actions/copyToGroup", new(scheduleactions.CopyToGroupAction)).
Post("/schedule/actions/copyToCluster", new(scheduleactions.CopyToClusterAction)).
Post("/schedule/actions/updateOrders", new(scheduleactions.UpdateActionsAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,67 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type InstallManualAction struct {
actionutils.ParentAction
}
func (this *InstallManualAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallManualAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "manual", this.LangCode())
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
installStatus := maps.Map{
"isRunning": false,
"isFinished": false,
}
if node.InstallStatus != nil {
installStatus = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,74 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstallNodesAction struct {
actionutils.ParentAction
}
func (this *InstallNodesAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallNodesAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "register", this.LangCode())
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if clusterResp.NodeCluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
cluster := clusterResp.NodeCluster
clusterAPINodesResp, err := this.RPC().NodeClusterRPC().FindAPINodesWithNodeCluster(this.AdminContext(), &pb.FindAPINodesWithNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if err != nil {
this.ErrorPage(err)
return
}
apiNodeAddrs := []string{}
if clusterAPINodesResp.UseAllAPINodes {
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, apiNode := range apiNodesResp.ApiNodes {
if !apiNode.IsOn {
continue
}
apiNodeAddrs = append(apiNodeAddrs, apiNode.AccessAddrs...)
}
} else {
for _, apiNode := range clusterAPINodesResp.ApiNodes {
if !apiNode.IsOn {
continue
}
apiNodeAddrs = append(apiNodeAddrs, apiNode.AccessAddrs...)
}
}
this.Data["cluster"] = maps.Map{
"uniqueId": cluster.UniqueId,
"secret": cluster.Secret,
"endpoints": "\"" + strings.Join(apiNodeAddrs, "\", \"") + "\"",
}
this.Show()
}

View File

@@ -0,0 +1,86 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type InstallRemoteAction struct {
actionutils.ParentAction
}
func (this *InstallRemoteAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallRemoteAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "install", this.LangCode())
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
installStatus := maps.Map{
"isRunning": false,
"isFinished": false,
}
if node.InstallStatus != nil {
installStatus = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})
}
this.Data["nodes"] = nodeMaps
this.Show()
}
func (this *InstallRemoteAction) RunPost(params struct {
NodeId int64
Must *actions.Must
}) {
_, err := this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogInstallNodeRemotely, params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,35 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type InstallStatusAction struct {
actionutils.ParentAction
}
func (this *InstallStatusAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().FindNodeInstallStatus(this.AdminContext(), &pb.FindNodeInstallStatusRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.InstallStatus == nil {
this.Data["status"] = nil
this.Success()
}
this.Data["status"] = maps.Map{
"isRunning": resp.InstallStatus.IsRunning,
"isFinished": resp.InstallStatus.IsFinished,
"isOk": resp.InstallStatus.IsOk,
"error": resp.InstallStatus.Error,
"errorCode": resp.InstallStatus.ErrorCode,
}
this.Success()
}

View File

@@ -0,0 +1,45 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type DataAction struct {
actionutils.ParentAction
}
func (this *DataAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
"monthlyTrafficBytes": resp.MonthlyTrafficBytes,
"todayTrafficBytes": resp.DailyTrafficBytes,
"yesterdayTrafficBytes": resp.LastDailyTrafficBytes,
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type DomainStatsAction struct {
actionutils.ParentAction
}
func (this *DomainStatsAction) RunPost(params struct {
NodeId int64
}) {
var hourFrom = timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
var hourTo = timeutil.Format("YmdH")
resp, err := this.RPC().ServerDomainHourlyStatRPC().ListTopServerDomainStatsWithServerId(this.AdminContext(), &pb.ListTopServerDomainStatsWithServerIdRequest{
NodeId: params.NodeId,
HourFrom: hourFrom,
HourTo: hourTo,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.DomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
})
}
this.Data["topDomainStats"] = statMaps
}
this.Success()
}

View File

@@ -0,0 +1,304 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"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/shared"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "board")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
if !teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/node?clusterId=" + strconv.FormatInt(params.ClusterId, 10) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
return
}
pbNode, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// disk information
this.Data["diskWritingSpeedMB"] = 0
this.Data["diskWritingSpeedMBDelta"] = 0
if !utils.JSONIsNull(pbNode.StatusJSON) {
var statusConfig = &nodeconfigs.NodeStatus{}
err = json.Unmarshal(pbNode.StatusJSON, statusConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["diskWritingSpeedMB"] = statusConfig.DiskWritingSpeedMB
}
// max cache capacity
this.Data["cacheDiskCapacityBytes"] = 0
if pbNode.MaxCacheDiskCapacity == nil {
pbNode.MaxCacheDiskCapacity = &pb.SizeCapacity{Count: 0, Unit: "byte"}
}
var capacityBytes = (&shared.SizeCapacity{
Count: pbNode.MaxCacheDiskCapacity.Count,
Unit: pbNode.MaxCacheDiskCapacity.Unit,
}).Bytes()
if capacityBytes <= 0 && params.ClusterId > 0 {
// lookup cache policy for cluster
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if clusterResp.NodeCluster != nil && clusterResp.NodeCluster.HttpCachePolicyId > 0 {
cachePolicyResp, err := this.RPC().HTTPCachePolicyRPC().FindEnabledHTTPCachePolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPCachePolicyConfigRequest{HttpCachePolicyId: clusterResp.NodeCluster.HttpCachePolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var cachePolicyJSON = cachePolicyResp.HttpCachePolicyJSON
if !utils.JSONIsNull(cachePolicyJSON) {
var cachePolicy = &serverconfigs.HTTPCachePolicy{}
err = json.Unmarshal(cachePolicyJSON, cachePolicy)
if err != nil {
this.ErrorPage(err)
return
}
if cachePolicy.IsOn && cachePolicy.Type == serverconfigs.CachePolicyStorageFile && cachePolicy.Capacity != nil {
capacityBytes = cachePolicy.Capacity.Bytes()
}
}
}
}
if capacityBytes > 0 {
this.Data["cacheDiskCapacityBytes"] = capacityBytes
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
"monthlyTrafficBytes": resp.MonthlyTrafficBytes,
"todayTrafficBytes": resp.DailyTrafficBytes,
"yesterdayTrafficBytes": resp.LastDailyTrafficBytes,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// CPU
{
var statMaps = []maps.Map{}
for _, stat := range resp.CpuNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["cpuValues"] = statMaps
}
// Memory
{
var statMaps = []maps.Map{}
for _, stat := range resp.MemoryNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["memoryValues"] = statMaps
}
// Load
{
var statMaps = []maps.Map{}
for _, stat := range resp.LoadNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("load1m"),
})
}
this.Data["loadValues"] = statMaps
}
// CacheDirs
{
var statMaps = []maps.Map{}
for _, stat := range resp.CacheDirsValues {
var m = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &m)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": m,
})
}
this.Data["cacheDirValues"] = statMaps
}
// Network Packets
{
var hasData = false
var statMaps = []maps.Map{}
for _, stat := range resp.NetworkPacketsValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
var tcpInPPS = valueMap.GetUint64("tcpInPPS")
var udpInPPS = valueMap.GetUint64("udpInPPS")
var icmpInPPS = valueMap.GetUint64("icmpInPPS")
if tcpInPPS > 0 || udpInPPS > 0 || icmpInPPS > 0 {
hasData = true
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"tcpInPPS": tcpInPPS,
"udpInPPS": udpInPPS,
"icmpInPPS": icmpInPPS,
})
}
if !hasData {
statMaps = []maps.Map{}
}
this.Data["networkPacketsValues"] = statMaps
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Success()
}

View File

@@ -0,0 +1,403 @@
package node
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type DetailAction struct {
actionutils.ParentAction
}
func (this *DetailAction) Init() {
this.Nav("", "node", "node")
this.SecondMenu("nodes")
}
func (this *DetailAction) RunGet(params struct {
NodeId int64
ClusterId int64
}) {
this.Data["nodeId"] = params.NodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
// 主集群
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
var clusterId = node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster != nil {
clusterMap = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
}
}
}
// 从集群
var secondaryClustersMaps = []maps.Map{}
for _, cluster := range node.SecondaryNodeClusters {
secondaryClustersMaps = append(secondaryClustersMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"isOn": cluster.IsOn,
})
}
// 当前访问集群的DNS设置
clusterDNSInfo, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsIsExcludingLnNode"] = clusterDNSInfo != nil && !clusterDNSInfo.IncludingLnNodes && node.Level > 1
// IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
var ipAddresses = ipAddressesResp.NodeIPAddresses
var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses {
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil {
this.ErrorPage(err)
return
}
// 是否有备用IP
var originIP = addr.Ip
if len(addr.BackupIP) > 0 {
addr.Ip = addr.BackupIP
}
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id,
"name": addr.Name,
"originIP": originIP,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
"isOn": addr.IsOn,
"isUp": addr.IsUp,
"clusters": addrClusterMaps,
"thresholds": thresholds,
})
}
// DNS相关
var clusters = []*pb.NodeCluster{node.NodeCluster}
clusters = append(clusters, node.SecondaryNodeClusters...)
var recordMaps = []maps.Map{}
var routeMaps = []maps.Map{}
for _, cluster := range clusters {
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId,
NodeClusterId: cluster.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
var dnsInfo = dnsInfoResp.Node
if len(dnsInfo.DnsDomainName) == 0 || len(dnsInfo.NodeClusterDNSName) == 0 {
continue
}
var domainName = dnsInfo.DnsDomainName
// 默认线路
if len(dnsInfo.Routes) == 0 {
dnsInfo.Routes = append(dnsInfo.Routes, &pb.DNSRoute{})
} else {
for _, route := range dnsInfo.Routes {
routeMaps = append(routeMaps, maps.Map{
"domainName": domainName,
"code": route.Code,
"name": route.Name,
})
}
}
for _, addr := range ipAddresses {
if !addr.CanAccess || !addr.IsUp || !addr.IsOn {
continue
}
// 过滤集群
if len(addr.NodeClusters) > 0 {
var inCluster = false
for _, addrCluster := range addr.NodeClusters {
if addrCluster.Id == cluster.Id {
inCluster = true
}
}
if !inCluster {
continue
}
}
for _, route := range dnsInfo.Routes {
var recordType = "A"
if iputils.IsIPv6(addr.Ip) {
recordType = "AAAA"
}
recordMaps = append(recordMaps, maps.Map{
"name": dnsInfo.NodeClusterDNSName + "." + domainName,
"type": recordType,
"route": route.Name,
"value": addr.Ip,
"clusterName": cluster.Name,
"isBackup": dnsInfo.IsBackupForCluster || dnsInfo.IsBackupForGroup,
"isOffline": dnsInfo.IsOffline,
})
}
}
}
// 登录信息
var loginMap maps.Map = nil
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
var grantMap = maps.Map{}
var grantId = loginParams.GetInt64("grantId")
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method, this.LangCode()),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}
}
// 运行状态
var status = &nodeconfigs.NodeStatus{}
this.Data["nodeDatetime"] = ""
this.Data["nodeTimeDiff"] = 0
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(err)
return
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
if status.Timestamp > 0 {
this.Data["nodeDatetime"] = timeutil.FormatTime("Y-m-d H:i:s", status.Timestamp)
if status.UpdatedAt > 0 {
var diff = status.UpdatedAt - status.Timestamp
if diff < 0 {
diff = -diff
}
this.Data["nodeTimeDiff"] = diff
}
} else if status.UpdatedAt > 0 {
this.Data["nodeDatetime"] = timeutil.FormatTime("Y-m-d H:i:s", status.UpdatedAt)
}
}
// 检查是否有新版本
if len(status.OS) > 0 {
checkVersionResp, err := this.RPC().NodeRPC().CheckNodeLatestVersion(this.AdminContext(), &pb.CheckNodeLatestVersionRequest{
Os: status.OS,
Arch: status.Arch,
CurrentVersion: status.BuildVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["shouldUpgrade"] = checkVersionResp.HasNewVersion
this.Data["newVersion"] = checkVersionResp.NewVersion
} else {
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// 缓存硬盘 & 内存容量
var maxCacheDiskCapacity maps.Map
if node.MaxCacheDiskCapacity != nil {
maxCacheDiskCapacity = maps.Map{
"count": node.MaxCacheDiskCapacity.Count,
"unit": node.MaxCacheDiskCapacity.Unit,
}
} else {
maxCacheDiskCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var maxCacheMemoryCapacity maps.Map
if node.MaxCacheMemoryCapacity != nil {
maxCacheMemoryCapacity = maps.Map{
"count": node.MaxCacheMemoryCapacity.Count,
"unit": node.MaxCacheMemoryCapacity.Unit,
}
} else {
maxCacheMemoryCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var lnAddrs = node.LnAddrs
if lnAddrs == nil {
lnAddrs = []string{}
}
// API节点地址
var apiNodeAddrStrings = []string{}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(node.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
for _, addr := range apiNodeAddrs {
if addr.Init() == nil {
apiNodeAddrStrings = append(apiNodeAddrStrings, addr.FullAddresses()...)
}
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"secondaryClusters": secondaryClustersMaps,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"records": recordMaps,
"routes": routeMaps,
"level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"offlineDay": node.OfflineDay,
"isOffline": len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd"),
"isBackupForCluster": node.IsBackupForCluster,
"isBackupForGroup": node.IsBackupForGroup,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"load1m": numberutils.FormatFloat2(status.Load1m),
"load5m": numberutils.FormatFloat2(status.Load5m),
"load15m": numberutils.FormatFloat2(status.Load15m),
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
"diskWritingSpeedMB": status.DiskWritingSpeedMB,
},
"group": groupMap,
"region": regionMap,
"maxCacheDiskCapacity": maxCacheDiskCapacity,
"maxCacheMemoryCapacity": maxCacheMemoryCapacity,
}
this.Show()
}

View File

@@ -0,0 +1,34 @@
package node
import (
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "node")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
if teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/node/boards?clusterId=" + fmt.Sprintf("%d", this.Data["clusterId"]) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
} else {
this.RedirectURL("/clusters/cluster/node/detail?clusterId=" + fmt.Sprintf("%d", this.Data["clusterId"]) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
}
}

View File

@@ -0,0 +1,155 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"path/filepath"
"strings"
)
// InstallAction 安装节点
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
// 节点
node, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// 最近运行目录
var exeRoot = ""
if len(node.StatusJSON) > 0 {
var nodeStatus = &nodeconfigs.NodeStatus{}
err = json.Unmarshal(node.StatusJSON, nodeStatus)
if err == nil {
var exePath = nodeStatus.ExePath
if len(exePath) > 0 {
exeRoot = filepath.Dir(filepath.Dir(exePath))
}
}
}
this.Data["exeRoot"] = exeRoot
// 安装信息
if node.InstallStatus != nil {
this.Data["installStatus"] = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"updatedAt": node.InstallStatus.UpdatedAt,
"error": node.InstallStatus.Error,
}
} else {
this.Data["installStatus"] = nil
}
// 集群
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
clusterId := node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
this.ErrorPage(err)
return
}
cluster := clusterResp.NodeCluster
if cluster != nil {
clusterMap = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
}
}
}
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodes = apiNodesResp.ApiNodes
apiEndpoints := []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
var nodeMap = this.Data["node"].(maps.Map)
nodeMap["installDir"] = node.InstallDir
nodeMap["isInstalled"] = node.IsInstalled
nodeMap["uniqueId"] = node.UniqueId
nodeMap["secret"] = node.Secret
nodeMap["cluster"] = clusterMap
// 安装文件
var installerFiles = clusterutils.ListInstallerFiles()
this.Data["installerFiles"] = installerFiles
// SSH主机地址
this.Data["sshAddr"] = ""
if node.NodeLogin != nil && node.NodeLogin.Type == "ssh" && !utils.JSONIsNull(node.NodeLogin.Params) {
var loginParams = maps.Map{}
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
var host = loginParams.GetString("host")
if len(host) > 0 {
var port = loginParams.GetString("port")
if port == "0" {
port = "22"
}
this.Data["sshAddr"] = configutils.QuoteIP(host) + ":" + port
}
}
this.Show()
}
// RunPost 开始安装
func (this *InstallAction) RunPost(params struct {
NodeId int64
Must *actions.Must
}) {
_, err := this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogInstallNode, params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,109 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/nodelogutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type LogsAction struct {
actionutils.ParentAction
}
func (this *LogsAction) Init() {
this.Nav("", "node", "log")
this.SecondMenu("nodes")
}
func (this *LogsAction) RunGet(params struct {
NodeId int64
DayFrom string
DayTo string
Keyword string
Level string
Tag string
}) {
// 初始化节点信息(用于菜单)
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["tags"] = nodelogutils.FindNodeCommonTags(this.LangCode())
this.Data["nodeId"] = params.NodeId
this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo
this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level
this.Data["tag"] = params.Tag
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
Role: nodeconfigs.NodeRoleNode,
NodeId: params.NodeId,
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
Level: params.Level,
Tag: params.Tag,
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count, 20)
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
NodeId: params.NodeId,
Role: "node",
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
Level: params.Level,
Tag: params.Tag,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
logs := []maps.Map{}
for _, log := range logsResp.NodeLogs {
// 服务信息
var serverMap = maps.Map{"id": 0}
if log.ServerId > 0 {
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: log.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
var server = serverResp.Server
if server != nil {
serverMap = maps.Map{"id": server.Id, "name": server.Name}
}
}
logs = append(logs, maps.Map{
"tag": log.Tag,
"description": log.Description,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
"level": log.Level,
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
"count": log.Count,
"server": serverMap,
})
}
this.Data["logs"] = logs
this.Data["page"] = page.AsHTML()
this.Show()
}

View File

@@ -0,0 +1,106 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodeutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strconv"
)
// InitNodeInfo 初始化节点信息
func InitNodeInfo(parentAction *actionutils.ParentAction, nodeId int64) (*pb.Node, error) {
// 节点信息(用于菜单)
nodeResp, err := parentAction.RPC().NodeRPC().FindEnabledNode(parentAction.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
return nil, err
}
if nodeResp.Node == nil {
return nil, errors.New("node '" + strconv.FormatInt(nodeId, 10) + "' not found")
}
var node = nodeResp.Node
info, err := parentAction.RPC().NodeRPC().FindEnabledNodeConfigInfo(parentAction.AdminContext(), &pb.FindEnabledNodeConfigInfoRequest{NodeId: nodeId})
if err != nil {
return nil, err
}
var groupMap maps.Map
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
parentAction.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"isOn": node.IsOn,
"isUp": node.IsUp,
"group": groupMap,
"level": node.Level,
}
var clusterId int64 = 0
if node.NodeCluster != nil {
parentAction.Data["clusterId"] = node.NodeCluster.Id
clusterId = node.NodeCluster.Id
}
// 左侧菜单
var prefix = "/clusters/cluster/node"
var query = "clusterId=" + types.String(clusterId) + "&nodeId=" + types.String(nodeId)
var menuItem = parentAction.Data.GetString("secondMenuItem")
var menuItems = []maps.Map{
{
"name": parentAction.Lang(codes.NodeMenu_SettingBasic),
"url": prefix + "/update?" + query,
"isActive": menuItem == "basic",
},
{
"name": parentAction.Lang(codes.NodeMenu_SettingDNS),
"url": prefix + "/settings/dns?" + query,
"isActive": menuItem == "dns",
"isOn": info.HasDNSInfo,
},
{
"name": parentAction.Lang(codes.NodeMenu_SettingCache),
"url": prefix + "/settings/cache?" + query,
"isActive": menuItem == "cache",
"isOn": info.HasCacheInfo,
},
{
"name": parentAction.Lang(codes.NodeMenu_SettingDDoSProtection),
"url": prefix + "/settings/ddos-protection?" + query,
"isActive": menuItem == "ddosProtection",
"isOn": info.HasDDoSProtection,
},
{
"name": "-",
"url": "",
},
}
menuItems = filterMenuItems(menuItems, menuItem, prefix, query, info, parentAction.LangCode())
menuItems = append(menuItems, []maps.Map{
{
"name": parentAction.Lang(codes.NodeMenu_SettingSSH),
"url": prefix + "/settings/ssh?" + query,
"isActive": menuItem == "ssh",
"isOn": info.HasSSH,
},
{
"name": parentAction.Lang(codes.NodeMenu_SettingSystem),
"url": prefix + "/settings/system?" + query,
"isActive": menuItem == "system",
"isOn": info.HasSystemSettings,
},
}...)
parentAction.Data["leftMenuItems"] = menuItems
return nodeResp.Node, nil
}

View File

@@ -0,0 +1,13 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodeutils
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func filterMenuItems(menuItems []maps.Map, menuItem string, prefix string, query string, info *pb.FindEnabledNodeConfigInfoResponse, langCode string) []maps.Map {
return menuItems
}

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package nodeutils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
"github.com/iwind/TeaGo/maps"
)
func filterMenuItems(menuItems []maps.Map, menuItem string, prefix string, query string, info *pb.FindEnabledNodeConfigInfoResponse, langCode string) []maps.Map {
if teaconst.IsPlus {
if plusutils.CheckComponent(plusutils.ComponentCodeScheduling) {
menuItems = append(menuItems, maps.Map{
"name": langs.Message(langCode, codes.NodeMenu_SettingSchedule),
"url": prefix + "/settings/schedule?" + query,
"isActive": menuItem == "schedule",
"isOn": info.HasScheduleSettings,
})
}
menuItems = append(menuItems, []maps.Map{
{
"name": langs.Message(langCode, codes.NodeMenu_SettingThresholds),
"url": prefix + "/settings/thresholds?" + query,
"isActive": menuItem == "threshold",
"isOn": info.HasThresholds,
},
}...)
}
return menuItems
}

View File

@@ -0,0 +1,16 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodeutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestInstallLocalNode(t *testing.T) {
err := nodeutils.InstallLocalNode()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,143 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cache
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("cache")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
node, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// 缓存硬盘 & 内存容量
var maxCacheDiskCapacity maps.Map
if node.MaxCacheDiskCapacity != nil {
maxCacheDiskCapacity = maps.Map{
"count": node.MaxCacheDiskCapacity.Count,
"unit": node.MaxCacheDiskCapacity.Unit,
}
} else {
maxCacheDiskCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var maxCacheMemoryCapacity maps.Map
if node.MaxCacheMemoryCapacity != nil {
maxCacheMemoryCapacity = maps.Map{
"count": node.MaxCacheMemoryCapacity.Count,
"unit": node.MaxCacheMemoryCapacity.Unit,
}
} else {
maxCacheMemoryCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var diskSubDirs = []*serverconfigs.CacheDir{}
if len(node.CacheDiskSubDirsJSON) > 0 {
err = json.Unmarshal(node.CacheDiskSubDirsJSON, &diskSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
var nodeMap = this.Data["node"].(maps.Map)
nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity
nodeMap["cacheDiskDir"] = node.CacheDiskDir
nodeMap["cacheDiskSubDirs"] = diskSubDirs
nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
MaxCacheDiskCapacityJSON []byte
CacheDiskDir string
CacheDiskSubDirsJSON []byte
MaxCacheMemoryCapacityJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeCache_LogUpdateNodeCacheSettings, params.NodeId)
// 缓存硬盘 & 内存容量
var pbMaxCacheDiskCapacity *pb.SizeCapacity
if len(params.MaxCacheDiskCapacityJSON) > 0 {
var sizeCapacity = &shared.SizeCapacity{}
err := json.Unmarshal(params.MaxCacheDiskCapacityJSON, sizeCapacity)
if err != nil {
this.ErrorPage(err)
return
}
pbMaxCacheDiskCapacity = &pb.SizeCapacity{
Count: sizeCapacity.Count,
Unit: sizeCapacity.Unit,
}
}
var pbMaxCacheMemoryCapacity *pb.SizeCapacity
if len(params.MaxCacheMemoryCapacityJSON) > 0 {
var sizeCapacity = &shared.SizeCapacity{}
err := json.Unmarshal(params.MaxCacheMemoryCapacityJSON, sizeCapacity)
if err != nil {
this.ErrorPage(err)
return
}
pbMaxCacheMemoryCapacity = &pb.SizeCapacity{
Count: sizeCapacity.Count,
Unit: sizeCapacity.Unit,
}
}
if len(params.CacheDiskSubDirsJSON) > 0 {
var cacheSubDirs = []*serverconfigs.CacheDir{}
err := json.Unmarshal(params.CacheDiskSubDirsJSON, &cacheSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
_, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{
NodeId: params.NodeId,
MaxCacheDiskCapacity: pbMaxCacheDiskCapacity,
CacheDiskDir: params.CacheDiskDir,
CacheDiskSubDirsJSON: params.CacheDiskSubDirsJSON,
MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,138 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ddosProtection
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"net"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("ddosProtection")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
// 集群设置
clusterProtectionResp, err := this.RPC().NodeClusterRPC().FindNodeClusterDDoSProtection(this.AdminContext(), &pb.FindNodeClusterDDoSProtectionRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var clusterDDoSProtectionIsOn = false
if len(clusterProtectionResp.DdosProtectionJSON) > 0 {
var clusterDDoSProtection = &ddosconfigs.ProtectionConfig{}
err = json.Unmarshal(clusterProtectionResp.DdosProtectionJSON, clusterDDoSProtection)
if err != nil {
this.ErrorPage(err)
return
}
clusterDDoSProtectionIsOn = clusterDDoSProtection.IsOn()
}
this.Data["clusterDDoSProtectionIsOn"] = clusterDDoSProtectionIsOn
// 节点设置
ddosProtectionResp, err := this.RPC().NodeRPC().FindNodeDDoSProtection(this.AdminContext(), &pb.FindNodeDDoSProtectionRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var ddosProtectionConfig = ddosconfigs.DefaultProtectionConfig()
if len(ddosProtectionResp.DdosProtectionJSON) > 0 {
err = json.Unmarshal(ddosProtectionResp.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["config"] = ddosProtectionConfig
this.Data["defaultConfigs"] = nodeconfigs.DefaultConfigs
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
DdosProtectionJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.DDoSProtection_LogUpdateNodeDDoSProtection, params.NodeId)
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
err := json.Unmarshal(params.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = ddosProtectionConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
// 校验参数
if ddosProtectionConfig.TCP != nil {
var tcpConfig = ddosProtectionConfig.TCP
if tcpConfig.MaxConnectionsPerIP > 0 && tcpConfig.MaxConnectionsPerIP < nodeconfigs.DefaultTCPMinConnectionsPerIP {
this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP))
}
if tcpConfig.NewConnectionsMinutelyRate > 0 && tcpConfig.NewConnectionsMinutelyRate < nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate {
this.FailField("tcpNewConnectionsMinutelyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate))
}
if tcpConfig.NewConnectionsSecondlyRate > 0 && tcpConfig.NewConnectionsSecondlyRate < nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate {
this.FailField("tcpNewConnectionsSecondlyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate))
}
// Port
for _, portConfig := range tcpConfig.Ports {
if portConfig.Port > 65535 {
this.Fail("端口号" + types.String(portConfig.Port) + "不能大于65535")
}
}
// IP
for _, ipConfig := range tcpConfig.AllowIPList {
if net.ParseIP(ipConfig.IP) == nil {
this.Fail("白名单IP '" + ipConfig.IP + "' 格式错误")
}
}
}
_, err = this.RPC().NodeRPC().UpdateNodeDDoSProtection(this.AdminContext(), &pb.UpdateNodeDDoSProtectionRequest{
NodeId: params.NodeId,
DdosProtectionJSON: params.DdosProtectionJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ddosProtection
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
)
type StatusAction struct {
actionutils.ParentAction
}
func (this *StatusAction) RunPost(params struct {
NodeId int64
}) {
results, err := nodeutils.SendMessageToNodeIds(this.AdminContext(), []int64{params.NodeId}, messageconfigs.MessageCodeCheckLocalFirewall, &messageconfigs.CheckLocalFirewallMessage{
Name: "nftables",
}, 10)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["results"] = results
this.Success()
}

View File

@@ -0,0 +1,143 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dns
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("dns")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
node, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// DNS相关
var clusters = []*pb.NodeCluster{node.NodeCluster}
clusters = append(clusters, node.SecondaryNodeClusters...)
var allDNSRouteMaps = map[int64][]maps.Map{} // domain id => routes
var routeMaps = map[int64][]maps.Map{} // domain id => routes
var domainIds = []int64{}
for _, cluster := range clusters {
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId,
NodeClusterId: cluster.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
var dnsInfo = dnsInfoResp.Node
if dnsInfo.DnsDomainId <= 0 || len(dnsInfo.DnsDomainName) == 0 {
continue
}
var domainId = dnsInfo.DnsDomainId
// remove same domain
if lists.ContainsInt64(domainIds, domainId) {
continue
}
domainIds = append(domainIds, domainId)
var domainName = dnsInfo.DnsDomainName
if len(dnsInfo.Routes) > 0 {
for _, route := range dnsInfo.Routes {
routeMaps[domainId] = append(routeMaps[domainId], maps.Map{
"domainId": domainId,
"domainName": domainName,
"code": route.Code,
"name": route.Name,
})
}
}
// 所有线路选项
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: dnsInfoResp.Node.DnsDomainId})
if err != nil {
this.ErrorPage(err)
return
}
for _, route := range routesResp.Routes {
allDNSRouteMaps[domainId] = append(allDNSRouteMaps[domainId], maps.Map{
"domainId": domainId,
"domainName": domainName,
"name": route.Name,
"code": route.Code,
})
}
}
var domainRoutes = []maps.Map{}
for _, m := range routeMaps {
domainRoutes = append(domainRoutes, m...)
}
this.Data["dnsRoutes"] = domainRoutes
var allDomainRoutes = []maps.Map{}
for _, m := range allDNSRouteMaps {
allDomainRoutes = append(allDomainRoutes, m...)
}
this.Data["allDNSRoutes"] = allDomainRoutes
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
DnsDomainId int64
DnsRoutesJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeDNS_LogUpdateNodeDNS, params.NodeId)
var rawRouteCodes = []string{}
if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &rawRouteCodes)
if err != nil {
this.ErrorPage(err)
return
}
}
// remove duplications
var dnsRouteCodes = []string{}
for _, routeCode := range rawRouteCodes {
if !lists.ContainsString(dnsRouteCodes, routeCode) {
dnsRouteCodes = append(dnsRouteCodes, routeCode)
}
}
_, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{
NodeId: params.NodeId,
IpAddr: "",
DnsDomainId: 0,
Routes: dnsRouteCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package scheduleactions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type CopyToClusterAction struct {
actionutils.ParentAction
}
func (this *CopyToClusterAction) RunPost(params struct {
NodeId int64
}) {
defer this.CreateLogInfo(codes.NodeAction_LogCopyNodeActionsToCluster, params.NodeId)
_, err := this.RPC().NodeRPC().CopyNodeActionsToNodeCluster(this.AdminContext(), &pb.CopyNodeActionsToNodeClusterRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package scheduleactions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type CopyToGroupAction struct {
actionutils.ParentAction
}
func (this *CopyToGroupAction) RunPost(params struct {
NodeId int64
}) {
defer this.CreateLogInfo(codes.NodeAction_LogCopyNodeActionsToGroup, params.NodeId)
_, err := this.RPC().NodeRPC().CopyNodeActionsToNodeGroup(this.AdminContext(), &pb.CopyNodeActionsToNodeGroupRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,135 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package scheduleactions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"regexp"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
// 选项
this.Data["params"] = nodeconfigs.FindAllNodeActionParamDefinitions()
this.Data["operators"] = nodeconfigs.FindAllNodeActionOperatorDefinitions()
this.Data["actions"] = nodeconfigs.FindAllNodeActionDefinitions()
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
NodeId int64
CondsJSON []byte
ActionJSON []byte
DurationMinutes int16
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var createdActionId int64
defer func() {
this.CreateLogInfo(codes.NodeAction_LogCreateNodeAction, createdActionId)
}()
// 校验条件
if len(params.CondsJSON) == 0 {
this.Fail("请设置条件")
return
}
var condsConfig = &nodeconfigs.NodeActionCondsConfig{}
err := json.Unmarshal(params.CondsJSON, condsConfig)
if err != nil {
this.Fail("条件解析错误:" + err.Error())
return
}
// 校验动作
if len(params.ActionJSON) == 0 {
this.Fail("请选择要执行的动作")
return
}
var actionConfig = nodeconfigs.NewNodeActionConfig()
err = json.Unmarshal(params.ActionJSON, actionConfig)
if err != nil {
this.Fail("动作解析错误:" + err.Error())
return
}
if len(actionConfig.Code) == 0 {
this.Fail("请选择要执行的动作")
return
}
// 校验动作参数
switch actionConfig.Code {
case nodeconfigs.NodeActionCodeWebHook:
actionParamsJSON, err := json.Marshal(actionConfig.Params)
if err != nil {
this.Fail("解析WebHook参数失败" + err.Error())
return
}
var actionParams = &nodeconfigs.NodeActionCodeWebHookParams{}
err = json.Unmarshal(actionParamsJSON, actionParams)
if err != nil {
this.Fail("解析WebHook参数失败" + err.Error())
return
}
if len(actionParams.URL) == 0 {
this.Fail("请输入WebHook URL参数")
return
}
if !regexp.MustCompile(`^(?i)(http|https)://`).MatchString(actionParams.URL) {
this.Fail("请输入正确的WebHook URL参数")
return
}
}
// 时间限制
if params.DurationMinutes <= 0 {
this.Fail("请输入持续时间")
return
}
durationJSON, err := (&shared.TimeDuration{
Count: types.Int64(params.DurationMinutes),
Unit: shared.TimeDurationUnitMinute,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().NodeActionRPC().CreateNodeAction(this.AdminContext(), &pb.CreateNodeActionRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
CondsJSON: params.CondsJSON,
ActionJSON: params.ActionJSON,
DurationJSON: durationJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
createdActionId = createResp.NodeActionId
this.Success()
}

View File

@@ -0,0 +1,28 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package scheduleactions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ActionId int64
}) {
defer this.CreateLogInfo(codes.NodeAction_LogDeleteNodeAction, params.ActionId)
_, err := this.RPC().NodeActionRPC().DeleteNodeAction(this.AdminContext(), &pb.DeleteNodeActionRequest{NodeActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,33 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package scheduleactions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateActionsAction struct {
actionutils.ParentAction
}
func (this *UpdateActionsAction) RunPost(params struct {
NodeId int64
ActionIds []int64
}) {
defer this.CreateLogInfo(codes.NodeAction_LogSortNodeActions, params.NodeId)
if len(params.ActionIds) == 0 {
this.Success()
return
}
_, err := this.RPC().NodeActionRPC().UpdateNodeActionOrders(this.AdminContext(), &pb.UpdateNodeActionOrdersRequest{NodeActionIds: params.ActionIds})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,175 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package scheduleactions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ActionId int64
}) {
actionResp, err := this.RPC().NodeActionRPC().FindNodeAction(this.AdminContext(), &pb.FindNodeActionRequest{NodeActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
var action = actionResp.NodeAction
if action == nil {
this.NotFound("nodeAction", params.ActionId)
return
}
// 条件
var condsConfig = &nodeconfigs.NodeActionCondsConfig{}
err = json.Unmarshal(action.CondsJSON, condsConfig)
if err != nil {
this.ErrorPage(err)
return
}
// 动作
var actionConfig = nodeconfigs.NewNodeActionConfig()
err = json.Unmarshal(action.ActionJSON, actionConfig)
if err != nil {
this.ErrorPage(err)
return
}
// 时间
var duration = &shared.TimeDuration{}
if len(action.DurationJSON) > 0 {
err = json.Unmarshal(action.DurationJSON, duration)
if err != nil {
this.ErrorPage(err)
return
}
}
var durationMinutes = duration.Seconds() / 60 // 目前只支持分钟
this.Data["action"] = maps.Map{
"id": action.Id,
"isOn": action.IsOn,
"conds": condsConfig,
"action": actionConfig,
"durationMinutes": durationMinutes,
}
// 选项
this.Data["params"] = nodeconfigs.FindAllNodeActionParamDefinitions()
this.Data["operators"] = nodeconfigs.FindAllNodeActionOperatorDefinitions()
this.Data["actions"] = nodeconfigs.FindAllNodeActionDefinitions()
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ActionId int64
CondsJSON []byte
ActionJSON []byte
DurationMinutes int16
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeAction_LogUpdateNodeAction, params.ActionId)
// 校验条件
if len(params.CondsJSON) == 0 {
this.Fail("请设置条件")
return
}
var condsConfig = &nodeconfigs.NodeActionCondsConfig{}
err := json.Unmarshal(params.CondsJSON, condsConfig)
if err != nil {
this.Fail("条件解析错误:" + err.Error())
return
}
// 校验动作
if len(params.ActionJSON) == 0 {
this.Fail("请选择要执行的动作")
return
}
var actionConfig = nodeconfigs.NewNodeActionConfig()
err = json.Unmarshal(params.ActionJSON, actionConfig)
if err != nil {
this.Fail("动作解析错误:" + err.Error())
return
}
if len(actionConfig.Code) == 0 {
this.Fail("请选择要执行的动作")
return
}
// 校验动作参数
switch actionConfig.Code {
case nodeconfigs.NodeActionCodeWebHook:
actionParamsJSON, err := json.Marshal(actionConfig.Params)
if err != nil {
this.Fail("解析WebHook参数失败" + err.Error())
return
}
var actionParams = &nodeconfigs.NodeActionCodeWebHookParams{}
err = json.Unmarshal(actionParamsJSON, actionParams)
if err != nil {
this.Fail("解析WebHook参数失败" + err.Error())
return
}
if len(actionParams.URL) == 0 {
this.Fail("请输入WebHook URL参数")
return
}
if !regexp.MustCompile(`^(?i)(http|https)://`).MatchString(actionParams.URL) {
this.Fail("请输入正确的WebHook URL参数")
return
}
}
// 时间限制
if params.DurationMinutes <= 0 {
this.Fail("请输入持续时间")
return
}
durationJSON, err := (&shared.TimeDuration{
Count: types.Int64(params.DurationMinutes),
Unit: shared.TimeDurationUnitMinute,
}).AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeActionRPC().UpdateNodeAction(this.AdminContext(), &pb.UpdateNodeActionRequest{
NodeActionId: params.ActionId,
CondsJSON: params.CondsJSON,
ActionJSON: params.ActionJSON,
DurationJSON: durationJSON,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,237 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package nodeschedule
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"net"
"regexp"
"strings"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("schedule")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
// 检查权限
if !teaconst.IsPlus {
return
}
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
// 基础设置
scheduleInfoResp, err := this.RPC().NodeRPC().FindNodeScheduleInfo(this.AdminContext(), &pb.FindNodeScheduleInfoRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var scheduleInfo = scheduleInfoResp.ScheduleInfo
if scheduleInfo == nil {
this.NotFound("node", params.NodeId)
return
}
if len(scheduleInfo.OfflineDay) == 8 {
scheduleInfo.OfflineDay = scheduleInfo.OfflineDay[:4] + "-" + scheduleInfo.OfflineDay[4:6] + "-" + scheduleInfo.OfflineDay[6:]
}
if scheduleInfo.BackupIPs == nil {
scheduleInfo.BackupIPs = []string{}
}
// 当前节点所属集群
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.NotFound("node", params.NodeId)
return
}
var clusterName = ""
if node.NodeCluster != nil {
clusterName = node.NodeCluster.Name
}
// 当前节点所属分组
var groupName = ""
if node.NodeGroup != nil {
groupName = node.NodeGroup.Name
}
// 动作状态
var actionStatus = &nodeconfigs.NodeActionStatus{}
if len(scheduleInfo.ActionStatusJSON) > 0 {
err = json.Unmarshal(scheduleInfo.ActionStatusJSON, actionStatus)
if err != nil {
this.ErrorPage(err)
return
}
}
var actionStatusExpiresTime = ""
if actionStatus.ExpiresAt > 0 {
actionStatusExpiresTime = timeutil.FormatTime("Y-m-d H:i:s", actionStatus.ExpiresAt)
}
this.Data["schedule"] = maps.Map{
"id": params.NodeId,
"offlineDay": scheduleInfo.OfflineDay,
"isOffline": len(scheduleInfo.OfflineDay) > 0 && scheduleInfo.OfflineDay < timeutil.Format("Y-m-d" /** 前面的OfflineDay已经被转换 **/),
"isNearlyOffline": len(scheduleInfo.OfflineDay) > 0 && scheduleInfo.OfflineDay < timeutil.Format("Y-m-d", time.Now().AddDate(0, 0, 3)),
"isBackupForCluster": scheduleInfo.IsBackupForCluster,
"isBackupForGroup": scheduleInfo.IsBackupForGroup,
"backupIPs": scheduleInfo.BackupIPs,
"nodeClusterName": clusterName,
"nodeGroupName": groupName,
"actionStatus": actionStatus,
"actionStatusExpiresTime": actionStatusExpiresTime,
}
// 所有动作
actionsResp, err := this.RPC().NodeActionRPC().FindAllNodeActions(this.AdminContext(), &pb.FindAllNodeActionsRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
var actionMaps = []maps.Map{}
for _, action := range actionsResp.NodeActions {
// conds
var condsConfig = nodeconfigs.NewNodeActionCondsConfig()
err = json.Unmarshal(action.CondsJSON, condsConfig)
if err != nil {
this.ErrorPage(err)
return
}
// action
var actionConfig = nodeconfigs.NewNodeActionConfig()
err = json.Unmarshal(action.ActionJSON, actionConfig)
if err != nil {
this.ErrorPage(err)
return
}
var actionDef = nodeconfigs.FindNodeActionDefinition(actionConfig.Code)
if actionDef == nil {
continue
}
// duration
var duration = &shared.TimeDuration{}
if len(action.DurationJSON) > 0 {
err = json.Unmarshal(action.DurationJSON, duration)
if err != nil {
this.ErrorPage(err)
return
}
}
var durationDescription = duration.Description()
// special durations
for _, cond := range condsConfig.Conds {
switch cond.Param {
case nodeconfigs.NodeActionParamDailyTrafficOut:
durationDescription = "当天"
case nodeconfigs.NodeActionParamMonthlyTrafficOut:
durationDescription = "当月"
}
}
actionMaps = append(actionMaps, maps.Map{
"id": action.Id,
"conds": condsConfig,
"action": actionConfig,
"actionName": actionDef.Name,
"durationDescription": durationDescription,
"isOn": action.IsOn,
})
}
this.Data["actions"] = actionMaps
// 选项
this.Data["params"] = nodeconfigs.FindAllNodeActionParamDefinitions()
this.Data["operators"] = nodeconfigs.FindAllNodeActionOperatorDefinitions()
var actionMap = map[string]*shared.Definition{} // code => name
for _, actionDef := range nodeconfigs.FindAllNodeActionDefinitions() {
actionMap[actionDef.Code] = actionDef
}
this.Data["actionMap"] = actionMap
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
OfflineDay string
IsBackupForCluster bool
IsBackupForGroup bool
BackupIPs []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeSchedule_LogUpdateNodeScheduleBasic)
// 校验参数
if len(params.OfflineDay) > 0 {
if !regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(params.OfflineDay) {
this.Fail("租期结束日期格式错误")
return
}
params.OfflineDay = strings.ReplaceAll(params.OfflineDay, "-", "")
}
for _, backupIP := range params.BackupIPs {
if net.ParseIP(backupIP) == nil {
this.Fail("备用IP '" + backupIP + "' IP错误")
return
}
}
_, err := this.RPC().NodeRPC().UpdateNodeScheduleInfo(this.AdminContext(), &pb.UpdateNodeScheduleInfoRequest{
NodeId: params.NodeId,
OfflineDay: params.OfflineDay,
IsBackupForCluster: params.IsBackupForCluster,
IsBackupForGroup: params.IsBackupForGroup,
BackupIPs: params.BackupIPs,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,28 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package nodeschedule
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type ResetActionStatusAction struct {
actionutils.ParentAction
}
func (this *ResetActionStatusAction) RunPost(params struct {
NodeId int64
}) {
defer this.CreateLogInfo(codes.NodeSchedule_LogResetNodeActionStatus, params.NodeId)
_, err := this.RPC().NodeRPC().ResetNodeActionStatus(this.AdminContext(), &pb.ResetNodeActionStatusRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,165 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ssh
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("ssh")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
node, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hostIsAutoFilled"] = false
// 登录信息
var loginMap maps.Map = nil
if node.NodeLogin != nil {
var loginParams = maps.Map{}
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
var grantMap = maps.Map{}
var grantId = loginParams.GetInt64("grantId")
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method, this.LangCode()),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}
}
if loginMap == nil {
addressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{NodeId: node.Id})
if err != nil {
this.ErrorPage(err)
return
}
if len(addressesResp.NodeIPAddresses) > 0 {
this.Data["hostIsAutoFilled"] = true
loginMap = maps.Map{
"id": 0,
"name": "",
"type": "ssh",
"params": maps.Map{
"host": addressesResp.NodeIPAddresses[0].Ip,
"port": 22,
"grantId": 0,
},
"grant": nil,
}
}
} else {
var loginParams = loginMap.GetMap("params")
if len(loginParams.GetString("host")) == 0 {
addressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{NodeId: node.Id})
if err != nil {
this.ErrorPage(err)
return
}
if len(addressesResp.NodeIPAddresses) > 0 {
this.Data["hostIsAutoFilled"] = true
loginParams["host"] = addressesResp.NodeIPAddresses[0].Ip
}
}
if loginParams.GetInt("port") == 0 {
loginParams["port"] = 22
}
loginMap["params"] = loginParams
}
var nodeMap = this.Data["node"].(maps.Map)
nodeMap["login"] = loginMap
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
LoginId int64
GrantId int64
SshHost string
SshPort int
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeSSH_LogUpdateNodeSSH, params.NodeId)
// 检查IP地址
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
this.Fail("SSH主机地址 '" + params.SshHost + "' IP格式错误")
}
// TODO 检查登录授权
var loginInfo = &pb.NodeLogin{
Id: params.LoginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
}
_, err := this.RPC().NodeRPC().UpdateNodeLogin(this.AdminContext(), &pb.UpdateNodeLoginRequest{
NodeId: params.NodeId,
NodeLogin: loginInfo,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,31 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ssh
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) RunPost(params struct {
GrantId int64
Host string
Port int32
}) {
resp, err := this.RPC().NodeGrantRPC().TestNodeGrant(this.AdminContext(), &pb.TestNodeGrantRequest{
NodeGrantId: params.GrantId,
Host: params.Host,
Port: params.Port,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isOk"] = resp.IsOk
this.Data["error"] = resp.Error
this.Success()
}

View File

@@ -0,0 +1,147 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package system
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("system")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
node, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// 获取节点信息
var nodeMap = this.Data["node"].(maps.Map)
nodeMap["maxCPU"] = node.MaxCPU
// DNS
dnsResolverResp, err := this.RPC().NodeRPC().FindNodeDNSResolver(this.AdminContext(), &pb.FindNodeDNSResolverRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
if len(dnsResolverResp.DnsResolverJSON) > 0 {
err = json.Unmarshal(dnsResolverResp.DnsResolverJSON, dnsResolverConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["dnsResolverConfig"] = dnsResolverConfig
// API相关
apiConfigResp, err := this.RPC().NodeRPC().FindNodeAPIConfig(this.AdminContext(), &pb.FindNodeAPIConfigRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(apiConfigResp.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(apiConfigResp.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["apiNodeAddrs"] = apiNodeAddrs
this.Show()
}
func (this *IndexAction) RunPost(params struct {
NodeId int64
MaxCPU int32
DnsResolverJSON []byte
ApiNodeAddrsJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeSystem_LogUpdateNodeSystemSettings, params.NodeId)
if params.MaxCPU < 0 {
this.Fail("CPU线程数不能小于0")
}
// 系统设置
_, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{
NodeId: params.NodeId,
MaxCPU: params.MaxCPU,
})
if err != nil {
this.ErrorPage(err)
return
}
// DNS解析设置
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = dnsResolverConfig.Init()
if err != nil {
this.Fail("校验DNS解析配置失败" + err.Error())
}
_, err = this.RPC().NodeRPC().UpdateNodeDNSResolver(this.AdminContext(), &pb.UpdateNodeDNSResolverRequest{
NodeId: params.NodeId,
DnsResolverJSON: params.DnsResolverJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
// API节点设置
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(params.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(params.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
for _, addr := range apiNodeAddrs {
err = addr.Init()
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
}
}
_, err = this.RPC().NodeRPC().UpdateNodeAPIConfig(this.AdminContext(), &pb.UpdateNodeAPIConfigRequest{
NodeId: params.NodeId,
ApiNodeAddrsJSON: params.ApiNodeAddrsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package thresholds2
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("threshold")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
// 列出所有阈值
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
thresholdMaps := []maps.Map{}
for _, threshold := range thresholdsResp.NodeThresholds {
thresholdMaps = append(thresholdMaps, maps.Map{
"id": threshold.Id,
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
"paramIsPercent": nodeconfigs.CheckNodeValueItemParamIsPercent(threshold.Item, threshold.Param),
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
"duration": threshold.Duration,
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
"isOn": threshold.IsOn,
})
}
this.Data["thresholds"] = thresholdMaps
this.Show()
}

View File

@@ -0,0 +1,30 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StartAction struct {
actionutils.ParentAction
}
func (this *StartAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().StartNode(this.AdminContext(), &pb.StartNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogStartNodeRemotely, params.NodeId)
if resp.IsOk {
this.Success()
}
this.Fail("启动失败:" + resp.Error)
}

View File

@@ -0,0 +1,46 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// StatusAction 节点状态
type StatusAction struct {
actionutils.ParentAction
}
func (this *StatusAction) RunPost(params struct {
NodeId int64
}) {
// 节点
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
// 安装信息
if node.InstallStatus != nil {
this.Data["installStatus"] = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"updatedAt": node.InstallStatus.UpdatedAt,
"error": node.InstallStatus.Error,
"errorCode": node.InstallStatus.ErrorCode,
}
} else {
this.Data["installStatus"] = nil
}
this.Data["isInstalled"] = node.IsInstalled
this.Success()
}

View File

@@ -0,0 +1,30 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type StopAction struct {
actionutils.ParentAction
}
func (this *StopAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().StopNode(this.AdminContext(), &pb.StopNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogStopNodeRemotely, params.NodeId)
if resp.IsOk {
this.Success()
}
this.Fail("执行失败:" + resp.Error)
}

View File

@@ -0,0 +1,33 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SyncDomainAction struct {
actionutils.ParentAction
}
func (this *SyncDomainAction) RunPost(params struct {
DomainId int64
}) {
// 记录日志
defer this.CreateLogInfo(codes.DNS_LogSyncDomain, params.DomainId)
// 执行同步
resp, err := this.RPC().DNSDomainRPC().SyncDNSDomainData(this.AdminContext(), &pb.SyncDNSDomainDataRequest{DnsDomainId: params.DomainId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsOk {
this.Success()
} else {
this.Data["shouldFix"] = resp.ShouldFix
this.Fail(resp.Error)
}
this.Success()
}

View File

@@ -0,0 +1,32 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UninstallAction struct {
actionutils.ParentAction
}
func (this *UninstallAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().UninstallNode(this.AdminContext(), &pb.UninstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogUninstallNodeRemotely, params.NodeId)
if resp.IsOk {
this.Success()
}
this.Fail("执行失败:" + resp.Error)
}

View File

@@ -0,0 +1,29 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// 手动上线
type UpAction struct {
actionutils.ParentAction
}
func (this *UpAction) RunPost(params struct {
NodeId int64
}) {
defer this.CreateLogInfo(codes.Node_LogUpNode, params.NodeId)
_, err := this.RPC().NodeRPC().UpdateNodeUp(this.AdminContext(), &pb.UpdateNodeUpRequest{
NodeId: params.NodeId,
IsUp: true,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,275 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("basic")
}
func (this *UpdateAction) RunGet(params struct {
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
clusterMap = maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
}
}
// IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses {
// 阈值
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil {
this.ErrorPage(err)
return
}
// 专属集群
var clusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
"isOn": addr.IsOn,
"isUp": addr.IsUp,
"thresholds": thresholds,
"clusters": clusterMaps,
})
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
var nodeMap = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"isOn": node.IsOn,
"group": groupMap,
"region": regionMap,
"level": node.Level,
"enableIPLists": node.EnableIPLists,
}
if node.LnAddrs == nil {
nodeMap["lnAddrs"] = []string{}
} else {
nodeMap["lnAddrs"] = node.LnAddrs
}
if node.NodeCluster != nil {
nodeMap["primaryCluster"] = maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
}
} else {
nodeMap["primaryCluster"] = nil
}
if len(node.SecondaryNodeClusters) > 0 {
var secondaryClusterMaps = []maps.Map{}
for _, cluster := range node.SecondaryNodeClusters {
secondaryClusterMaps = append(secondaryClusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
})
}
nodeMap["secondaryClusters"] = secondaryClusterMaps
} else {
nodeMap["secondaryClusters"] = []interface{}{}
}
this.Data["node"] = nodeMap
this.Data["canUpdateLevel"] = this.CanUpdateLevel(2)
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
LoginId int64
NodeId int64
GroupId int64
RegionId int64
Name string
IPAddressesJSON []byte `alias:"ipAddressesJSON"`
PrimaryClusterId int64
SecondaryClusterIds []byte
IsOn bool
Level int32
LnAddrs []string
EnableIPLists bool
Must *actions.Must
}) {
// 创建日志
defer this.CreateLogInfo(codes.Node_LogUpdateNode, params.NodeId)
if params.NodeId <= 0 {
this.Fail("要操作的节点不存在")
}
params.Must.
Field("name", params.Name).
Require("请输入节点名称")
// TODO 检查cluster
if params.PrimaryClusterId <= 0 {
this.Fail("请选择节点所在主集群")
}
var secondaryClusterIds = []int64{}
if len(params.SecondaryClusterIds) > 0 {
err := json.Unmarshal(params.SecondaryClusterIds, &secondaryClusterIds)
if err != nil {
this.ErrorPage(err)
return
}
}
// IP地址
var ipAddresses = []maps.Map{}
if len(params.IPAddressesJSON) > 0 {
err := json.Unmarshal(params.IPAddressesJSON, &ipAddresses)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(ipAddresses) == 0 {
this.Fail("请至少输入一个IP地址")
}
// 保存
if !this.CanUpdateLevel(params.Level) {
this.Fail("没有权限修改节点级别:" + types.String(params.Level))
}
// 检查Ln节点地址
var lnAddrs = []string{}
if params.Level > 1 {
for _, lnAddr := range params.LnAddrs {
if len(lnAddr) == 0 {
continue
}
// 处理 host:port
host, _, err := net.SplitHostPort(lnAddr)
if err == nil {
lnAddr = host
}
if net.ParseIP(lnAddr) == nil {
this.Fail("L2级别访问地址 '" + lnAddr + "' 格式错误,请纠正后再提交")
}
lnAddrs = append(lnAddrs, lnAddr)
}
}
_, err := this.RPC().NodeRPC().UpdateNode(this.AdminContext(), &pb.UpdateNodeRequest{
NodeId: params.NodeId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
Name: params.Name,
NodeClusterId: params.PrimaryClusterId,
SecondaryNodeClusterIds: secondaryClusterIds,
IsOn: params.IsOn,
Level: params.Level,
LnAddrs: lnAddrs,
EnableIPLists: params.EnableIPLists,
})
if err != nil {
this.ErrorPage(err)
return
}
// 禁用老的IP地址
_, err = this.RPC().NodeIPAddressRPC().DisableAllNodeIPAddressesWithNodeId(this.AdminContext(), &pb.DisableAllNodeIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
// 添加新的IP地址
err = ipaddressutils.UpdateNodeIPAddresses(this.Parent(), params.NodeId, nodeconfigs.NodeRoleNode, params.IPAddressesJSON)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,117 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
)
type UpdateDNSPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateDNSPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateDNSPopupAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
dnsInfo := dnsInfoResp.Node
if dnsInfo == nil {
this.NotFound("node", params.NodeId)
return
}
this.Data["ipAddr"] = dnsInfo.IpAddr
this.Data["routes"] = domainutils.ConvertRoutesToMaps(dnsInfo)
this.Data["domainId"] = dnsInfo.DnsDomainId
this.Data["domainName"] = dnsInfo.DnsDomainName
// 读取所有线路
allRouteMaps := []maps.Map{}
if dnsInfo.DnsDomainId > 0 {
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: dnsInfo.DnsDomainId})
if err != nil {
this.ErrorPage(err)
return
}
if len(routesResp.Routes) > 0 {
for _, route := range routesResp.Routes {
allRouteMaps = append(allRouteMaps, maps.Map{
"name": route.Name,
"code": route.Code,
"domainName": dnsInfo.DnsDomainName,
"domainId": dnsInfo.DnsDomainId,
})
}
// 筛选
var routes = domainutils.FilterRoutes(dnsInfo.Routes, routesResp.Routes)
dnsInfo.Routes = routes
this.Data["routes"] = domainutils.ConvertRoutesToMaps(dnsInfo)
}
}
this.Data["allRoutes"] = allRouteMaps
this.Show()
}
func (this *UpdateDNSPopupAction) RunPost(params struct {
NodeId int64
IpAddr string
DomainId int64
DnsRoutesJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 操作日志
defer this.CreateLogInfo(codes.NodeDNS_LogUpdateNodeDNS, params.NodeId)
routes := []string{}
if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &routes)
if err != nil {
this.ErrorPage(err)
return
}
}
params.Must.
Field("ipAddr", params.IpAddr).
Require("请输入IP地址")
if net.ParseIP(params.IpAddr) == nil {
this.FailField("ipAddr", "请输入正确的IP地址")
}
// 执行修改
_, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{
NodeId: params.NodeId,
IpAddr: params.IpAddr,
DnsDomainId: params.DomainId,
Routes: routes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,30 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateInstallStatusAction struct {
actionutils.ParentAction
}
func (this *UpdateInstallStatusAction) RunPost(params struct {
NodeId int64
IsInstalled bool
}) {
// 创建日志
defer this.CreateLogInfo(codes.Node_LogUpdateNodeInstallationStatus, params.NodeId)
_, err := this.RPC().NodeRPC().UpdateNodeIsInstalled(this.AdminContext(), &pb.UpdateNodeIsInstalledRequest{
NodeId: params.NodeId,
IsInstalled: params.IsInstalled,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateIsOnAction struct {
actionutils.ParentAction
}
func (this *UpdateIsOnAction) RunPost(params struct {
NodeId int64
IsOn bool
}) {
if params.IsOn {
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOn, params.NodeId)
} else {
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOff, params.NodeId)
}
_, err := this.RPC().NodeRPC().UpdateNodeIsOn(this.AdminContext(), &pb.UpdateNodeIsOnRequest{
NodeId: params.NodeId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package node
func (this *UpdateAction) CanUpdateLevel(level int32) bool {
return level <= 1
}

View File

@@ -0,0 +1,13 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package node
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
)
func (this *UpdateAction) CanUpdateLevel(level int32) bool {
return (teaconst.IsPlus && plus.AllowComponent(plus.ComponentCodeL2Node)) || level <= 1
}

View File

@@ -0,0 +1,335 @@
package cluster
import (
"encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"time"
)
type NodesAction struct {
actionutils.ParentAction
}
func (this *NodesAction) Init() {
this.Nav("", "node", "index")
this.SecondMenu("nodes")
}
func (this *NodesAction) RunGet(params struct {
ClusterId int64
GroupId int64
RegionId int64
InstalledState int
ActiveState int
Keyword string
Level int32
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
LoadOrder string
ConnectionsOrder string
}) {
this.Data["groupId"] = params.GroupId
this.Data["regionId"] = params.RegionId
this.Data["installState"] = params.InstalledState
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level
this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0 || len(params.ConnectionsOrder) > 0
// 集群是否已经设置了线路
clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasClusterDNS"] = clusterDNSResp.Domain != nil
// 数量
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countAll"] = countAllResp.Count
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
Level: params.Level,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
var req = &pb.ListEnabledNodesMatchRequest{
Offset: page.Offset,
Size: page.Size,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
Level: params.Level,
InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
}
if params.CpuOrder == "asc" {
req.CpuAsc = true
} else if params.CpuOrder == "desc" {
req.CpuDesc = true
} else if params.MemoryOrder == "asc" {
req.MemoryAsc = true
} else if params.MemoryOrder == "desc" {
req.MemoryDesc = true
} else if params.TrafficInOrder == "asc" {
req.TrafficInAsc = true
} else if params.TrafficInOrder == "desc" {
req.TrafficInDesc = true
} else if params.TrafficOutOrder == "asc" {
req.TrafficOutAsc = true
} else if params.TrafficOutOrder == "desc" {
req.TrafficOutDesc = true
} else if params.LoadOrder == "asc" {
req.LoadAsc = true
} else if params.LoadOrder == "desc" {
req.LoadDesc = true
} else if params.ConnectionsOrder == "asc" {
req.ConnectionsAsc = true
} else if params.ConnectionsOrder == "desc" {
req.ConnectionsDesc = true
}
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
if err != nil {
this.ErrorPage(err)
return
}
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.Nodes {
// 状态
var isSynced = false
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
logs.Error(err)
continue
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
isSynced = status.ConfigVersion == node.Version
}
// IP
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: node.Id,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
var ipAddresses = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses {
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddresses = append(ipAddresses, maps.Map{
"id": addr.Id,
"name": addr.Name,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
"isUp": addr.IsUp,
"isOn": addr.IsOn,
"clusters": addrClusterMaps,
})
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// DNS
dnsRouteNames := []string{}
for _, route := range node.DnsRoutes {
dnsRouteNames = append(dnsRouteNames, route.Name)
}
// 从集群
var secondaryClusterMaps []maps.Map
for _, secondaryCluster := range node.SecondaryNodeClusters {
secondaryClusterMaps = append(secondaryClusterMaps, maps.Map{
"id": secondaryCluster.Id,
"name": secondaryCluster.Name,
"isOn": secondaryCluster.IsOn,
})
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"isInstalled": node.IsInstalled,
"isOn": node.IsOn,
"isUp": node.IsUp,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"offlineDay": node.OfflineDay,
"installStatus": maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
"countConnections": status.ConnectionCount,
},
"cluster": maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
},
"secondaryClusters": secondaryClusterMaps,
"isSynced": isSynced,
"ipAddresses": ipAddresses,
"group": groupMap,
"region": regionMap,
"dnsRouteNames": dnsRouteNames,
"level": node.Level,
})
}
this.Data["nodes"] = nodeMaps
// 所有分组
var groupMaps = []maps.Map{}
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, group := range groupsResp.NodeGroups {
countNodesInGroupResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
NodeGroupId: group.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countNodesInGroupResp.Count
groupName := group.Name
if countNodes > 0 {
groupName += "(" + types.String(countNodes) + ")"
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": groupName,
"countNodes": countNodes,
})
}
// 是否有未分组
if len(groupMaps) > 0 {
countNodesInGroupResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: params.ClusterId,
NodeGroupId: -1,
})
if err != nil {
this.ErrorPage(err)
return
}
var countUngroupNodes = countNodesInGroupResp.Count
if countUngroupNodes > 0 {
groupMaps = append([]maps.Map{
{
"id": -1,
"name": "[" + this.Lang(codes.Node_UngroupedLabel) + "](" + types.String(countUngroupNodes) + ")",
"countNodes": countUngroupNodes,
},
}, groupMaps...)
}
}
this.Data["groups"] = groupMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
// 级别
this.Data["levels"] = []maps.Map{}
if teaconst.IsPlus {
this.Data["levels"] = nodeconfigs.FindAllNodeLevels()
}
// 记录最近访问
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
ItemType: "cluster",
ItemId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,76 @@
package cache
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("cache")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
cluster, err := dao.SharedNodeClusterDAO.FindEnabledNodeCluster(this.AdminContext(), params.ClusterId)
if err != nil {
this.ErrorPage(err)
return
}
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
// 缓存设置
this.Data["cachePolicy"] = nil
if cluster.HttpCachePolicyId > 0 {
cachePolicy, err := dao.SharedHTTPCachePolicyDAO.FindEnabledHTTPCachePolicy(this.AdminContext(), cluster.HttpCachePolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if cachePolicy != nil {
this.Data["cachePolicy"] = maps.Map{
"id": cachePolicy.Id,
"name": cachePolicy.Name,
"isOn": cachePolicy.IsOn,
}
}
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
CachePolicyId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ServerCache_LogUpdateClusterCachePolicy, params.ClusterId, params.CachePolicyId)
if params.CachePolicyId <= 0 {
this.Fail("请选择缓存策略")
}
_, err := this.RPC().NodeClusterRPC().UpdateNodeClusterHTTPCachePolicyId(this.AdminContext(), &pb.UpdateNodeClusterHTTPCachePolicyIdRequest{
NodeClusterId: params.ClusterId,
HttpCachePolicyId: params.CachePolicyId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,173 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cc
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("cc")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
httpCCResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterHTTPCCPolicy(this.AdminContext(), &pb.FindEnabledNodeClusterHTTPCCPolicyRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var defaultThresholds = serverconfigs.CloneDefaultHTTPCCThresholds()
this.Data["defaultThresholds"] = defaultThresholds
var thresholds = []*serverconfigs.HTTPCCThreshold{}
for range defaultThresholds {
thresholds = append(thresholds, serverconfigs.NewHTTPCCThreshold())
}
this.Data["thresholds"] = thresholds
if len(httpCCResp.HttpCCPolicyJSON) == 0 {
this.Data["httpCCPolicy"] = nodeconfigs.NewHTTPCCPolicy()
} else {
var config = nodeconfigs.NewHTTPCCPolicy()
err = json.Unmarshal(httpCCResp.HttpCCPolicyJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
for index, threshold := range config.Thresholds {
if index < len(thresholds) {
thresholds[index].Merge(threshold)
}
}
this.Data["httpCCPolicy"] = config
}
this.Data["defaultHTTPCCPolicyMaxConnectionsPerIP"] = nodeconfigs.HTTPCCPolicyMaxConnectionsPerIP
this.Data["defaultHTTPCCPolicyRedirectsCheckingValidatePath"] = nodeconfigs.HTTPCCPolicyRedirectsCheckingValidatePath
this.Data["defaultHTTPCCPolicyRedirectsCheckingDurationSeconds"] = nodeconfigs.HTTPCCPolicyRedirectsCheckingDurationSeconds
this.Data["defaultHTTPCCPolicyRedirectsCheckingMaxRedirects"] = nodeconfigs.HTTPCCPolicyRedirectsCheckingMaxRedirects
this.Data["defaultHTTPCCPolicyRedirectsCheckingBlockSeconds"] = nodeconfigs.HTTPCCPolicyRedirectsCheckingBlockSeconds
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
IsOn bool
ThresholdPeriodSeconds []int32
ThresholdMaxRequests []int32
ThresholdBlockSeconds []int32
MaxConnectionsPerIP int
RedirectsCheckingValidatePath string
RedirectsCheckingDurationSeconds int
RedirectsCheckingMaxRedirects int
RedirectsCheckingBlockSeconds int
FirewallScope string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ServerCC_LogUpdateClusterHTTPCCPolicy, params.ClusterId)
var config = nodeconfigs.NewHTTPCCPolicy()
config.IsOn = params.IsOn
// 阈值
var thresholds = []*serverconfigs.HTTPCCThreshold{}
var countThresholdPeriodSeconds = len(params.ThresholdPeriodSeconds)
if countThresholdPeriodSeconds == len(params.ThresholdMaxRequests) && countThresholdPeriodSeconds == len(params.ThresholdBlockSeconds) {
for index, periodSeconds := range params.ThresholdPeriodSeconds {
var maxRequests = params.ThresholdMaxRequests[index]
var blockSeconds = params.ThresholdBlockSeconds[index]
var threshold = serverconfigs.NewHTTPCCThreshold()
if periodSeconds >= 0 {
threshold.PeriodSeconds = periodSeconds
}
if maxRequests >= 0 {
threshold.MaxRequests = maxRequests
}
if blockSeconds >= 0 {
threshold.BlockSeconds = blockSeconds
}
thresholds = append(thresholds, threshold)
}
}
config.Thresholds = thresholds
// 连接数
if params.MaxConnectionsPerIP > 0 {
config.MaxConnectionsPerIP = params.MaxConnectionsPerIP
} else {
config.MaxConnectionsPerIP = 0
}
// 跳转
if len(params.RedirectsCheckingValidatePath) > 0 && !regexp.MustCompile(`^/[/.\w-]+$`).MatchString(params.RedirectsCheckingValidatePath) {
this.FailField("redirectsCheckingValidatePath", "跳转检测路径必须是一个常规URL")
return
}
config.RedirectsChecking.ValidatePath = params.RedirectsCheckingValidatePath
if params.RedirectsCheckingDurationSeconds > 0 {
config.RedirectsChecking.DurationSeconds = params.RedirectsCheckingDurationSeconds
} else {
config.RedirectsChecking.DurationSeconds = 0
}
if params.RedirectsCheckingMaxRedirects > 0 {
config.RedirectsChecking.MaxRedirects = params.RedirectsCheckingMaxRedirects
} else {
config.RedirectsChecking.MaxRedirects = 0
}
if params.RedirectsCheckingBlockSeconds > 0 {
config.RedirectsChecking.BlockSeconds = params.RedirectsCheckingBlockSeconds
} else {
config.RedirectsChecking.BlockSeconds = 0
}
// 防火墙
config.Firewall.Scope = params.FirewallScope
err := config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterHTTPCCPolicy(this.AdminContext(), &pb.UpdateNodeClusterHTTPCCPolicyRequest{
NodeClusterId: params.ClusterId,
HttpCCPolicyJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,111 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ddosProtection
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"net"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("ddosProtection")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
protectionResp, err := this.RPC().NodeClusterRPC().FindNodeClusterDDoSProtection(this.AdminContext(), &pb.FindNodeClusterDDoSProtectionRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var ddosProtectionConfig = ddosconfigs.DefaultProtectionConfig()
if len(protectionResp.DdosProtectionJSON) > 0 {
err = json.Unmarshal(protectionResp.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["config"] = ddosProtectionConfig
this.Data["defaultConfigs"] = nodeconfigs.DefaultConfigs
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
DdosProtectionJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.DDoSProtection_LogUpdateClusterDDoSProtection, params.ClusterId)
var ddosProtectionConfig = &ddosconfigs.ProtectionConfig{}
err := json.Unmarshal(params.DdosProtectionJSON, ddosProtectionConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = ddosProtectionConfig.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
// 校验参数
if ddosProtectionConfig.TCP != nil {
var tcpConfig = ddosProtectionConfig.TCP
if tcpConfig.MaxConnectionsPerIP > 0 && tcpConfig.MaxConnectionsPerIP < nodeconfigs.DefaultTCPMinConnectionsPerIP {
this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP))
}
if tcpConfig.NewConnectionsMinutelyRate > 0 && tcpConfig.NewConnectionsMinutelyRate < nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate {
this.FailField("tcpNewConnectionsMinutelyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate))
}
if tcpConfig.NewConnectionsSecondlyRate > 0 && tcpConfig.NewConnectionsSecondlyRate < nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate {
this.FailField("tcpNewConnectionsSecondlyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate))
}
// Port
for _, portConfig := range tcpConfig.Ports {
if portConfig.Port > 65535 {
this.Fail("端口号" + types.String(portConfig.Port) + "不能大于65535")
}
}
// IP
for _, ipConfig := range tcpConfig.AllowIPList {
if net.ParseIP(ipConfig.IP) == nil {
this.Fail("白名单IP '" + ipConfig.IP + "' 格式错误")
}
}
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterDDoSProtection(this.AdminContext(), &pb.UpdateNodeClusterDDoSProtectionRequest{
NodeClusterId: params.ClusterId,
DdosProtectionJSON: params.DdosProtectionJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,71 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ddosProtection
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
"github.com/iwind/TeaGo/maps"
)
type StatusAction struct {
actionutils.ParentAction
}
func (this *StatusAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("ddosProtection")
}
func (this *StatusAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Show()
}
func (this *StatusAction) RunPost(params struct {
ClusterId int64
}) {
results, err := nodeutils.SendMessageToCluster(this.AdminContext(), params.ClusterId, messageconfigs.MessageCodeCheckLocalFirewall, &messageconfigs.CheckLocalFirewallMessage{
Name: "nftables",
}, 10, false)
if err != nil {
this.ErrorPage(err)
return
}
var resultMaps = []maps.Map{}
for _, result := range results {
var resultMap = maps.Map{
"isOk": result.IsOK,
"message": result.Message,
"nodeId": result.NodeId,
"nodeName": result.NodeName,
}
nodeResp, err := this.RPC().NodeRPC().FindNodeDDoSProtection(this.AdminContext(), &pb.FindNodeDDoSProtectionRequest{NodeId: result.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
if len(nodeResp.DdosProtectionJSON) > 0 {
var ddosProtection = ddosconfigs.DefaultProtectionConfig()
err = json.Unmarshal(nodeResp.DdosProtectionJSON, ddosProtection)
if err != nil {
this.ErrorPage(err)
return
}
resultMap["isPrior"] = !ddosProtection.IsPriorEmpty()
}
resultMaps = append(resultMaps, resultMap)
}
this.Data["results"] = resultMaps
this.Success()
}

View File

@@ -0,0 +1,141 @@
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("dns")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
// 是否有域名可选
hasDomainsResp, err := this.RPC().DNSDomainRPC().ExistAvailableDomains(this.AdminContext(), &pb.ExistAvailableDomainsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasDomains"] = hasDomainsResp.Exist
// 当前集群的DNS信息
this.Data["domainId"] = 0
this.Data["domainName"] = ""
this.Data["dnsName"] = ""
dnsInfoResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsName"] = dnsInfoResp.Name
this.Data["nodesAutoSync"] = dnsInfoResp.NodesAutoSync
this.Data["serversAutoSync"] = dnsInfoResp.ServersAutoSync
var domainProviderMap = maps.Map{
"id": 0,
"name": "",
}
if dnsInfoResp.Domain != nil {
this.Data["domainId"] = dnsInfoResp.Domain.Id
this.Data["domainName"] = dnsInfoResp.Domain.Name
if dnsInfoResp.Provider != nil {
domainProviderMap = maps.Map{
"id": dnsInfoResp.Provider.Id,
"name": dnsInfoResp.Provider.Name,
}
}
}
this.Data["domainProvider"] = domainProviderMap
if len(dnsInfoResp.CnameRecords) == 0 {
this.Data["cnameRecords"] = []string{}
} else {
this.Data["cnameRecords"] = dnsInfoResp.CnameRecords
}
this.Data["ttl"] = dnsInfoResp.Ttl
this.Data["cnameAsDomain"] = dnsInfoResp.CnameAsDomain
this.Data["includingLnNodes"] = dnsInfoResp.IncludingLnNodes
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
DnsDomainId int64
DnsName string
NodesAutoSync bool
ServersAutoSync bool
CnameRecords []string
Ttl int32
CnameAsDomain bool
IncludingLnNodes bool
ConfirmResetDomain bool // 是否确认重置域名
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 创建日志
defer this.CreateLogInfo(codes.DNS_LogUpdateClusterDNS, params.ClusterId)
if !params.ConfirmResetDomain {
if params.DnsDomainId <= 0 {
this.Fail("请选择集群的主域名")
}
params.Must.
Field("dnsName", params.DnsName).
Require("请输入DNS子域名")
}
// 检查DNS名称
if len(params.DnsName) > 0 {
if !domainutils.ValidateDomainFormat(params.DnsName) {
this.FailField("dnsName", "请输入正确的DNS子域名")
}
// 检查是否已经被使用
resp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSName(this.AdminContext(), &pb.CheckNodeClusterDNSNameRequest{
NodeClusterId: params.ClusterId,
DnsName: params.DnsName,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.IsUsed {
this.FailField("dnsName", "此DNS子域名已经被使用请换一个再试")
}
}
_, err := this.RPC().NodeClusterRPC().UpdateNodeClusterDNS(this.AdminContext(), &pb.UpdateNodeClusterDNSRequest{
NodeClusterId: params.ClusterId,
DnsName: params.DnsName,
DnsDomainId: params.DnsDomainId,
NodesAutoSync: params.NodesAutoSync,
ServersAutoSync: params.ServersAutoSync,
CnameRecords: params.CnameRecords,
Ttl: params.Ttl,
CnameAsDomain: params.CnameAsDomain,
IncludingLnNodes: params.IncludingLnNodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,18 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/rands"
)
type RandomNameAction struct {
actionutils.ParentAction
}
func (this *RandomNameAction) RunPost(params struct{}) {
this.Data["name"] = "cluster" + rands.HexString(8)
this.Success()
}

View File

@@ -0,0 +1,303 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type RecordsAction struct {
actionutils.ParentAction
}
func (this *RecordsAction) Init() {
this.Nav("", "setting", "records")
this.SecondMenu("dns")
}
func (this *RecordsAction) RunGet(params struct {
ClusterId int64
}) {
// 集群信息
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
this.Data["cluster"] = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
}
// DNS信息
dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var defaultRoute = dnsResp.DefaultRoute
var domainName = ""
var dnsMap = maps.Map{
"dnsName": dnsResp.Name,
"domainId": 0,
"domainName": "",
"providerId": 0,
"providerName": "",
"providerTypeName": "",
}
if dnsResp.Domain != nil {
domainName = dnsResp.Domain.Name
dnsMap["domainId"] = dnsResp.Domain.Id
dnsMap["domainName"] = dnsResp.Domain.Name
}
if dnsResp.Provider != nil {
dnsMap["providerId"] = dnsResp.Provider.Id
dnsMap["providerName"] = dnsResp.Provider.Name
dnsMap["providerTypeName"] = dnsResp.Provider.TypeName
}
if len(dnsResp.CnameRecords) > 0 {
dnsMap["cnameRecords"] = dnsResp.CnameRecords
} else {
dnsMap["cnameRecords"] = []string{}
}
this.Data["dnsInfo"] = dnsMap
// 未安装的节点
notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: false,
})
if err != nil {
this.ErrorPage(err)
return
}
var allNodes = notInstalledNodesResp.Nodes
// 节点DNS解析记录
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var installedNodeIdsMap = map[int64]bool{}
for _, node := range nodesResp.Nodes {
installedNodeIdsMap[node.Id] = true
}
allNodes = append(allNodes, nodesResp.Nodes...)
var nodeMaps = []maps.Map{}
for _, node := range allNodes {
var isInstalled = installedNodeIdsMap[node.Id]
if len(node.Routes) > 0 {
for _, route := range node.Routes {
// 检查是否已解析
var isResolved = false
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
var recordType = "A"
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: cluster.DnsName,
Type: recordType,
Route: route.Code,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": route.Name,
"code": route.Code,
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
} else {
// 默认线路
var isResolved = false
if isInstalled && len(defaultRoute) > 0 {
var recordType = "A"
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: cluster.DnsName,
Type: recordType,
Route: defaultRoute,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": "",
"code": "",
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
}
this.Data["nodes"] = nodeMaps
// 代理服务解析记录
serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers {
// 检查是否已解析
isResolved := false
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(server.DnsName) > 0 && len(domainName) > 0 {
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: server.DnsName,
Type: "CNAME",
Value: cluster.DnsName + "." + domainName,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
serverMaps = append(serverMaps, maps.Map{
"id": server.Id,
"name": server.Name,
"dnsName": server.DnsName,
"isResolved": isResolved,
})
}
this.Data["servers"] = serverMaps
// 检查解析记录是否有变化
checkChangesResp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSChanges(this.AdminContext(), &pb.CheckNodeClusterDNSChangesRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsHasChanges"] = checkChangesResp.IsChanged
// 需要解决的问题
issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var issueMaps = []maps.Map{}
for _, issue := range issuesResp.Issues {
issueMaps = append(issueMaps, maps.Map{
"target": issue.Target,
"targetId": issue.TargetId,
"type": issue.Type,
"description": issue.Description,
"params": issue.Params,
})
}
this.Data["issues"] = issueMaps
// 当前正在执行的任务
resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var taskMaps = []maps.Map{}
for _, task := range resp.DnsTasks {
var clusterMap maps.Map = nil
var nodeMap maps.Map = nil
var serverMap maps.Map = nil
var domainMap maps.Map = nil
if task.NodeCluster != nil {
clusterMap = maps.Map{
"id": task.NodeCluster.Id,
"name": task.NodeCluster.Name,
}
}
if task.Node != nil {
nodeMap = maps.Map{
"id": task.Node.Id,
"name": task.Node.Name,
}
}
if task.Server != nil {
serverMap = maps.Map{
"id": task.Server.Id,
"name": task.Server.Name,
}
}
if task.DnsDomain != nil {
domainMap = maps.Map{
"id": task.DnsDomain.Id,
"name": task.DnsDomain.Name,
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"type": task.Type,
"isDone": task.IsDone,
"isOk": task.IsOk,
"error": task.Error,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
"cluster": clusterMap,
"node": nodeMap,
"server": serverMap,
"domain": domainMap,
})
}
this.Data["tasks"] = taskMaps
this.Show()
}

View File

@@ -0,0 +1,136 @@
package firewallActions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Data["actionTypes"] = firewallconfigs.FindAllFirewallActionTypes()
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
Name string
EventLevel string
Type string
// ipset
IpsetWhiteName string
IpsetBlackName string
IpsetWhiteNameIPv6 string
IpsetBlackNameIPv6 string
IpsetAutoAddToIPTables bool
IpsetAutoAddToFirewalld bool
// script
ScriptPath string
// http api
HttpAPIURL string
// html
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.WAFAction_LogCreateWAFAction, params.ClusterId)
params.Must.
Field("name", params.Name).
Require("请输入动作名称").
Field("type", params.Type).
Require("请选择动作类型")
var actionParams interface{} = nil
switch params.Type {
case firewallconfigs.FirewallActionTypeIPSet:
params.Must.
Field("ipsetWhiteName", params.IpsetWhiteName).
Require("请输入IPSet白名单名称").
Match(`^\w+$`, "请输入正确的IPSet白名单名称").
Field("ipsetBlackName", params.IpsetBlackName).
Require("请输入IPSet黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet黑名单名称").
Field("ipsetWhiteNameIPv6", params.IpsetWhiteNameIPv6).
Require("请输入IPSet IPv6白名单名称").
Match(`^\w+$`, "请输入正确的IPSet IPv6白名单名称").
Field("ipsetBlackNameIPv6", params.IpsetBlackNameIPv6).
Require("请输入IPSet IPv6黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet IPv6黑名单名称")
actionParams = &firewallconfigs.FirewallActionIPSetConfig{
WhiteName: params.IpsetWhiteName,
BlackName: params.IpsetBlackName,
WhiteNameIPv6: params.IpsetWhiteNameIPv6,
BlackNameIPv6: params.IpsetBlackNameIPv6,
AutoAddToIPTables: params.IpsetAutoAddToIPTables,
AutoAddToFirewalld: params.IpsetAutoAddToFirewalld,
}
case firewallconfigs.FirewallActionTypeIPTables:
actionParams = &firewallconfigs.FirewallActionIPTablesConfig{}
case firewallconfigs.FirewallActionTypeFirewalld:
actionParams = &firewallconfigs.FirewallActionFirewalldConfig{}
case firewallconfigs.FirewallActionTypeScript:
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
actionParams = &firewallconfigs.FirewallActionScriptConfig{
Path: params.ScriptPath,
}
case firewallconfigs.FirewallActionTypeHTTPAPI:
params.Must.
Field("httpAPIURL", params.HttpAPIURL).
Require("请输入API URL").
Match(`^(http|https):`, "API地址必须以http://或https://开头")
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: params.HttpAPIURL,
}
case firewallconfigs.FirewallActionTypeHTML:
params.Must.
Field("htmlContent", params.HtmlContent).
Require("请输入HTML内容")
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
Content: params.HtmlContent,
}
default:
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
}
actionParamsJSON, err := json.Marshal(actionParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterFirewallActionRPC().CreateNodeClusterFirewallAction(this.AdminContext(), &pb.CreateNodeClusterFirewallActionRequest{
NodeClusterId: params.ClusterId,
Name: params.Name,
EventLevel: params.EventLevel,
Type: params.Type,
ParamsJSON: actionParamsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,25 @@
package firewallActions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ActionId int64
}) {
defer this.CreateLogInfo(codes.WAFAction_LogDeleteWAFAction, params.ActionId)
_, err := this.RPC().NodeClusterFirewallActionRPC().DeleteNodeClusterFirewallAction(this.AdminContext(), &pb.DeleteNodeClusterFirewallActionRequest{NodeClusterFirewallActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,65 @@
package firewallActions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("firewallAction")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
actionsResp, err := this.RPC().NodeClusterFirewallActionRPC().FindAllEnabledNodeClusterFirewallActions(this.AdminContext(), &pb.FindAllEnabledNodeClusterFirewallActionsRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
levelMaps := map[string][]maps.Map{} // level => actionMaps
for _, action := range actionsResp.NodeClusterFirewallActions {
actionMaps, ok := levelMaps[action.EventLevel]
if !ok {
actionMaps = []maps.Map{}
}
actionMaps = append(actionMaps, maps.Map{
"id": action.Id,
"name": action.Name,
"type": action.Type,
"typeName": firewallconfigs.FindFirewallActionTypeName(action.Type),
})
levelMaps[action.EventLevel] = actionMaps
}
levelMaps2 := []maps.Map{} // []levelMap
hasActions := false
for _, level := range firewallconfigs.FindAllFirewallEventLevels() {
actionMaps, ok := levelMaps[level.Code]
if !ok {
actionMaps = []maps.Map{}
} else {
hasActions = true
}
levelMaps2 = append(levelMaps2, maps.Map{
"name": level.Name,
"code": level.Code,
"actions": actionMaps,
})
}
this.Data["levels"] = levelMaps2
this.Data["hasActions"] = hasActions
this.Show()
}

View File

@@ -0,0 +1,165 @@
package firewallActions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ActionId int64
}) {
actionResp, err := this.RPC().NodeClusterFirewallActionRPC().FindEnabledNodeClusterFirewallAction(this.AdminContext(), &pb.FindEnabledNodeClusterFirewallActionRequest{NodeClusterFirewallActionId: params.ActionId})
if err != nil {
this.ErrorPage(err)
return
}
action := actionResp.NodeClusterFirewallAction
if action == nil {
this.NotFound("nodeClusterFirewallAction", params.ActionId)
return
}
actionParams := maps.Map{}
if len(action.ParamsJSON) > 0 {
err = json.Unmarshal(action.ParamsJSON, &actionParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["action"] = maps.Map{
"id": action.Id,
"name": action.Name,
"eventLevel": action.EventLevel,
"params": actionParams,
"type": action.Type,
}
// 通用参数
this.Data["actionTypes"] = firewallconfigs.FindAllFirewallActionTypes()
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ActionId int64
Name string
EventLevel string
Type string
// ipset
IpsetWhiteName string
IpsetBlackName string
IpsetWhiteNameIPv6 string
IpsetBlackNameIPv6 string
IpsetAutoAddToIPTables bool
IpsetAutoAddToFirewalld bool
// script
ScriptPath string
// http api
HttpAPIURL string
// HTML内容
HtmlContent string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.WAFAction_LogUpdateWAFAction, params.ActionId)
params.Must.
Field("name", params.Name).
Require("请输入动作名称").
Field("type", params.Type).
Require("请选择动作类型")
var actionParams interface{} = nil
switch params.Type {
case firewallconfigs.FirewallActionTypeIPSet:
params.Must.
Field("ipsetWhiteName", params.IpsetWhiteName).
Require("请输入IPSet白名单名称").
Match(`^\w+$`, "请输入正确的IPSet白名单名称").
Field("ipsetBlackName", params.IpsetBlackName).
Require("请输入IPSet黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet黑名单名称").
Field("ipsetWhiteNameIPv6", params.IpsetWhiteNameIPv6).
Require("请输入IPSet IPv6白名单名称").
Match(`^\w+$`, "请输入正确的IPSet IPv6白名单名称").
Field("ipsetBlackNameIPv6", params.IpsetBlackNameIPv6).
Require("请输入IPSet IPv6黑名单名称").
Match(`^\w+$`, "请输入正确的IPSet IPv6黑名单名称")
actionParams = &firewallconfigs.FirewallActionIPSetConfig{
WhiteName: params.IpsetWhiteName,
BlackName: params.IpsetBlackName,
WhiteNameIPv6: params.IpsetWhiteNameIPv6,
BlackNameIPv6: params.IpsetBlackNameIPv6,
AutoAddToIPTables: params.IpsetAutoAddToIPTables,
AutoAddToFirewalld: params.IpsetAutoAddToFirewalld,
}
case firewallconfigs.FirewallActionTypeIPTables:
actionParams = &firewallconfigs.FirewallActionIPTablesConfig{}
case firewallconfigs.FirewallActionTypeFirewalld:
actionParams = &firewallconfigs.FirewallActionFirewalldConfig{}
case firewallconfigs.FirewallActionTypeScript:
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
actionParams = &firewallconfigs.FirewallActionScriptConfig{
Path: params.ScriptPath,
}
case firewallconfigs.FirewallActionTypeHTTPAPI:
params.Must.
Field("httpAPIURL", params.HttpAPIURL).
Require("请输入API URL").
Match(`^(http|https):`, "API地址必须以http://或https://开头")
actionParams = &firewallconfigs.FirewallActionHTTPAPIConfig{
URL: params.HttpAPIURL,
}
case firewallconfigs.FirewallActionTypeHTML:
params.Must.
Field("htmlContent", params.HtmlContent).
Require("请输入HTML内容")
actionParams = &firewallconfigs.FirewallActionHTMLConfig{
Content: params.HtmlContent,
}
default:
this.Fail("选择的类型'" + params.Type + "'暂时不支持")
}
actionParamsJSON, err := json.Marshal(actionParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterFirewallActionRPC().UpdateNodeClusterFirewallAction(this.AdminContext(), &pb.UpdateNodeClusterFirewallActionRequest{
NodeClusterFirewallActionId: params.ActionId,
Name: params.Name,
EventLevel: params.EventLevel,
Type: params.Type,
ParamsJSON: actionParamsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,280 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package globalServerConfig
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("globalServerConfig")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterGlobalServerConfig(this.AdminContext(), &pb.FindNodeClusterGlobalServerConfigRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var configJSON = configResp.GlobalServerConfigJSON
var config = serverconfigs.NewGlobalServerConfig()
if len(configJSON) > 0 {
err = json.Unmarshal(configJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["config"] = config
var httpAllDomainMismatchActionCode = serverconfigs.DomainMismatchActionPage
var httpAllDomainMismatchActionContentHTML string
var httpAllDomainMismatchActionStatusCode = "404"
var httpAllDomainMismatchActionRedirectURL = ""
if config.HTTPAll.DomainMismatchAction != nil {
httpAllDomainMismatchActionCode = config.HTTPAll.DomainMismatchAction.Code
if config.HTTPAll.DomainMismatchAction.Options != nil {
// 即使是非 page 处理动作,也读取这些内容,以便于在切换到 page 时,可以顺利读取到先前的设置
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
if statusCode > 0 {
httpAllDomainMismatchActionStatusCode = types.String(statusCode)
}
if config.HTTPAll.DomainMismatchAction.Code == serverconfigs.DomainMismatchActionRedirect {
httpAllDomainMismatchActionRedirectURL = config.HTTPAll.DomainMismatchAction.Options.GetString("url")
}
}
} else {
httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>404 not found</title>
<style>
* { font-family: Roboto, system-ui, sans-serif; }
h3, p { text-align: center; }
p { color: grey; }
</style>
</head>
<body>
<h3>Error: 404 Page Not Found</h3>
<h3>找不到您要访问的页面。</h3>
<p>原因:找不到当前访问域名对应的网站,请联系网站管理员。</p>
</body>
</html>`
}
this.Data["httpAllDomainMismatchActionCode"] = httpAllDomainMismatchActionCode
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode
this.Data["httpAllDomainMismatchActionRedirectURL"] = httpAllDomainMismatchActionRedirectURL
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
HttpAllMatchDomainStrictly bool
HttpAllDomainMismatchActionCode string
HttpAllDomainMismatchActionContentHTML string
HttpAllDomainMismatchActionStatusCode string
HttpAllDomainMismatchActionRedirectURL string
HttpAllAllowMismatchDomainsJSON []byte
HttpAllAllowNodeIP bool
HttpAllDefaultDomain string
HttpAllNodeIPPageHTML string
HttpAllNodeIPShowPage bool
HttpAllEnableServerAddrVariable bool
HttpAllRequestOriginsWithEncodings bool
HttpAllXFFMaxAddresses int
HttpAllDomainAuditingIsOn bool
HttpAllDomainAuditingPrompt string
HttpAllServerName string
HttpAllSupportsLowVersionHTTP bool
HttpAllMatchCertFromAllServers bool
HttpAllForceLnRequest bool
HttpAllLnRequestSchedulingMethod string
HttpAccessLogIsOn bool
HttpAccessLogEnableRequestHeaders bool
HttpAccessLogEnableResponseHeaders bool
HttpAccessLogCommonRequestHeadersOnly bool
HttpAccessLogEnableCookies bool
HttpAccessLogEnableServerNotFound bool
LogRecordServerError bool
PerformanceAutoReadTimeout bool
PerformanceAutoWriteTimeout bool
PerformanceDebug bool
// TCP端口设置
TcpAllPortRangeMin int
TcpAllPortRangeMax int
TcpAllDenyPorts []int
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ServerGlobalSetting_LogUpdateClusterGlobalServerConfig, params.ClusterId)
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterGlobalServerConfig(this.AdminContext(), &pb.FindNodeClusterGlobalServerConfigRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var configJSON = configResp.GlobalServerConfigJSON
var config = serverconfigs.NewGlobalServerConfig()
if len(configJSON) > 0 {
err = json.Unmarshal(configJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
var domainMisMatchStatusCodeString = params.HttpAllDomainMismatchActionStatusCode
if !regexp.MustCompile(`^\d{3}$`).MatchString(domainMisMatchStatusCodeString) {
this.FailField("httpAllDomainMismatchActionContentStatusCode", "请输入正确的状态码")
return
}
var domainMisMatchStatusCode = types.Int(domainMisMatchStatusCodeString)
config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly
// validate
if config.HTTPAll.MatchDomainStrictly {
// validate redirect
if params.HttpAllDomainMismatchActionCode == serverconfigs.DomainMismatchActionRedirect {
if len(params.HttpAllDomainMismatchActionRedirectURL) == 0 {
this.FailField("httpAllDomainMismatchActionRedirectURL", "请输入跳转目标网址URL")
return
}
if !regexp.MustCompile(`(?i)(http|https)://`).MatchString(params.HttpAllDomainMismatchActionRedirectURL) {
this.FailField("httpAllDomainMismatchActionRedirectURL", "目标网址URL必须以http://或https://开头")
return
}
}
}
config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
Code: params.HttpAllDomainMismatchActionCode,
Options: maps.Map{
"statusCode": domainMisMatchStatusCode, // page
"contentHTML": params.HttpAllDomainMismatchActionContentHTML, // page
"url": params.HttpAllDomainMismatchActionRedirectURL, // redirect
},
}
var allowMismatchDomains = []string{}
if len(params.HttpAllAllowMismatchDomainsJSON) > 0 {
err = json.Unmarshal(params.HttpAllAllowMismatchDomainsJSON, &allowMismatchDomains)
if err != nil {
this.ErrorPage(err)
return
}
}
// 域名
config.HTTPAll.AllowMismatchDomains = allowMismatchDomains
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
config.HTTPAll.NodeIPShowPage = params.HttpAllNodeIPShowPage
config.HTTPAll.NodeIPPageHTML = params.HttpAllNodeIPPageHTML
config.HTTPAll.DomainAuditingIsOn = params.HttpAllDomainAuditingIsOn
config.HTTPAll.DomainAuditingPrompt = params.HttpAllDomainAuditingPrompt
// HTTP All
config.HTTPAll.ServerName = params.HttpAllServerName
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest
config.HTTPAll.LnRequestSchedulingMethod = params.HttpAllLnRequestSchedulingMethod
config.HTTPAll.EnableServerAddrVariable = params.HttpAllEnableServerAddrVariable
config.HTTPAll.RequestOriginsWithEncodings = params.HttpAllRequestOriginsWithEncodings
config.HTTPAll.XFFMaxAddresses = params.HttpAllXFFMaxAddresses
// 访问日志
config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders
config.HTTPAccessLog.CommonRequestHeadersOnly = params.HttpAccessLogCommonRequestHeadersOnly
config.HTTPAccessLog.EnableCookies = params.HttpAccessLogEnableCookies
config.HTTPAccessLog.EnableServerNotFound = params.HttpAccessLogEnableServerNotFound
// 日志
config.Log.RecordServerError = params.LogRecordServerError
// TCP
if params.TcpAllPortRangeMin < 1024 {
params.TcpAllPortRangeMin = 1024
}
if params.TcpAllPortRangeMax > 65534 {
params.TcpAllPortRangeMax = 65534
} else if params.TcpAllPortRangeMax < 1024 {
params.TcpAllPortRangeMax = 1024
}
if params.TcpAllPortRangeMin > params.TcpAllPortRangeMax {
params.TcpAllPortRangeMin, params.TcpAllPortRangeMax = params.TcpAllPortRangeMax, params.TcpAllPortRangeMin
}
config.TCPAll.DenyPorts = params.TcpAllDenyPorts
config.TCPAll.PortRangeMin = params.TcpAllPortRangeMin
config.TCPAll.PortRangeMax = params.TcpAllPortRangeMax
// 性能
config.Performance.AutoReadTimeout = params.PerformanceAutoReadTimeout
config.Performance.AutoWriteTimeout = params.PerformanceAutoWriteTimeout
config.Performance.Debug = params.PerformanceDebug
err = config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
configJSON, err = json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterGlobalServerConfig(this.AdminContext(), &pb.UpdateNodeClusterGlobalServerConfigRequest{
NodeClusterId: params.ClusterId,
GlobalServerConfigJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package health
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"net"
"strings"
)
type CheckDomainAction struct {
actionutils.ParentAction
}
func (this *CheckDomainAction) RunPost(params struct {
Host string
ClusterId int64
}) {
this.Data["isOk"] = true // 默认为TRUE
var host = params.Host
if len(host) > 0 &&
!strings.Contains(host, "{") /** 包含变量 **/ {
h, _, err := net.SplitHostPort(host)
if err == nil && len(h) > 0 {
host = h
}
// 是否为IP
if net.ParseIP(host) != nil {
this.Success()
return
}
host = strings.ToLower(host)
resp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
NodeClusterId: params.ClusterId,
ServerNames: []string{host},
SupportWildcard: true,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(resp.DuplicatedServerNames) == 0 {
this.Data["isOk"] = false
}
}
this.Success()
}

View File

@@ -0,0 +1,67 @@
package health
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("health")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
configResp, err := this.RPC().NodeClusterRPC().FindNodeClusterHealthCheckConfig(this.AdminContext(), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var config *serverconfigs.HealthCheckConfig = nil
if len(configResp.HealthCheckJSON) > 0 {
config = &serverconfigs.HealthCheckConfig{}
err = json.Unmarshal(configResp.HealthCheckJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["healthCheckConfig"] = config
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
HealthCheckJSON []byte
Must *actions.Must
}) {
// 创建日志
defer this.CreateLogInfo(codes.NodeCluster_LogUpdateClusterHealthCheck, params.ClusterId)
config := &serverconfigs.HealthCheckConfig{}
err := json.Unmarshal(params.HealthCheckJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterHealthCheck(this.AdminContext(), &pb.UpdateNodeClusterHealthCheckRequest{
NodeClusterId: params.ClusterId,
HealthCheckJSON: params.HealthCheckJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,50 @@
package health
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type RunPopupAction struct {
actionutils.ParentAction
}
func (this *RunPopupAction) Init() {
this.Nav("", "", "")
}
func (this *RunPopupAction) RunGet(params struct {
ClusterId int64
}) {
// 检查是否已部署服务
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasServers"] = countServersResp.Count > 0
this.Show()
}
func (this *RunPopupAction) RunPost(params struct {
ClusterId int64
Must *actions.Must
}) {
// 创建日志
defer this.CreateLogInfo(codes.NodeCluster_LogRunClusterHealthCheck, params.ClusterId)
resp, err := this.RPC().NodeClusterRPC().ExecuteNodeClusterHealthCheck(this.AdminContext(), &pb.ExecuteNodeClusterHealthCheckRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.Fail(err.Error())
}
if resp.Results == nil {
resp.Results = []*pb.ExecuteNodeClusterHealthCheckResponse_Result{}
}
this.Data["results"] = resp.Results
this.Success()
}

View File

@@ -0,0 +1,92 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package http3
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("http3")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
http3Resp, err := this.RPC().NodeClusterRPC().FindNodeClusterHTTP3Policy(this.AdminContext(), &pb.FindNodeClusterHTTP3PolicyRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if len(http3Resp.Http3PolicyJSON) == 0 {
this.Data["http3Policy"] = nodeconfigs.NewHTTP3Policy()
} else {
var config = nodeconfigs.NewHTTP3Policy()
err = json.Unmarshal(http3Resp.Http3PolicyJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["http3Policy"] = config
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
IsOn bool
Port int
SupportMobileBrowsers bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ServerHTTP3_LogUpdateClusterHTTP3Policy, params.ClusterId)
if params.Port <= 0 || params.Port > 1024 {
this.FailField("port", "请输入1-1024之间的端口号")
return
}
var config = nodeconfigs.NewHTTP3Policy()
config.IsOn = params.IsOn
config.Port = params.Port
config.SupportMobileBrowsers = params.SupportMobileBrowsers
err := config.Init()
if err != nil {
this.Fail("配置校验失败:")
}
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterHTTP3Policy(this.AdminContext(), &pb.UpdateNodeClusterHTTP3PolicyRequest{
NodeClusterId: params.ClusterId,
Http3PolicyJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,172 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package http3
import (
"context"
"crypto/tls"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/taskutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"net/http"
"net/url"
"time"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "setting", "test")
this.SecondMenu("http3")
}
func (this *TestAction) RunGet(params struct {
ClusterId int64
}) {
http3Resp, err := this.RPC().NodeClusterRPC().FindNodeClusterHTTP3Policy(this.AdminContext(), &pb.FindNodeClusterHTTP3PolicyRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if len(http3Resp.Http3PolicyJSON) == 0 {
this.Data["http3Policy"] = nodeconfigs.NewHTTP3Policy()
} else {
var config = nodeconfigs.NewHTTP3Policy()
err = json.Unmarshal(http3Resp.Http3PolicyJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["http3Policy"] = config
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
ClusterId int64
Port int
URL string `alias:"url"`
}) {
var testingURL = params.URL
if len(testingURL) == 0 {
this.FailField("url", "请输入测试网址")
return
}
u, err := url.Parse(testingURL)
if err != nil {
this.FailField("url", "测试网址格式不正确")
return
}
if u.Scheme != "https" {
this.FailField("url", "测试网址必须以https://开头")
return
}
type Result struct {
NodeId int64 `json:"nodeId"`
NodeName string `json:"nodeName"`
IP string `json:"ip"`
IsOk bool `json:"isOk"`
Error string `json:"error"`
}
var results = []*Result{}
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IncludeSecondary: false,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.Nodes {
if !node.IsOn {
continue
}
addrsResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: node.Id,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, addr := range addrsResp.NodeIPAddresses {
if !addr.IsOn || !addr.CanAccess {
continue
}
results = append(results, &Result{
NodeId: node.Id,
NodeName: node.Name,
IP: addr.Ip,
IsOk: false,
Error: "",
})
}
}
var totalResults = len(results)
if totalResults == 0 {
this.Data["results"] = results
this.Success()
return
}
err = taskutils.RunConcurrent(results, 16, func(task any) {
var result = task.(*Result)
var client = &http.Client{
Transport: &http3.RoundTripper{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
var realAddr = configutils.QuoteIP(result.IP) + ":" + types.String(params.Port)
return quic.DialAddrEarly(ctx, realAddr, tlsCfg, cfg)
},
},
Timeout: 5 * time.Second,
}
defer client.CloseIdleConnections()
req, err := http.NewRequest(http.MethodGet, testingURL, nil)
if err != nil {
result.Error = "validate url failed: " + err.Error()
return
}
resp, err := client.Do(req)
if err != nil {
result.Error = "request url failed: " + err.Error()
return
}
defer func() {
_ = resp.Body.Close()
}()
result.IsOk = true
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["results"] = results
this.Success()
}

View File

@@ -0,0 +1,216 @@
package settings
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("basic")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
// 基本信息
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
cluster := clusterResp.NodeCluster
if cluster == nil {
this.WriteString("not found cluster")
return
}
// 认证
var grantMap interface{} = nil
if cluster.NodeGrantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: cluster.NodeGrantId})
if err != nil {
this.ErrorPage(err)
return
}
var grant = grantResp.NodeGrant
if grant != nil {
grantMap = maps.Map{
"id": grant.Id,
"name": grant.Name,
"method": grant.Method,
"methodName": grantutils.FindGrantMethodName(grant.Method, this.LangCode()),
}
}
}
this.Data["grant"] = grantMap
// 时区
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
if len(cluster.TimeZone) == 0 {
cluster.TimeZone = nodeconfigs.DefaultTimeZoneLocation
}
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(cluster.TimeZone)
// 时钟
var clockConfig = nodeconfigs.DefaultClockConfig()
if len(cluster.ClockJSON) > 0 {
err = json.Unmarshal(cluster.ClockJSON, clockConfig)
if err != nil {
this.ErrorPage(err)
return
}
if clockConfig == nil {
clockConfig = nodeconfigs.DefaultClockConfig()
}
}
// SSH参数
var sshParams = nodeconfigs.DefaultSSHParams()
if len(cluster.SshParamsJSON) > 0 {
err = json.Unmarshal(cluster.SshParamsJSON, sshParams)
if err != nil {
this.ErrorPage(err)
return
}
}
// DNS信息
var fullDomainName = ""
if len(cluster.DnsName) > 0 && cluster.DnsDomainId > 0 {
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{DnsDomainId: cluster.DnsDomainId})
if err != nil {
this.ErrorPage(err)
return
}
if domainResp.DnsDomain != nil {
fullDomainName = cluster.DnsName + "." + domainResp.DnsDomain.Name
}
}
this.Data["cluster"] = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
"timeZone": cluster.TimeZone,
"nodeMaxThreads": cluster.NodeMaxThreads,
"autoOpenPorts": cluster.AutoOpenPorts,
"clock": clockConfig,
"autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables,
"autoSystemTuning": cluster.AutoSystemTuning,
"autoTrimDisks": cluster.AutoTrimDisks,
"maxConcurrentReads": cluster.MaxConcurrentReads,
"maxConcurrentWrites": cluster.MaxConcurrentWrites,
"sshParams": sshParams,
"domainName": fullDomainName,
}
// 默认值
this.Data["defaultNodeMaxThreads"] = nodeconfigs.DefaultMaxThreads
this.Data["defaultNodeMaxThreadsMin"] = nodeconfigs.DefaultMaxThreadsMin
this.Data["defaultNodeMaxThreadsMax"] = nodeconfigs.DefaultMaxThreadsMax
this.Show()
}
// RunPost 保存设置
func (this *IndexAction) RunPost(params struct {
ClusterId int64
Name string
GrantId int64
SshParamsPort int
InstallDir string
TimeZone string
NodeMaxThreads int32
AutoOpenPorts bool
ClockAutoSync bool
ClockServer string
ClockCheckChrony bool
AutoRemoteStart bool
AutoInstallNftables bool
AutoSystemTuning bool
AutoTrimDisks bool
MaxConcurrentReads int32
MaxConcurrentWrites int32
Must *actions.Must
}) {
// 创建日志
defer this.CreateLogInfo(codes.NodeCluster_LogUpdateClusterBasicSettings, params.ClusterId)
params.Must.
Field("name", params.Name).
Require("请输入集群名称")
if params.NodeMaxThreads > 0 {
params.Must.
Field("nodeMaxThreads", params.NodeMaxThreads).
Gte(int64(nodeconfigs.DefaultMaxThreadsMin), "单节点最大线程数最小值不能小于"+types.String(nodeconfigs.DefaultMaxThreadsMin)).
Lte(int64(nodeconfigs.DefaultMaxThreadsMax), "单节点最大线程数最大值不能大于"+types.String(nodeconfigs.DefaultMaxThreadsMax))
}
// ssh
var sshParams = nodeconfigs.DefaultSSHParams()
sshParams.Port = params.SshParamsPort
sshParamsJSON, err := json.Marshal(sshParams)
if err != nil {
this.ErrorPage(err)
return
}
// clock
var clockConfig = nodeconfigs.DefaultClockConfig()
clockConfig.AutoSync = params.ClockAutoSync
clockConfig.Server = params.ClockServer
clockConfig.CheckChrony = params.ClockCheckChrony
clockConfigJSON, err := json.Marshal(clockConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = clockConfig.Init()
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeCluster(this.AdminContext(), &pb.UpdateNodeClusterRequest{
NodeClusterId: params.ClusterId,
Name: params.Name,
NodeGrantId: params.GrantId,
InstallDir: params.InstallDir,
TimeZone: params.TimeZone,
NodeMaxThreads: params.NodeMaxThreads,
AutoOpenPorts: params.AutoOpenPorts,
ClockJSON: clockConfigJSON,
AutoRemoteStart: params.AutoRemoteStart,
AutoInstallNftables: params.AutoInstallNftables,
AutoSystemTuning: params.AutoSystemTuning,
AutoTrimDisks: params.AutoTrimDisks,
SshParamsJSON: sshParamsJSON,
MaxConcurrentReads: params.MaxConcurrentReads,
MaxConcurrentWrites: params.MaxConcurrentWrites,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,80 @@
package settings
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cache"
ddosProtection "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/ddos-protection"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/dns"
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
globalServerConfig "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/global-server-config"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/metrics"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/waf"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/webp"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Prefix("/clusters/cluster/settings").
Data("teaSubMenu", "cluster").
GetPost("", new(IndexAction)).
// 健康检查
GetPost("/health", new(health.IndexAction)).
GetPost("/health/runPopup", new(health.RunPopupAction)).
Post("/health/checkDomain", new(health.CheckDomainAction)).
// 缓存
GetPost("/cache", new(cache.IndexAction)).
// WAF
GetPost("/waf", new(waf.IndexAction)).
// DNS
Prefix("/clusters/cluster/settings/dns").
GetPost("", new(dns.IndexAction)).
Get("/records", new(dns.RecordsAction)).
Post("/randomName", new(dns.RandomNameAction)).
// 系统服务设置
Prefix("/clusters/cluster/settings/services").
GetPost("", new(services.IndexAction)).
GetPost("/status", new(services.StatusAction)).
// 防火墙动作
Prefix("/clusters/cluster/settings/firewall-actions").
Get("", new(firewallActions.IndexAction)).
GetPost("/createPopup", new(firewallActions.CreatePopupAction)).
GetPost("/updatePopup", new(firewallActions.UpdatePopupAction)).
Post("/delete", new(firewallActions.DeleteAction)).
// 指标
Prefix("/clusters/cluster/settings/metrics").
Get("", new(metrics.IndexAction)).
GetPost("/createPopup", new(metrics.CreatePopupAction)).
Post("/delete", new(metrics.DeleteAction)).
// WebP
Prefix("/clusters/cluster/settings/webp").
GetPost("", new(webp.IndexAction)).
// DDOS Protection
Prefix("/clusters/cluster/settings/ddos-protection").
GetPost("", new(ddosProtection.IndexAction)).
GetPost("/status", new(ddosProtection.StatusAction)).
// 全局服务配置
Prefix("/clusters/cluster/settings/global-server-config").
GetPost("", new(globalServerConfig.IndexAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package settings
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
schedulesettings "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/schedule"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Data("teaSubMenu", "cluster").
Prefix("/clusters/cluster/settings").
// 消息
Prefix("/clusters/cluster/settings/message").
GetPost("", new(message.IndexAction)).
Get("/selectReceiverPopup", new(message.SelectReceiverPopupAction)).
Post("/selectedReceivers", new(message.SelectedReceiversAction)).
// 阈值
Prefix("/clusters/cluster/settings/thresholds").
Get("", new(thresholds.IndexAction)).
GetPost("/createPopup", new(thresholds.CreatePopupAction)).
GetPost("/updatePopup", new(thresholds.UpdatePopupAction)).
Post("/delete", new(thresholds.DeleteAction)).
// 智能调度
Prefix("/clusters/cluster/settings/schedule").
Get("", new(schedulesettings.IndexAction)).
// TOA
Prefix("/clusters/cluster/settings/toa").
GetPost("", new(toa.IndexAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,78 @@
//go:build plus
package message
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("message")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
ReceiversJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.MessageReceiver_LogUpdateClusterMessageReceivers, params.ClusterId)
receiverMaps := []maps.Map{}
if len(params.ReceiversJSON) > 0 {
err := json.Unmarshal(params.ReceiversJSON, &receiverMaps)
if err != nil {
this.ErrorPage(err)
return
}
}
pbReceiverOptions := &pb.UpdateMessageReceiversRequest_RecipientOptions{}
for _, receiverMap := range receiverMaps {
recipientId := int64(0)
groupId := int64(0)
receiverType := receiverMap.GetString("type")
switch receiverType {
case "recipient":
recipientId = receiverMap.GetInt64("id")
case "group":
groupId = receiverMap.GetInt64("id")
default:
continue
}
pbReceiverOptions.RecipientOptions = append(pbReceiverOptions.RecipientOptions, &pb.UpdateMessageReceiversRequest_RecipientOption{
MessageRecipientId: recipientId,
MessageRecipientGroupId: groupId,
})
}
_, err := this.RPC().MessageReceiverRPC().UpdateMessageReceivers(this.AdminContext(), &pb.UpdateMessageReceiversRequest{
NodeClusterId: params.ClusterId,
NodeId: 0,
ServerId: 0,
ParamsJSON: nil,
RecipientOptions: map[string]*pb.UpdateMessageReceiversRequest_RecipientOptions{
"*": pbReceiverOptions,
},
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,79 @@
//go:build plus
package message
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type SelectReceiverPopupAction struct {
actionutils.ParentAction
}
func (this *SelectReceiverPopupAction) Init() {
this.Nav("", "", "")
}
func (this *SelectReceiverPopupAction) RunGet(params struct {
RecipientIds string
GroupIds string
}) {
recipientIds := utils.SplitNumbers(params.RecipientIds)
groupIds := utils.SplitNumbers(params.GroupIds)
// 所有接收人
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
Offset: 0,
Size: 1000, // TODO 支持搜索
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if !recipient.IsOn {
continue
}
if lists.ContainsInt64(recipientIds, recipient.Id) {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"name": recipient.Admin.Fullname,
"instanceName": recipient.MessageMediaInstance.Name,
})
}
this.Data["recipients"] = recipientMaps
// 所有分组
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
if !group.IsOn {
continue
}
if lists.ContainsInt64(groupIds, group.Id) {
continue
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,62 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package message
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type SelectedReceiversAction struct {
actionutils.ParentAction
}
func (this *SelectedReceiversAction) Init() {
this.Nav("", "", "")
}
func (this *SelectedReceiversAction) RunPost(params struct {
ClusterId int64
NodeId int64
ServerId int64
}) {
receiversResp, err := this.RPC().MessageReceiverRPC().FindAllEnabledMessageReceivers(this.AdminContext(), &pb.FindAllEnabledMessageReceiversRequest{
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
receiverMaps := []maps.Map{}
for _, receiver := range receiversResp.MessageReceivers {
id := int64(0)
name := ""
receiverType := ""
subName := ""
if receiver.MessageRecipient != nil {
id = receiver.MessageRecipient.Id
name = receiver.MessageRecipient.Admin.Fullname
subName = receiver.MessageRecipient.MessageMediaInstance.Name
receiverType = "recipient"
} else if receiver.MessageRecipientGroup != nil {
id = receiver.MessageRecipientGroup.Id
name = receiver.MessageRecipientGroup.Name
receiverType = "group"
} else {
continue
}
receiverMaps = append(receiverMaps, maps.Map{
"id": id,
"name": name,
"subName": subName,
"type": receiverType,
})
}
this.Data["receivers"] = receiverMaps
this.Success()
}

View File

@@ -0,0 +1,101 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
ClusterId int64
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
this.Data["clusterId"] = params.ClusterId
countResp, err := this.RPC().MetricItemRPC().CountAllEnabledMetricItems(this.AdminContext(), &pb.CountAllEnabledMetricItemsRequest{Category: params.Category})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().MetricItemRPC().ListEnabledMetricItems(this.AdminContext(), &pb.ListEnabledMetricItemsRequest{
Category: params.Category,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
// 是否已添加
existsResp, err := this.RPC().NodeClusterMetricItemRPC().ExistsNodeClusterMetricItem(this.AdminContext(), &pb.ExistsNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: item.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
var exists = existsResp.Exists
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"code": item.Code,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
"keys": item.Keys,
"value": item.Value,
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
"category": item.Category,
"isPublic": item.IsPublic,
"isChecked": exists,
})
}
this.Data["items"] = itemMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
ItemId int64
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.MetricItem_LogAddMetricItemToCluster, params.ItemId, params.ClusterId)
_, err := this.RPC().NodeClusterMetricItemRPC().EnableNodeClusterMetricItem(this.AdminContext(), &pb.EnableNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: params.ItemId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,31 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
ItemId int64
}) {
defer this.CreateLogInfo(codes.MetricItem_LogDeleteMetricItemFromCluster, params.ClusterId, params.ItemId)
_, err := this.RPC().NodeClusterMetricItemRPC().DisableNodeClusterMetricItem(this.AdminContext(), &pb.DisableNodeClusterMetricItemRequest{
NodeClusterId: params.ClusterId,
MetricItemId: params.ItemId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,59 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("metric")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
itemsResp, err := this.RPC().NodeClusterMetricItemRPC().FindAllNodeClusterMetricItems(this.AdminContext(), &pb.FindAllNodeClusterMetricItemsRequest{
NodeClusterId: params.ClusterId,
Category: params.Category,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"code": item.Code,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
"keys": item.Keys,
"value": item.Value,
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
"category": item.Category,
"isPublic": item.IsPublic,
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -0,0 +1,95 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package networksecurity
import (
"encoding/json"
"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/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("networkSecurity")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
policyResp, err := this.RPC().NodeClusterRPC().FindNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.FindNodeClusterNetworkSecurityPolicyRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var policy = nodeconfigs.NewNetworkSecurityPolicy()
if len(policyResp.NetworkSecurityPolicyJSON) > 0 {
err = json.Unmarshal(policyResp.NetworkSecurityPolicyJSON, policy)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["policy"] = policy
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
Status string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
policyResp, err := this.RPC().NodeClusterRPC().FindNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.FindNodeClusterNetworkSecurityPolicyRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var policy = nodeconfigs.NewNetworkSecurityPolicy()
if len(policyResp.NetworkSecurityPolicyJSON) > 0 {
err = json.Unmarshal(policyResp.NetworkSecurityPolicyJSON, policy)
if err != nil {
this.ErrorPage(err)
return
}
}
policy.Status = params.Status
err = policy.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
policyJSON, err := json.Marshal(policy)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.UpdateNodeClusterNetworkSecurityPolicyRequest{
NodeClusterId: params.ClusterId,
NetworkSecurityPolicyJSON: policyJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,93 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package pages
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("pages")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
if !teaconst.IsPlus {
return
}
pagesResp, err := this.RPC().NodeClusterRPC().FindNodeClusterHTTPPagesPolicy(this.AdminContext(), &pb.FindNodeClusterHTTPPagesPolicyRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if len(pagesResp.HttpPagesPolicyJSON) == 0 {
this.Data["pagesPolicy"] = nodeconfigs.NewHTTPPagesPolicy()
} else {
var config = nodeconfigs.NewHTTPPagesPolicy()
err = json.Unmarshal(pagesResp.HttpPagesPolicyJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["pagesPolicy"] = config
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
IsOn bool
PagesJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ServerPage_LogUpdateClusterPages, params.ClusterId)
var pagePolicy = nodeconfigs.NewHTTPPagesPolicy()
pagePolicy.IsOn = params.IsOn
err := json.Unmarshal(params.PagesJSON, &pagePolicy.Pages)
if err != nil {
this.Fail("解析配置失败:" + err.Error())
return
}
err = pagePolicy.Init()
if err != nil {
this.Fail("校验配置失败:" + err.Error())
return
}
pagePolicyJSON, err := json.Marshal(pagePolicy)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterHTTPPagesPolicy(this.AdminContext(), &pb.UpdateNodeClusterHTTPPagesPolicyRequest{
NodeClusterId: params.ClusterId,
HttpPagesPolicyJSON: pagePolicyJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,104 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package schedulesettings
import (
"encoding/json"
"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"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("schedule")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
// 当前集群信息
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
// 节点信息
nodesResp, err := this.RPC().NodeRPC().FindAllNodeScheduleInfoWithNodeClusterId(this.AdminContext(), &pb.FindAllNodeScheduleInfoWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
// 排序
var pbNodes = nodesResp.Nodes
sort.Slice(pbNodes, func(i, j int) bool {
var backup1 = 0
var backup2 = 0
if pbNodes[i].IsBackupForCluster || pbNodes[i].IsBackupForGroup {
backup1 = 0
}
if pbNodes[j].IsBackupForCluster || pbNodes[j].IsBackupForGroup {
backup2 = 0
}
if backup1 != backup2 {
return backup1 > backup2
}
return pbNodes[i].NodeId < pbNodes[j].NodeId
})
var nodeMaps = []maps.Map{}
for _, node := range pbNodes {
// 是否已下线
var isOffline = len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd")
// 备用IP
if node.BackupIPs == nil {
node.BackupIPs = []string{}
}
// 当前动作状态
var actionStatus = &nodeconfigs.NodeActionStatus{}
if len(node.ActionStatusJSON) > 0 {
err = json.Unmarshal(node.ActionStatusJSON, actionStatus)
if err != nil {
this.ErrorPage(err)
return
}
}
nodeMaps = append(nodeMaps, maps.Map{
"clusterName": cluster.Name,
"clusterId": cluster.Id,
"nodeId": node.NodeId,
"nodeName": node.NodeName,
"groupId": node.NodeGroupId,
"groupName": node.NodeGroupName,
"offlineDay": node.OfflineDay,
"isOffline": isOffline,
"isBackupForCluster": node.IsBackupForCluster,
"isBackupForGroup": node.IsBackupForGroup,
"backupIPs": node.BackupIPs,
"actionStatus": actionStatus,
})
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,78 @@
package services
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("service")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
serviceParamsResp, err := this.RPC().NodeClusterRPC().FindNodeClusterSystemService(this.AdminContext(), &pb.FindNodeClusterSystemServiceRequest{
NodeClusterId: params.ClusterId,
Type: nodeconfigs.SystemServiceTypeSystemd,
})
if err != nil {
this.ErrorPage(err)
return
}
paramsJSON := serviceParamsResp.ParamsJSON
if len(paramsJSON) == 0 {
this.Data["systemdIsOn"] = false
} else {
config := &nodeconfigs.SystemdServiceConfig{}
err = json.Unmarshal(paramsJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["systemdIsOn"] = config.IsOn
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
SystemdIsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeSystemd_LogUpdateClusterSystemdSettings, params.ClusterId)
serviceParams := &nodeconfigs.SystemdServiceConfig{
IsOn: params.SystemdIsOn,
}
serviceParamsJSON, err := json.Marshal(serviceParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterSystemService(this.AdminContext(), &pb.UpdateNodeClusterSystemServiceRequest{
NodeClusterId: params.ClusterId,
Type: nodeconfigs.SystemServiceTypeSystemd,
ParamsJSON: serviceParamsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,37 @@
package services
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
"github.com/iwind/TeaGo/actions"
)
type StatusAction struct {
actionutils.ParentAction
}
func (this *StatusAction) Init() {
this.Nav("", "setting", "status")
this.SecondMenu("service")
}
func (this *StatusAction) RunGet(params struct {
}) {
this.Show()
}
func (this *StatusAction) RunPost(params struct {
ClusterId int64
Must *actions.Must
}) {
results, err := nodeutils.SendMessageToCluster(this.AdminContext(), params.ClusterId, messageconfigs.MessageCodeCheckSystemdService, &messageconfigs.CheckSystemdServiceMessage{}, 10, false)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["results"] = results
this.Success()
}

View File

@@ -0,0 +1,81 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package thresholds
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["items"] = nodeconfigs.FindAllNodeValueItemDefinitions()
this.Data["operators"] = nodeconfigs.FindAllNodeValueOperatorDefinitions()
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
NodeId int64
Item string
Param string
SumMethod string
Operator string
Value string
Duration int32
DurationUnit string
Message string
NotifyDuration int32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
if params.ClusterId <= 0 && params.NodeId >= 0 {
this.Fail("集群或者节点至少需要填写其中一个参数")
}
valueJSON, err := json.Marshal(params.Value)
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().NodeThresholdRPC().CreateNodeThreshold(this.AdminContext(), &pb.CreateNodeThresholdRequest{
Role: "node",
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
Item: params.Item,
Param: params.Param,
Operator: params.Operator,
ValueJSON: valueJSON,
Message: params.Message,
Duration: params.Duration,
DurationUnit: params.DurationUnit,
SumMethod: params.SumMethod,
NotifyDuration: params.NotifyDuration,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.NodeThreshold_LogCreateNodeThreshold, resp.NodeThresholdId)
this.Success()
}

View File

@@ -0,0 +1,29 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package thresholds
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// DeleteAction 删除阈值
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ThresholdId int64
}) {
defer this.CreateLogInfo(codes.NodeThreshold_LogDeleteNodeThreshold, params.ThresholdId)
_, err := this.RPC().NodeThresholdRPC().DeleteNodeThreshold(this.AdminContext(), &pb.DeleteNodeThresholdRequest{NodeThresholdId: params.ThresholdId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,63 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package thresholds
import (
"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"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("threshold")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
// 列出所有阈值
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: params.ClusterId,
NodeId: 0,
})
if err != nil {
this.ErrorPage(err)
return
}
thresholdMaps := []maps.Map{}
for _, threshold := range thresholdsResp.NodeThresholds {
var nodeMap maps.Map = nil
if threshold.Node != nil {
nodeMap = maps.Map{
"id": threshold.Node.Id,
"name": threshold.Node.Name,
}
}
thresholdMaps = append(thresholdMaps, maps.Map{
"id": threshold.Id,
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
"paramIsPercent": nodeconfigs.CheckNodeValueItemParamIsPercent(threshold.Item, threshold.Param),
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
"duration": threshold.Duration,
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
"isOn": threshold.IsOn,
"node": nodeMap,
})
}
this.Data["thresholds"] = thresholdMaps
this.Show()
}

View File

@@ -0,0 +1,108 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package thresholds
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ThresholdId int64
}) {
// 通用参数
this.Data["items"] = nodeconfigs.FindAllNodeValueItemDefinitions()
this.Data["operators"] = nodeconfigs.FindAllNodeValueOperatorDefinitions()
// 阈值详情
thresholdResp, err := this.RPC().NodeThresholdRPC().FindEnabledNodeThreshold(this.AdminContext(), &pb.FindEnabledNodeThresholdRequest{NodeThresholdId: params.ThresholdId})
if err != nil {
this.ErrorPage(err)
return
}
threshold := thresholdResp.NodeThreshold
if threshold == nil {
this.NotFound("nodeThreshold", params.ThresholdId)
return
}
valueInterface := new(interface{})
err = json.Unmarshal(threshold.ValueJSON, valueInterface)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["threshold"] = maps.Map{
"id": threshold.Id,
"item": threshold.Item,
"param": threshold.Param,
"message": threshold.Message,
"notifyDuration": threshold.NotifyDuration,
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
"operator": threshold.Operator,
"duration": threshold.Duration,
"durationUnit": threshold.DurationUnit,
"isOn": threshold.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ThresholdId int64
Item string
Param string
SumMethod string
Operator string
Value string
Duration int32
DurationUnit string
Message string
NotifyDuration int32
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeThreshold_LogUpdateNodeThreshold, params.ThresholdId)
valueJSON, err := json.Marshal(params.Value)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeThresholdRPC().UpdateNodeThreshold(this.AdminContext(), &pb.UpdateNodeThresholdRequest{
NodeThresholdId: params.ThresholdId,
Item: params.Item,
Param: params.Param,
Operator: params.Operator,
ValueJSON: valueJSON,
Message: params.Message,
NotifyDuration: params.NotifyDuration,
Duration: params.Duration,
DurationUnit: params.DurationUnit,
SumMethod: params.SumMethod,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,96 @@
//go:build plus
package toa
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.SecondMenu("toa")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
if !teaconst.IsPlus {
return
}
toaResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterTOA(this.AdminContext(), &pb.FindEnabledNodeClusterTOARequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if len(toaResp.ToaJSON) == 0 {
this.Data["toa"] = nodeconfigs.NewTOAConfig()
} else {
var config = nodeconfigs.NewTOAConfig()
err = json.Unmarshal(toaResp.ToaJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["toa"] = config
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
IsOn bool
AutoSetup bool
OptionTypeV4 uint8
OptionTypeV6 uint8
MinQueueId uint8
MaxQueueId uint8
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeTOA_LogUpdateClusterTOA, params.ClusterId)
var config = nodeconfigs.TOAConfig{
IsOn: params.IsOn,
Debug: false, // 暂时不允许打开调试
OptionTypeV4: params.OptionTypeV4,
OptionTypeV6: params.OptionTypeV6,
MinQueueId: params.MinQueueId,
AutoSetup: params.AutoSetup,
}
err := config.Init()
if err != nil {
this.Fail("配置验证失败:" + err.Error())
return
}
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterTOA(this.AdminContext(), &pb.UpdateNodeClusterTOARequest{
NodeClusterId: params.ClusterId,
ToaJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

Some files were not shown because too many files have changed in this diff Show More