Initial commit (code only without large binaries)
This commit is contained in:
@@ -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
|
||||
}
|
||||
169
EdgeAdmin/internal/web/actions/default/dashboard/boards/dns.go
Normal file
169
EdgeAdmin/internal/web/actions/default/dashboard/boards/dns.go
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
413
EdgeAdmin/internal/web/actions/default/dashboard/boards/index.go
Normal file
413
EdgeAdmin/internal/web/actions/default/dashboard/boards/index.go
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
128
EdgeAdmin/internal/web/actions/default/dashboard/boards/user.go
Normal file
128
EdgeAdmin/internal/web/actions/default/dashboard/boards/user.go
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
134
EdgeAdmin/internal/web/actions/default/dashboard/boards/waf.go
Normal file
134
EdgeAdmin/internal/web/actions/default/dashboard/boards/waf.go
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
249
EdgeAdmin/internal/web/actions/default/dashboard/index.go
Normal file
249
EdgeAdmin/internal/web/actions/default/dashboard/index.go
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
18
EdgeAdmin/internal/web/actions/default/dashboard/init.go
Normal file
18
EdgeAdmin/internal/web/actions/default/dashboard/init.go
Normal 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()
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user