Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boardutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
var CountTodayAttacks int64 = 0
var CountTodayAttacksRead int64
func InitBoard(parent *actionutils.ParentAction) error {
countResp, err := parent.RPC().NodeLogRPC().CountAllUnreadNodeLogs(parent.AdminContext(), &pb.CountAllUnreadNodeLogsRequest{})
if err != nil {
return err
}
parent.Data["countEvents"] = countResp.Count
parent.Data["countTodayAttacks"] = CountTodayAttacks
parent.Data["countTodayAttacksFormat"] = numberutils.FormatCount(CountTodayAttacks)
parent.Data["countTodayAttacksRead"] = CountTodayAttacksRead
return nil
}

View File

@@ -0,0 +1,169 @@
// 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/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type DnsAction struct {
actionutils.ParentAction
}
func (this *DnsAction) Init() {
this.Nav("", "", "dns")
}
func (this *DnsAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
// 初始化
err := boardutils.InitBoard(this.Parent())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countDomains": 0,
"countRecords": 0,
"countClusters": 0,
"countNodes": 0,
"countOfflineNodes": 0,
}
this.Show()
}
func (this *DnsAction) RunPost(params struct{}) {
resp, err := this.RPC().NSRPC().ComposeNSBoard(this.AdminContext(), &pb.ComposeNSBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countDomains": resp.CountNSDomains,
"countRecords": resp.CountNSRecords,
"countClusters": resp.CountNSClusters,
"countNodes": resp.CountNSNodes,
"countOfflineNodes": resp.CountOfflineNSNodes,
}
// 流量排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["hourlyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["dailyStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNSDomainStats {
statMaps = append(statMaps, maps.Map{
"domainId": stat.NsDomainId,
"domainName": stat.NsDomainName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = statMaps
}
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNSNodeStats {
statMaps = append(statMaps, maps.Map{
"clusterId": stat.NsClusterId,
"nodeId": stat.NsNodeId,
"nodeName": stat.NsNodeName,
"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
}
this.Success()
}

View File

@@ -0,0 +1,241 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
"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 EventsAction struct {
actionutils.ParentAction
}
func (this *EventsAction) Init() {
this.Nav("", "", "event")
}
func (this *EventsAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
this.Data["keyword"] = ""
// 初始化
err := boardutils.InitBoard(this.Parent())
if err != nil {
this.ErrorPage(err)
return
}
// 分页
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
IsUnread: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
this.Data["autoDeleted"] = false
var logMaps = []maps.Map{}
for i := 0; i < 100; i++ {
currentLogMaps, goNext, err := this.listLogs(page.Offset, page.Size)
if err != nil {
this.ErrorPage(err)
return
}
if goNext {
this.Data["autoDeleted"] = true
} else {
logMaps = currentLogMaps
break
}
}
this.Data["logs"] = logMaps
this.Show()
}
func (this *EventsAction) listLogs(offset int64, size int64) (logMaps []maps.Map, goNext bool, resultErr error) {
logMaps = []maps.Map{}
// 单页
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
IsUnread: true,
Offset: offset,
Size: size,
})
if err != nil {
return nil, false, err
}
if len(logsResp.NodeLogs) == 0 {
return
}
for _, log := range logsResp.NodeLogs {
var logMap = maps.Map{
"id": log.Id,
"role": log.Role,
"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,
}
switch log.Role {
case nodeconfigs.NodeRoleNode:
// 节点信息
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.Node
if node == nil || node.NodeCluster == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"cluster": maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
},
"name": node.Name,
}
// 服务信息
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 {
return nil, false, err
}
var server = serverResp.Server
if server != nil {
serverMap = maps.Map{"id": server.Id, "name": server.Name}
}
}
logMap["server"] = serverMap
case nodeconfigs.NodeRoleAPI:
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.ApiNode
if node == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
case nodeconfigs.NodeRoleDNS:
// 节点信息
nodeResp, err := this.RPC().NSNodeRPC().FindNSNode(this.AdminContext(), &pb.FindNSNodeRequest{NsNodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.NsNode
if node == nil || node.NsCluster == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"cluster": maps.Map{
"id": node.NsCluster.Id,
"name": node.NsCluster.Name,
},
"name": node.Name,
}
case nodeconfigs.NodeRoleReport:
nodeResp, err := this.RPC().ReportNodeRPC().FindEnabledReportNode(this.AdminContext(), &pb.FindEnabledReportNodeRequest{ReportNodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.ReportNode
if node == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
case nodeconfigs.NodeRoleUser:
nodeResp, err := this.RPC().UserNodeRPC().FindEnabledUserNode(this.AdminContext(), &pb.FindEnabledUserNodeRequest{UserNodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.UserNode
if node == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
case nodeconfigs.NodeRoleAdmin:
// TODO
case nodeconfigs.NodeRoleDatabase:
nodeResp, err := this.RPC().DBNodeRPC().FindEnabledDBNode(this.AdminContext(), &pb.FindEnabledDBNodeRequest{DbNodeId: log.NodeId})
if err != nil {
return nil, false, err
}
var node = nodeResp.DbNode
if node == nil {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMap["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
default:
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{NodeLogIds: []int64{log.Id}})
if err != nil {
return nil, false, err
}
continue
}
logMaps = append(logMaps, logMap)
}
return logMaps, len(logMaps) == 0, nil
}

View File

@@ -0,0 +1,413 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/tasks"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils"
"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/userconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"regexp"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
if !uiConfig.ContainsModule(userconfigs.UserModuleCDN) {
if uiConfig.ContainsModule(userconfigs.UserModuleNS) {
this.RedirectURL("/ns")
return
}
this.View("@blank")
this.Show()
return
}
// 商业版错误
this.Data["plusErr"] = plus.ErrString
// 初始化
err = boardutils.InitBoard(this.Parent())
if err != nil {
this.ErrorPage(err)
return
}
// 取得用户的权限
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
if ok {
if module != "dashboard" {
for _, m := range configloaders.AllModuleMaps(this.LangCode()) {
if m.GetString("code") == module {
this.RedirectURL(m.GetString("url"))
return
}
}
}
}
// 版本更新
this.Data["currentVersionCode"] = teaconst.Version
this.Data["newVersionCode"] = teaconst.NewVersionCode
this.Data["newVersionDownloadURL"] = teaconst.NewVersionDownloadURL
this.Show()
}
func (this *IndexAction) RunPost(params struct {
}) {
// 读取看板数据
resp, err := this.RPC().AdminRPC().ComposeAdminDashboard(this.AdminContext(), &pb.ComposeAdminDashboardRequest{
ApiVersion: teaconst.APINodeVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
// 检查当前服务器空间
var diskUsageWarning = ""
diskPath, diskUsage, diskUsagePercent, shouldWarning := dashboardutils.CheckDiskPartitions(90)
if shouldWarning {
diskUsageWarning = codes.AdminDashboard_DiskUsageWarning.For(this.LangCode(), diskPath, diskUsage/(1<<30), diskUsagePercent, 100-diskUsagePercent)
}
this.Data["dashboard"] = maps.Map{
"defaultClusterId": resp.DefaultNodeClusterId,
"countServers": resp.CountServers,
"countAuditingServers": resp.CountAuditingServers,
"countNodeClusters": resp.CountNodeClusters,
"countNodes": resp.CountNodes,
"countOfflineNodes": resp.CountOfflineNodes,
"countUsers": resp.CountUsers,
"countAPINodes": resp.CountAPINodes,
"countOfflineAPINodes": resp.CountOfflineAPINodes,
"countDBNodes": resp.CountDBNodes,
"countOfflineDBNodes": resp.CountOfflineDBNodes,
"countUserNodes": resp.CountUserNodes,
"countOfflineUserNodes": resp.CountOfflineUserNodes,
"canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer),
"canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode),
"canGoSettings": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeSetting),
"canGoUsers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeUser),
"diskUsageWarning": diskUsageWarning,
}
// 今日流量和独立IP数
var todayTrafficBytes int64
var todayCountIPs int64
if len(resp.DailyTrafficStats) > 0 {
var lastDailyTrafficStat = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1]
todayTrafficBytes = lastDailyTrafficStat.Bytes
todayCountIPs = lastDailyTrafficStat.CountIPs
}
todayTrafficString := numberutils.FormatBytes(todayTrafficBytes)
result := regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(todayTrafficString)
if len(result) > 2 {
this.Data["todayTraffic"] = result[1]
this.Data["todayTrafficUnit"] = result[2]
} else {
this.Data["todayTraffic"] = todayTrafficString
this.Data["todayTrafficUnit"] = ""
}
this.Data["todayCountIPs"] = todayCountIPs
var yesterdayTrafficBytes = int64(0)
if len(resp.DailyTrafficStats) > 1 {
yesterdayTrafficBytes = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-2].Bytes
}
var yesterdayTrafficString = numberutils.FormatBytes(yesterdayTrafficBytes)
{
var result = regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(yesterdayTrafficString)
if len(result) > 2 {
this.Data["yesterdayTraffic"] = result[1]
this.Data["yesterdayTrafficUnit"] = result[2]
} else {
this.Data["yesterdayTraffic"] = yesterdayTrafficString
this.Data["yesterdayTrafficUnit"] = ""
}
}
var weekTrafficBytes = int64(0)
var weekday = types.Int(timeutil.Format("w"))
if weekday == 0 {
weekday = 7
}
var weekDayBegin = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -weekday+1))
if len(resp.DailyTrafficStats) > 1 {
for i := len(resp.DailyTrafficStats) - 1; i >= len(resp.DailyTrafficStats)-7 && i >= 0; i-- {
var stat = resp.DailyTrafficStats[i]
if stat.Day >= weekDayBegin {
weekTrafficBytes += stat.Bytes
}
}
}
var weekTrafficString = numberutils.FormatBytes(weekTrafficBytes)
{
var result = regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(weekTrafficString)
if len(result) > 2 {
this.Data["weekTraffic"] = result[1]
this.Data["weekTrafficUnit"] = result[2]
} else {
this.Data["weekTraffic"] = weekTrafficString
this.Data["weekTrafficUnit"] = ""
}
}
// 24小时流量趋势
{
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["hourlyTrafficStats"] = statMaps
}
// 15天流量趋势
{
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["dailyTrafficStats"] = statMaps
}
// 节点排行
{
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
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = statMaps
}
// 地区排行
{
var countryMaps = []maps.Map{}
for _, stat := range resp.TopCountryStats {
countryMaps = append(countryMaps, maps.Map{
"name": stat.CountryName,
"bytes": stat.Bytes,
"formattedBytes": numberutils.FormatBytes(stat.Bytes),
"countRequests": stat.CountRequests,
"countAttackRequests": stat.CountAttackRequests,
"percent": fmt.Sprintf("%.2f", stat.Percent),
})
}
this.Data["topCountryStats"] = countryMaps
}
// 版本升级
if resp.NodeUpgradeInfo != nil {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": resp.NodeUpgradeInfo.CountNodes,
"version": resp.NodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.ApiNodeUpgradeInfo != nil {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": resp.ApiNodeUpgradeInfo.CountNodes,
"version": resp.ApiNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.UserNodeUpgradeInfo != nil {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": resp.UserNodeUpgradeInfo.CountNodes,
"version": resp.UserNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["userNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": 0,
}
}
if resp.NsNodeUpgradeInfo != nil {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": resp.NsNodeUpgradeInfo.CountNodes,
"version": resp.NsNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nsNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.ReportNodeUpgradeInfo != nil {
this.Data["reportNodeUpgradeInfo"] = maps.Map{
"count": resp.ReportNodeUpgradeInfo.CountNodes,
"version": resp.ReportNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["reportNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
// 指标
{
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
}
// Plus过期时间
authorityKeyResp, err := this.RPC().AuthorityKeyRPC().ReadAuthorityKey(this.AdminContext(), &pb.ReadAuthorityKeyRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var authorityKey = authorityKeyResp.AuthorityKey
var plusExpireDay = ""
if authorityKey != nil && timeutil.Format("Y-m-d", time.Now().AddDate(0, 0, 10)) > authorityKey.DayTo {
plusExpireDay = authorityKey.DayTo
}
this.Data["plusExpireDay"] = plusExpireDay
tasks.NotifyAuthorityTask() // 通知状态变更
// 今日攻击信息
countBlocksResp, err := this.RPC().FirewallRPC().CountFirewallDailyBlocks(this.AdminContext(), &pb.CountFirewallDailyBlocksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
boardutils.CountTodayAttacks = countBlocksResp.CountBlocks
this.Data["countTodayAttacks"] = countBlocksResp.CountBlocks
this.Data["countTodayAttacksFormat"] = numberutils.FormatCount(countBlocksResp.CountBlocks)
// 当前API节点版本
{
exePath, runtimeVersion, fileVersion, ok := dashboardutils.CheckLocalAPINode(this.RPC(), this.AdminContext())
if ok {
this.Data["localLowerVersionAPINode"] = maps.Map{
"exePath": exePath,
"runtimeVersion": runtimeVersion,
"fileVersion": fileVersion,
"isRestarting": false,
}
}
}
// 弱密码提示
countWeakAdminsResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{HasWeakPassword: true})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countWeakAdmins"] = countWeakAdminsResp.Count
this.Success()
}

View File

@@ -0,0 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type ReadAllLogsAction struct {
actionutils.ParentAction
}
func (this *ReadAllLogsAction) RunPost(params struct {
LogIds []int64
}) {
_, err := this.RPC().NodeLogRPC().UpdateAllNodeLogsRead(this.AdminContext(), &pb.UpdateAllNodeLogsReadRequest{})
if err != nil {
this.ErrorPage(err)
return
}
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success()
}

View File

@@ -0,0 +1,30 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type ReadLogsAction struct {
actionutils.ParentAction
}
func (this *ReadLogsAction) RunPost(params struct {
LogIds []int64
}) {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{
NodeLogIds: params.LogIds,
})
if err != nil {
this.ErrorPage(err)
return
}
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success()
}

View File

@@ -0,0 +1,128 @@
// 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/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAction struct {
actionutils.ParentAction
}
func (this *UserAction) Init() {
this.Nav("", "", "user")
}
func (this *UserAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
// 初始化
err := boardutils.InitBoard(this.Parent())
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().UserRPC().ComposeUserGlobalBoard(this.AdminContext(), &pb.ComposeUserGlobalBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"totalUsers": resp.TotalUsers,
"countVerifyingUsers": resp.CountVerifyingUsers,
"countTodayUsers": resp.CountTodayUsers,
"countWeeklyUsers": resp.CountWeeklyUsers,
"countUserNodes": resp.CountUserNodes,
"countOfflineUserNodes": resp.CountOfflineUserNodes,
}
{
statMaps := []maps.Map{}
for _, stat := range resp.DailyStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day,
"count": stat.Count,
})
}
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
}
// 流量排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopTrafficStats {
statMaps = append(statMaps, maps.Map{
"userId": stat.UserId,
"userName": stat.UserName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topTrafficStats"] = statMaps
}
this.Show()
}

View File

@@ -0,0 +1,85 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package boards
import (
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"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 ValuesAction struct {
actionutils.ParentAction
}
func (this *ValuesAction) RunPost(params struct{}) {
resp, err := this.RPC().NodeValueRPC().SumAllNodeValueStats(this.AdminContext(), &pb.SumAllNodeValueStatsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var resultBytes = resp.TotalTrafficBytesPerSecond
// 当天流量统计
trafficDailyStatResp, err := this.RPC().TrafficDailyStatRPC().FindTrafficDailyStatWithDay(this.AdminContext(), &pb.FindTrafficDailyStatWithDayRequest{Day: timeutil.Format("Ymd")})
if err != nil {
this.ErrorPage(err)
return
}
var todayTrafficBytes int64 = 0
var todayTrafficFormat = "0B"
if trafficDailyStatResp.TrafficDailyStat != nil {
todayTrafficBytes = trafficDailyStatResp.TrafficDailyStat.Bytes
todayTrafficFormat = numberutils.FormatBytes(todayTrafficBytes)
}
// 昨天同期流量
yesterdayTrafficResp, err := this.RPC().TrafficDailyStatRPC().FindTrafficDailyStatWithDay(this.AdminContext(), &pb.FindTrafficDailyStatWithDayRequest{
Day: timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)),
Minute: timeutil.Format("His"),
})
if err != nil {
this.ErrorPage(err)
return
}
var yesterdayPercentFormat = ""
if yesterdayTrafficResp.TrafficDailyStat != nil {
var yesterdayTrafficBytes = yesterdayTrafficResp.TrafficDailyStat.Bytes
if yesterdayTrafficBytes > 0 {
var percent = float64((todayTrafficBytes-yesterdayTrafficBytes)*100) / float64(yesterdayTrafficBytes)
if percent > 0.01 {
yesterdayPercentFormat = "+" + fmt.Sprintf("%.2f", percent)
} else if percent < 0.01 {
yesterdayPercentFormat = fmt.Sprintf("%.2f", percent)
}
}
}
this.Data["stat"] = maps.Map{
"totalTrafficBytesPerSecond": resultBytes,
"totalTrafficPerSecondFormat": numberutils.FormatBits(resultBytes * 8),
"avgCPUUsage": resp.AvgCPUUsage * 100,
"avgCPUUsageFormat": fmt.Sprintf("%.2f", resp.AvgCPUUsage*100),
"maxCPUUsage": resp.MaxCPUUsage * 100,
"totalCPUCores": resp.TotalCPUCores,
"avgMemoryUsage": resp.AvgMemoryUsage * 100,
"avgMemoryUsageFormat": fmt.Sprintf("%.2f", resp.AvgMemoryUsage*100),
"maxMemoryUsage": resp.MaxMemoryUsage * 100,
"totalMemoryBytes": resp.TotalMemoryBytes,
"totalMemoryFormat": numberutils.FormatBytes(resp.TotalMemoryBytes),
"avgLoad1min": resp.AvgLoad1Min,
"avgLoad1minFormat": fmt.Sprintf("%.2f", resp.AvgLoad1Min),
"maxLoad1min": resp.MaxLoad1Min,
"avgLoad5min": resp.AvgLoad5Min,
"avgLoad5minFormat": fmt.Sprintf("%.2f", resp.AvgLoad5Min),
"todayTrafficFormat": todayTrafficFormat,
"yesterdayPercentFormat": yesterdayPercentFormat,
}
this.Success()
}

View File

@@ -0,0 +1,134 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"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/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type WafAction struct {
actionutils.ParentAction
}
func (this *WafAction) Init() {
this.Nav("", "", "waf")
}
func (this *WafAction) RunGet(params struct{}) {
if !teaconst.IsPlus {
this.RedirectURL("/dashboard")
return
}
// 将badge置为已读
boardutils.CountTodayAttacksRead = boardutils.CountTodayAttacks
// 初始化
err := boardutils.InitBoard(this.Parent())
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().FirewallRPC().ComposeFirewallGlobalBoard(this.AdminContext(), &pb.ComposeFirewallGlobalBoardRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countDailyLogs": resp.CountDailyLogs,
"countDailyBlocks": resp.CountDailyBlocks,
"countDailyCaptcha": resp.CountDailyCaptcha,
"countWeeklyBlocks": resp.CountWeeklyBlocks,
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyStats {
statMaps = append(statMaps, maps.Map{
"hour": stat.Hour,
"countLogs": stat.CountLogs,
"countCaptcha": stat.CountCaptcha,
"countBlocks": stat.CountBlocks,
})
}
this.Data["hourlyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyStats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day,
"countLogs": stat.CountLogs,
"countCaptcha": stat.CountCaptcha,
"countBlocks": stat.CountBlocks,
})
}
this.Data["dailyStats"] = statMaps
}
{
var statMaps = []maps.Map{}
for _, stat := range resp.HttpFirewallRuleGroups {
statMaps = append(statMaps, maps.Map{
"name": stat.HttpFirewallRuleGroup.Name,
"count": stat.Count,
})
}
this.Data["groupStats"] = statMaps
}
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNodeStats {
statMaps = append(statMaps, maps.Map{
"nodeId": stat.NodeId,
"nodeName": stat.NodeName,
"countRequests": stat.CountAttackRequests,
"bytes": stat.AttackBytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountAttackRequests,
"bytes": stat.AttackBytes,
})
}
this.Data["topDomainStats"] = statMaps
}
// 地区排行
{
var countryMaps = []maps.Map{}
for _, stat := range resp.TopCountryStats {
countryMaps = append(countryMaps, maps.Map{
"name": stat.CountryName,
"bytes": stat.Bytes,
"formattedBytes": numberutils.FormatBytes(stat.Bytes),
"countRequests": stat.CountRequests,
"countAttackRequests": stat.CountAttackRequests,
"percent": fmt.Sprintf("%.2f", stat.Percent),
})
}
this.Data["topCountryStats"] = countryMaps
}
this.Show()
}

View File

@@ -0,0 +1,142 @@
// 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/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type WafLogsAction struct {
actionutils.ParentAction
}
func (this *WafLogsAction) RunPost(params struct{}) {
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Partition: -1,
HasFirewallPolicy: true,
Reverse: false,
Day: timeutil.Format("Ymd"),
Size: 5,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["accessLogs"] = resp.HttpAccessLogs
var ipList = []string{}
var wafMaps = []maps.Map{}
for _, accessLog := range resp.HttpAccessLogs {
// IP
if len(accessLog.RemoteAddr) > 0 {
if !lists.ContainsString(ipList, accessLog.RemoteAddr) {
ipList = append(ipList, accessLog.RemoteAddr)
}
}
// WAF信息集合
if accessLog.FirewallPolicyId > 0 && accessLog.FirewallRuleGroupId > 0 && accessLog.FirewallRuleSetId > 0 {
// 检查Set是否已经存在
var existSet = false
for _, wafMap := range wafMaps {
if wafMap.GetInt64("setId") == accessLog.FirewallRuleSetId {
existSet = true
break
}
}
if !existSet {
wafMaps = append(wafMaps, maps.Map{
"policyId": accessLog.FirewallPolicyId,
"groupId": accessLog.FirewallRuleGroupId,
"setId": accessLog.FirewallRuleSetId,
})
}
}
}
// 根据IP查询区域
this.Data["regions"] = iplibrary.LookupIPSummaries(ipList)
// WAF相关
var wafInfos = map[int64]maps.Map{} // set id => WAF Map
var wafPolicyCacheMap = map[int64]*pb.HTTPFirewallPolicy{} // id => *pb.HTTPFirewallPolicy
var wafGroupCacheMap = map[int64]*pb.HTTPFirewallRuleGroup{} // id => *pb.HTTPFirewallRuleGroup
var wafSetCacheMap = map[int64]*pb.HTTPFirewallRuleSet{} // id => *pb.HTTPFirewallRuleSet
for _, wafMap := range wafMaps {
var policyId = wafMap.GetInt64("policyId")
var groupId = wafMap.GetInt64("groupId")
var setId = wafMap.GetInt64("setId")
if policyId > 0 {
pbPolicy, ok := wafPolicyCacheMap[policyId]
if !ok {
policyResp, err := this.RPC().HTTPFirewallPolicyRPC().FindEnabledHTTPFirewallPolicy(this.AdminContext(), &pb.FindEnabledHTTPFirewallPolicyRequest{HttpFirewallPolicyId: policyId})
if err != nil {
this.ErrorPage(err)
return
}
pbPolicy = policyResp.HttpFirewallPolicy
wafPolicyCacheMap[policyId] = pbPolicy
}
if pbPolicy != nil {
wafMap = maps.Map{
"policy": maps.Map{
"id": pbPolicy.Id,
"name": pbPolicy.Name,
"serverId": pbPolicy.ServerId,
},
}
if groupId > 0 {
pbGroup, ok := wafGroupCacheMap[groupId]
if !ok {
groupResp, err := this.RPC().HTTPFirewallRuleGroupRPC().FindEnabledHTTPFirewallRuleGroup(this.AdminContext(), &pb.FindEnabledHTTPFirewallRuleGroupRequest{FirewallRuleGroupId: groupId})
if err != nil {
this.ErrorPage(err)
return
}
pbGroup = groupResp.FirewallRuleGroup
wafGroupCacheMap[groupId] = pbGroup
}
if pbGroup != nil {
wafMap["group"] = maps.Map{
"id": pbGroup.Id,
"name": pbGroup.Name,
}
if setId > 0 {
pbSet, ok := wafSetCacheMap[setId]
if !ok {
setResp, err := this.RPC().HTTPFirewallRuleSetRPC().FindEnabledHTTPFirewallRuleSet(this.AdminContext(), &pb.FindEnabledHTTPFirewallRuleSetRequest{FirewallRuleSetId: setId})
if err != nil {
this.ErrorPage(err)
return
}
pbSet = setResp.FirewallRuleSet
wafSetCacheMap[setId] = pbSet
}
if pbSet != nil {
wafMap["set"] = maps.Map{
"id": pbSet.Id,
"name": pbSet.Name,
}
}
}
}
}
}
}
wafInfos[setId] = wafMap
}
this.Data["wafInfos"] = wafInfos
this.Success()
}

View File

@@ -0,0 +1,177 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dashboardutils
import (
"bytes"
"context"
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
"github.com/shirou/gopsutil/v3/disk"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
)
// CheckDiskPartitions 检查服务器硬盘空间
func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
partitions, err := disk.Partitions(false)
if err != nil {
return
}
if !lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
return
}
var rootFS = ""
for _, p := range partitions {
if p.Mountpoint == "/" {
rootFS = p.Fstype
break
}
}
for _, p := range partitions {
if p.Mountpoint == "/boot" {
continue
}
if p.Fstype != rootFS {
continue
}
// skip some specified partitions on macOS
if runtime.GOOS == "darwin" {
if strings.Contains(p.Mountpoint, "/Developer/") {
continue
}
}
stat, _ := disk.Usage(p.Mountpoint)
if stat != nil {
if stat.Used < (5<<30) || stat.Free > (100<<30) {
continue
}
if stat.UsedPercent > thresholdPercent {
path = stat.Path
usage = stat.Used
usagePercent = stat.UsedPercent
shouldWarning = true
break
}
}
}
return
}
// CheckLocalAPINode 检查本地的API节点
func CheckLocalAPINode(rpcClient *rpc.RPCClient, ctx context.Context) (exePath string, runtimeVersion string, fileVersion string, ok bool) {
resp, err := rpcClient.APINodeRPC().FindCurrentAPINode(ctx, &pb.FindCurrentAPINodeRequest{})
if err != nil {
return
}
if resp.ApiNode == nil {
return
}
var instanceCode = resp.ApiNode.InstanceCode
if len(instanceCode) == 0 {
return
}
var statusJSON = resp.ApiNode.StatusJSON
if len(statusJSON) == 0 {
return
}
var status = &nodeconfigs.NodeStatus{}
err = json.Unmarshal(statusJSON, status)
if err != nil {
return
}
runtimeVersion = status.BuildVersion
if len(runtimeVersion) == 0 {
return
}
if stringutil.VersionCompare(runtimeVersion, teaconst.APINodeVersion) >= 0 {
return
}
exePath = status.ExePath
if len(exePath) == 0 {
return
}
stat, err := os.Stat(exePath)
if err != nil {
return
}
if stat.IsDir() {
return
}
// 实例信息
{
var outputBuffer = &bytes.Buffer{}
var cmd = exec.Command(exePath, "instance")
cmd.Stdout = outputBuffer
err = cmd.Run()
if err != nil {
return
}
var outputBytes = outputBuffer.Bytes()
if len(outputBytes) == 0 {
return
}
var instanceMap = maps.Map{}
err = json.Unmarshal(bytes.TrimSpace(outputBytes), &instanceMap)
if err != nil {
return
}
if instanceMap.GetString("code") != instanceCode {
return
}
}
// 文件版本
{
var outputBuffer = &bytes.Buffer{}
var cmd = exec.Command(exePath, "-v")
cmd.Stdout = outputBuffer
err = cmd.Run()
if err != nil {
return
}
var outputString = outputBuffer.String()
if len(outputString) == 0 {
return
}
var subMatch = regexp.MustCompile(`\s+v([\d.]+)\s+`).FindStringSubmatch(outputString)
if len(subMatch) == 0 {
return
}
fileVersion = subMatch[1]
// 文件版本是否为最新
if fileVersion != teaconst.APINodeVersion {
fileVersion = runtimeVersion
}
}
ok = true
return
}

View File

@@ -0,0 +1,249 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dashboard
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
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/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"regexp"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
// 通知菜单数字Badge更新
helpers.NotifyIPItemsCountChanges()
helpers.NotifyNodeLogsCountChange()
if this.checkPlus() {
this.RedirectURL("/dashboard/boards")
return
}
// 取得用户的权限
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
if ok {
if module != "dashboard" {
for _, m := range configloaders.AllModuleMaps(this.LangCode()) {
if m.GetString("code") == module {
this.RedirectURL(m.GetString("url"))
return
}
}
}
}
// 版本更新
this.Data["currentVersionCode"] = teaconst.Version
this.Data["newVersionCode"] = teaconst.NewVersionCode
this.Data["newVersionDownloadURL"] = teaconst.NewVersionDownloadURL
this.Show()
}
func (this *IndexAction) RunPost(params struct{}) {
// 读取看板数据
resp, err := this.RPC().AdminRPC().ComposeAdminDashboard(this.AdminContext(), &pb.ComposeAdminDashboardRequest{
ApiVersion: teaconst.APINodeVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
// 检查当前服务器空间
var diskUsageWarning = ""
diskPath, diskUsage, diskUsagePercent, shouldWarning := dashboardutils.CheckDiskPartitions(90)
if shouldWarning {
diskUsageWarning = codes.AdminDashboard_DiskUsageWarning.For(this.LangCode(), diskPath, diskUsage/(1<<30), diskUsagePercent, 100-diskUsagePercent)
}
this.Data["dashboard"] = maps.Map{
"defaultClusterId": resp.DefaultNodeClusterId,
"countServers": resp.CountServers,
"countNodeClusters": resp.CountNodeClusters,
"countNodes": resp.CountNodes,
"countOfflineNodes": resp.CountOfflineNodes,
"countUsers": resp.CountUsers,
"countAPINodes": resp.CountAPINodes,
"countOfflineAPINodes": resp.CountOfflineAPINodes,
"countDBNodes": resp.CountDBNodes,
"canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer),
"canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode),
"canGoSettings": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeSetting),
"canGoUsers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeUser),
"diskUsageWarning": diskUsageWarning,
}
// 今日流量和独立IP数
var todayTrafficBytes int64
var todayCountIPs int64
if len(resp.DailyTrafficStats) > 0 {
var lastDailyTrafficStat = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1]
todayTrafficBytes = lastDailyTrafficStat.Bytes
todayCountIPs = lastDailyTrafficStat.CountIPs
}
var todayTrafficString = numberutils.FormatBytes(todayTrafficBytes)
var result = regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(todayTrafficString)
if len(result) > 2 {
this.Data["todayTraffic"] = result[1]
this.Data["todayTrafficUnit"] = result[2]
} else {
this.Data["todayTraffic"] = todayTrafficString
this.Data["todayTrafficUnit"] = ""
}
this.Data["todayCountIPs"] = todayCountIPs
// 24小时流量趋势
{
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["hourlyTrafficStats"] = statMaps
}
// 15天流量趋势
{
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:] + "日",
"countIPs": stat.CountIPs,
})
}
this.Data["dailyTrafficStats"] = statMaps
}
// 版本升级
if resp.NodeUpgradeInfo != nil {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": resp.NodeUpgradeInfo.CountNodes,
"version": resp.NodeUpgradeInfo.NewVersion,
}
} else {
this.Data["nodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
if resp.ApiNodeUpgradeInfo != nil {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": resp.ApiNodeUpgradeInfo.CountNodes,
"version": resp.ApiNodeUpgradeInfo.NewVersion,
}
} else {
this.Data["apiNodeUpgradeInfo"] = maps.Map{
"count": 0,
"version": "",
}
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopDomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topDomainStats"] = 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
}
// 当前API节点版本
{
exePath, runtimeVersion, fileVersion, ok := dashboardutils.CheckLocalAPINode(this.RPC(), this.AdminContext())
if ok {
this.Data["localLowerVersionAPINode"] = maps.Map{
"exePath": exePath,
"runtimeVersion": runtimeVersion,
"fileVersion": fileVersion,
"isRestarting": false,
}
}
}
// 弱密码提示
countWeakAdminsResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{HasWeakPassword: true})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countWeakAdmins"] = countWeakAdminsResp.Count
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package dashboard
func (this *IndexAction) checkPlus() bool {
return false
}

View File

@@ -0,0 +1,14 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package dashboard
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/tasks"
)
func (this *IndexAction) checkPlus() bool {
tasks.NotifyAuthorityTask()
return teaconst.IsPlus
}

View File

@@ -0,0 +1,18 @@
package dashboard
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.Prefix("/dashboard").
Data("teaMenu", "dashboard").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
GetPost("", new(IndexAction)).
Post("/restartLocalAPINode", new(RestartLocalAPINodeAction)).
EndAll()
})
}

View File

@@ -0,0 +1,32 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package dashboard
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Data("teaMenu", "dashboard").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
// 看板
Prefix("/dashboard/boards").
GetPost("", new(boards.IndexAction)).
Get("/waf", new(boards.WafAction)).
Post("/wafLogs", new(boards.WafLogsAction)).
GetPost("/dns", new(boards.DnsAction)).
Get("/user", new(boards.UserAction)).
Get("/events", new(boards.EventsAction)).
Post("/readLogs", new(boards.ReadLogsAction)).
Post("/readAllLogs", new(boards.ReadAllLogsAction)).
Post("/values", new(boards.ValuesAction)).
EndAll()
})
}

View File

@@ -0,0 +1,76 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dashboard
import (
"bytes"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"os/exec"
"regexp"
"time"
)
type RestartLocalAPINodeAction struct {
actionutils.ParentAction
}
func (this *RestartLocalAPINodeAction) RunPost(params struct {
ExePath string
}) {
// 检查当前用户是超级用户
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: this.AdminId()})
if err != nil {
this.ErrorPage(err)
return
}
if adminResp.Admin == nil || !adminResp.Admin.IsSuper {
this.Fail("请切换到超级用户进行此操作")
}
var exePath = params.ExePath
if len(exePath) == 0 {
this.Fail("找不到要重启的API节点文件")
}
{
var stdoutBuffer = &bytes.Buffer{}
var cmd = exec.Command(exePath, "restart")
cmd.Stdout = stdoutBuffer
err = cmd.Run()
if err != nil {
this.Fail("运行失败:输出:" + stdoutBuffer.String())
}
}
// 停止1秒等待命令运行完毕
time.Sleep(1 * time.Second)
// 检查是否已启动
var countTries = 120
for {
countTries--
if countTries < 0 {
this.Fail("启动超时,请尝试手动启动")
break
}
var stdoutBuffer = &bytes.Buffer{}
var cmd = exec.Command(exePath, "status")
cmd.Stdout = stdoutBuffer
err = cmd.Run()
if err != nil {
time.Sleep(1 * time.Second)
continue
}
if regexp.MustCompile(`pid:\s*\d+`).
MatchString(stdoutBuffer.String()) {
break
}
time.Sleep(1 * time.Second)
}
this.Success()
}