diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index 75d55a5b..9f10808f 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -91,6 +91,10 @@ func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient { return pb.NewNodeIPAddressServiceClient(this.pickConn()) } +func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient { + return pb.NewNodeValueServiceClient(this.pickConn()) +} + func (this *RPCClient) ServerRPC() pb.ServerServiceClient { return pb.NewServerServiceClient(this.pickConn()) } diff --git a/internal/web/actions/default/clusters/cluster/init.go b/internal/web/actions/default/clusters/cluster/init.go index bf7915ea..49237a30 100644 --- a/internal/web/actions/default/clusters/cluster/init.go +++ b/internal/web/actions/default/clusters/cluster/init.go @@ -4,6 +4,7 @@ import ( "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/monitor" clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" "github.com/iwind/TeaGo" @@ -37,6 +38,12 @@ func init() { Post("/node/start", new(node.StartAction)). Post("/node/stop", new(node.StopAction)). Post("/node/up", new(node.UpAction)). + Get("/node/monitor", new(monitor.IndexAction)). + Post("/node/monitor/cpu", new(monitor.CpuAction)). + Post("/node/monitor/memory", new(monitor.MemoryAction)). + Post("/node/monitor/load", new(monitor.LoadAction)). + Post("/node/monitor/trafficIn", new(monitor.TrafficInAction)). + Post("/node/monitor/trafficOut", new(monitor.TrafficOutAction)). // 分组相关 Get("/groups", new(groups.IndexAction)). diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/cpu.go b/internal/web/actions/default/clusters/cluster/node/monitor/cpu.go new file mode 100644 index 00000000..c0964918 --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/cpu.go @@ -0,0 +1,73 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import ( + "encoding/json" + "fmt" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" + "time" +) + +type CpuAction struct { + actionutils.ParentAction +} + +func (this *CpuAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{ + Role: "node", + NodeId: params.NodeId, + Item: nodeconfigs.NodeValueItemCPU, + Range: nodeconfigs.NodeValueRangeMinute, + }) + if err != nil { + this.ErrorPage(err) + return + } + valuesMap := map[string]float32{} // YmdHi => usage + for _, v := range resp.NodeValues { + if len(v.ValueJSON) == 0 { + continue + } + + valueMap := maps.Map{} + err = json.Unmarshal(v.ValueJSON, &valueMap) + if err != nil { + this.ErrorPage(err) + return + } + + valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("usage") * 100 + } + + // 过去一个小时 + result := []maps.Map{} + for i := 60; i >= 1; i-- { + timestamp := time.Now().Unix() - int64(i)*60 + minute := timeutil.FormatTime("YmdHi", timestamp) + total, ok := valuesMap[minute] + if ok { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": total, + "text": fmt.Sprintf("%.2f%%", total), + }) + } else { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": 0, + "text": "0.0%", + }) + } + } + + this.Data["values"] = result + + this.Success() +} diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/index.go b/internal/web/actions/default/clusters/cluster/node/monitor/index.go new file mode 100644 index 00000000..cdef3fd6 --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/index.go @@ -0,0 +1,21 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + +type IndexAction struct { + actionutils.ParentAction +} + +func (this *IndexAction) Init() { + this.Nav("", "node", "monitor") +} + +func (this *IndexAction) RunGet(params struct { + NodeId int64 +}) { + this.Data["nodeId"] = params.NodeId + + this.Show() +} diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/load.go b/internal/web/actions/default/clusters/cluster/node/monitor/load.go new file mode 100644 index 00000000..b31c3663 --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/load.go @@ -0,0 +1,73 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import ( + "encoding/json" + "fmt" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" + "time" +) + +type LoadAction struct { + actionutils.ParentAction +} + +func (this *LoadAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{ + Role: "node", + NodeId: params.NodeId, + Item: nodeconfigs.NodeValueItemLoad, + Range: nodeconfigs.NodeValueRangeMinute, + }) + if err != nil { + this.ErrorPage(err) + return + } + valuesMap := map[string]float32{} // YmdHi => load5m + for _, v := range resp.NodeValues { + if len(v.ValueJSON) == 0 { + continue + } + + valueMap := maps.Map{} + err = json.Unmarshal(v.ValueJSON, &valueMap) + if err != nil { + this.ErrorPage(err) + return + } + + valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("load5m") + } + + // 过去一个小时 + result := []maps.Map{} + for i := 60; i >= 1; i-- { + timestamp := time.Now().Unix() - int64(i)*60 + minute := timeutil.FormatTime("YmdHi", timestamp) + total, ok := valuesMap[minute] + if ok { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": total, + "text": fmt.Sprintf("5分钟: %.2f", total), + }) + } else { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": 0, + "text": "5分钟: 0.0", + }) + } + } + + this.Data["values"] = result + + this.Success() +} diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/memory.go b/internal/web/actions/default/clusters/cluster/node/monitor/memory.go new file mode 100644 index 00000000..4b4e7255 --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/memory.go @@ -0,0 +1,73 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import ( + "encoding/json" + "fmt" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" + "time" +) + +type MemoryAction struct { + actionutils.ParentAction +} + +func (this *MemoryAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{ + Role: "node", + NodeId: params.NodeId, + Item: nodeconfigs.NodeValueItemMemory, + Range: nodeconfigs.NodeValueRangeMinute, + }) + if err != nil { + this.ErrorPage(err) + return + } + valuesMap := map[string]float32{} // YmdHi => usage + for _, v := range resp.NodeValues { + if len(v.ValueJSON) == 0 { + continue + } + + valueMap := maps.Map{} + err = json.Unmarshal(v.ValueJSON, &valueMap) + if err != nil { + this.ErrorPage(err) + return + } + + valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetFloat32("usage") * 100 + } + + // 过去一个小时 + result := []maps.Map{} + for i := 60; i >= 1; i-- { + timestamp := time.Now().Unix() - int64(i)*60 + minute := timeutil.FormatTime("YmdHi", timestamp) + total, ok := valuesMap[minute] + if ok { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": total, + "text": fmt.Sprintf("%.2f%%", total), + }) + } else { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": 0, + "text": "0.0%", + }) + } + } + + this.Data["values"] = result + + this.Success() +} diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/trafficIn.go b/internal/web/actions/default/clusters/cluster/node/monitor/trafficIn.go new file mode 100644 index 00000000..bad2a865 --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/trafficIn.go @@ -0,0 +1,73 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" + "time" +) + +type TrafficInAction struct { + actionutils.ParentAction +} + +func (this *TrafficInAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{ + Role: "node", + NodeId: params.NodeId, + Item: nodeconfigs.NodeValueItemTrafficIn, + Range: nodeconfigs.NodeValueRangeMinute, + }) + if err != nil { + this.ErrorPage(err) + return + } + valuesMap := map[string]int64{} // YmdHi => bytes + for _, v := range resp.NodeValues { + if len(v.ValueJSON) == 0 { + continue + } + + valueMap := maps.Map{} + err = json.Unmarshal(v.ValueJSON, &valueMap) + if err != nil { + this.ErrorPage(err) + return + } + + valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetInt64("total") + } + + // 过去一个小时 + result := []maps.Map{} + for i := 60; i >= 1; i-- { + timestamp := time.Now().Unix() - int64(i)*60 + minute := timeutil.FormatTime("YmdHi", timestamp) + total, ok := valuesMap[minute] + if ok { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": total, + "text": numberutils.FormatBytes(total), + }) + } else { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": 0, + "text": numberutils.FormatBytes(0), + }) + } + } + + this.Data["values"] = result + + this.Success() +} diff --git a/internal/web/actions/default/clusters/cluster/node/monitor/trafficOut.go b/internal/web/actions/default/clusters/cluster/node/monitor/trafficOut.go new file mode 100644 index 00000000..4f1a975b --- /dev/null +++ b/internal/web/actions/default/clusters/cluster/node/monitor/trafficOut.go @@ -0,0 +1,73 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package monitor + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + timeutil "github.com/iwind/TeaGo/utils/time" + "time" +) + +type TrafficOutAction struct { + actionutils.ParentAction +} + +func (this *TrafficOutAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().NodeValueRPC().ListNodeValues(this.AdminContext(), &pb.ListNodeValuesRequest{ + Role: "node", + NodeId: params.NodeId, + Item: nodeconfigs.NodeValueItemTrafficOut, + Range: nodeconfigs.NodeValueRangeMinute, + }) + if err != nil { + this.ErrorPage(err) + return + } + valuesMap := map[string]int64{} // YmdHi => bytes + for _, v := range resp.NodeValues { + if len(v.ValueJSON) == 0 { + continue + } + + valueMap := maps.Map{} + err = json.Unmarshal(v.ValueJSON, &valueMap) + if err != nil { + this.ErrorPage(err) + return + } + + valuesMap[timeutil.FormatTime("YmdHi", v.CreatedAt)] = valueMap.GetInt64("total") + } + + // 过去一个小时 + result := []maps.Map{} + for i := 60; i >= 1; i-- { + timestamp := time.Now().Unix() - int64(i)*60 + minute := timeutil.FormatTime("YmdHi", timestamp) + total, ok := valuesMap[minute] + if ok { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": total, + "text": numberutils.FormatBytes(total), + }) + } else { + result = append(result, maps.Map{ + "label": timeutil.FormatTime("H:i", timestamp), + "value": 0, + "text": numberutils.FormatBytes(0), + }) + } + } + + this.Data["values"] = result + + this.Success() +} diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go index 945218a5..71031de4 100644 --- a/internal/web/helpers/user_must_auth.go +++ b/internal/web/helpers/user_must_auth.go @@ -107,6 +107,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam } action.Data["teaShowOpenSourceInfo"] = config.ShowOpenSourceInfo action.Data["teaIsSuper"] = false + action.Data["teaIsPlus"] = teaconst.IsPlus action.Data["teaDemoEnabled"] = teaconst.IsDemo action.Data["teaShowFinance"] = configloaders.ShowFinance() if !action.Data.Has("teaSubMenu") { diff --git a/web/views/@default/clusters/cluster/node/@node_menu.html b/web/views/@default/clusters/cluster/node/@node_menu.html index ec4f1176..eed3b382 100644 --- a/web/views/@default/clusters/cluster/node/@node_menu.html +++ b/web/views/@default/clusters/cluster/node/@node_menu.html @@ -2,7 +2,8 @@ 节点列表 | 节点详情 - 运行日志 + 监控图表 + 运行日志 修改设置 安装节点 \ No newline at end of file diff --git a/web/views/@default/clusters/cluster/node/monitor/index.css b/web/views/@default/clusters/cluster/node/monitor/index.css new file mode 100644 index 00000000..3007cba5 --- /dev/null +++ b/web/views/@default/clusters/cluster/node/monitor/index.css @@ -0,0 +1,4 @@ +.chart-box { + height: 20em; +} +/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/web/views/@default/clusters/cluster/node/monitor/index.css.map b/web/views/@default/clusters/cluster/node/monitor/index.css.map new file mode 100644 index 00000000..8c66954e --- /dev/null +++ b/web/views/@default/clusters/cluster/node/monitor/index.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"} \ No newline at end of file diff --git a/web/views/@default/clusters/cluster/node/monitor/index.html b/web/views/@default/clusters/cluster/node/monitor/index.html new file mode 100644 index 00000000..615d7b37 --- /dev/null +++ b/web/views/@default/clusters/cluster/node/monitor/index.html @@ -0,0 +1,22 @@ +{$layout} +{$template "../node_menu"} + +{$var "header"} + + +{$end} + +

上行流量

+
+ +

下行流量

+
+ +

CPU

+
+ +

内存

+
+ +

负载

+
\ No newline at end of file diff --git a/web/views/@default/clusters/cluster/node/monitor/index.js b/web/views/@default/clusters/cluster/node/monitor/index.js new file mode 100644 index 00000000..8af4923b --- /dev/null +++ b/web/views/@default/clusters/cluster/node/monitor/index.js @@ -0,0 +1,217 @@ +Tea.context(function () { + this.$delay(function () { + this.loadTrafficInChart() + this.loadTrafficOutChart() + this.loadCPUChart() + this.loadMemoryChart() + this.loadLoadChart() + + let that = this + window.addEventListener("resize", function () { + that.resizeChart("traffic-in-chart") + that.resizeChart("traffic-out-chart") + that.resizeChart("cpu-chart") + that.resizeChart("memory-chart") + that.resizeChart("load-chart") + }) + }) + + this.loadTrafficInChart = function () { + this.$post(".trafficIn") + .params({ + nodeId: this.nodeId + }) + .success(function (resp) { + let values = resp.data.values + let maxFunc = function () { + let max = values.map(function (v) { + return v.value + }).$max() / 1024 / 1024 + if (max < 1) { + return 1 + } + if (max < 10) { + return 10 + } + if (max < 100) { + return 100 + } + return null + } + let valueFunc = function (v) { + return v.value / 1024 / 1024 + } + this.reloadChart("traffic-in-chart", "", values, "M", maxFunc, valueFunc) + }) + .done(function () { + this.$delay(function () { + this.loadTrafficInChart() + }, 30000) + }) + } + + this.loadTrafficOutChart = function () { + this.$post(".trafficOut") + .params({ + nodeId: this.nodeId + }) + .success(function (resp) { + let values = resp.data.values + let maxFunc = function () { + let max = values.map(function (v) { + return v.value + }).$max() / 1024 / 1024 + if (max < 1) { + return 1 + } + if (max < 10) { + return 10 + } + if (max < 100) { + return 100 + } + return null + } + let valueFunc = function (v) { + return v.value / 1024 / 1024 + } + this.reloadChart("traffic-out-chart", "", values, "M", maxFunc, valueFunc) + }) + .done(function () { + this.$delay(function () { + this.loadTrafficOutChart() + }, 30000) + }) + } + + this.loadCPUChart = function () { + this.$post(".cpu") + .params({ + nodeId: this.nodeId + }) + .success(function (resp) { + let values = resp.data.values + let maxFunc = function () { + return 100 + } + let valueFunc = function (v) { + return v.value + } + this.reloadChart("cpu-chart", "", values, "%", maxFunc, valueFunc) + }) + .done(function () { + this.$delay(function () { + this.loadCPUChart() + }, 30000) + }) + } + + this.loadMemoryChart = function () { + this.$post(".memory") + .params({ + nodeId: this.nodeId + }) + .success(function (resp) { + let values = resp.data.values + let maxFunc = function () { + return 100 + } + let valueFunc = function (v) { + return v.value + } + this.reloadChart("memory-chart", "", values, "%", maxFunc, valueFunc) + }) + .done(function () { + this.$delay(function () { + this.loadMemoryChart() + }, 30000) + }) + } + + this.loadLoadChart = function () { + this.$post(".load") + .params({ + nodeId: this.nodeId + }) + .success(function (resp) { + let values = resp.data.values + let maxFunc = function () { + let max = values.map(function (v) { + return v.value + }).$max() + if (max < 10) { + return 10 + } + return null + } + let valueFunc = function (v) { + return v.value + } + this.reloadChart("load-chart", "5分钟", values, "", maxFunc, valueFunc) + }) + .done(function () { + this.$delay(function () { + this.loadLoadChart() + }, 30000) + }) + } + + this.reloadChart = function (chartId, name, stats, unit, maxFunc, valueFunc) { + let chartBox = document.getElementById(chartId) + if (chartBox == null) { + return + } + let chart = echarts.init(chartBox) + let option = { + xAxis: { + data: stats.map(function (stat) { + return stat.label + }) + }, + yAxis: { + max: maxFunc(), + axisLabel: { + formatter: function (value) { + return value + unit + } + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (args) { + return stats[args.dataIndex].label + ": " + stats[args.dataIndex].text + } + }, + grid: { + left: 50, + top: 10, + right: 20, + bottom: 20 + }, + series: [ + { + name: name, + type: "line", + data: stats.map(valueFunc), + itemStyle: { + color: "#9DD3E8" + }, + areaStyle: {} + } + ], + animation: true + } + chart.setOption(option) + chart.resize() + } + + this.resizeChart = function (chartId) { + let chartBox = document.getElementById(chartId) + if (chartBox == null) { + return + } + let chart = echarts.init(chartBox) + chart.resize() + } +}) \ No newline at end of file diff --git a/web/views/@default/clusters/cluster/node/monitor/index.less b/web/views/@default/clusters/cluster/node/monitor/index.less new file mode 100644 index 00000000..bed0b0ae --- /dev/null +++ b/web/views/@default/clusters/cluster/node/monitor/index.less @@ -0,0 +1,3 @@ +.chart-box { + height: 20em; +} \ No newline at end of file