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 @@