mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-11 01:50:27 +08:00
实现集群看板(暂时只对企业版开放)
This commit is contained in:
2
build/configs/.gitignore
vendored
2
build/configs/.gitignore
vendored
@@ -2,4 +2,4 @@ api.yaml
|
|||||||
server.yaml
|
server.yaml
|
||||||
api_db.yaml
|
api_db.yaml
|
||||||
*.pem
|
*.pem
|
||||||
tip.json
|
*.cache.json
|
||||||
40
internal/configs/plus_config.go
Normal file
40
internal/configs/plus_config.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var plusConfigFile = "plus.cache.json"
|
||||||
|
|
||||||
|
type PlusConfig struct {
|
||||||
|
IsPlus bool `json:"isPlus"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPlusConfig() *PlusConfig {
|
||||||
|
data, err := ioutil.ReadFile(Tea.ConfigFile(plusConfigFile))
|
||||||
|
if err != nil {
|
||||||
|
return &PlusConfig{IsPlus: false}
|
||||||
|
}
|
||||||
|
var config = &PlusConfig{IsPlus: false}
|
||||||
|
err = json.Unmarshal(data, config)
|
||||||
|
if err != nil {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func WritePlusConfig(config *PlusConfig) error {
|
||||||
|
configJSON, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(Tea.ConfigFile(plusConfigFile), configJSON, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -404,6 +404,14 @@ func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServic
|
|||||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
|
||||||
|
return pb.NewServerStatBoardServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
|
||||||
|
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
// Context 构造Admin上下文
|
// Context 构造Admin上下文
|
||||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package tasks
|
package tasks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||||
@@ -28,6 +29,13 @@ func NewAuthorityTask() *AuthorityTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *AuthorityTask) Start() {
|
func (this *AuthorityTask) Start() {
|
||||||
|
// 从缓存中读取
|
||||||
|
config := configs.ReadPlusConfig()
|
||||||
|
if config != nil {
|
||||||
|
teaconst.IsPlus = config.IsPlus
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始计时器
|
||||||
ticker := time.NewTicker(10 * time.Minute)
|
ticker := time.NewTicker(10 * time.Minute)
|
||||||
if Tea.IsTesting() {
|
if Tea.IsTesting() {
|
||||||
// 快速测试
|
// 快速测试
|
||||||
@@ -65,10 +73,16 @@ func (this *AuthorityTask) Loop() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var oldState = teaconst.IsPlus
|
||||||
if resp.AuthorityKey != nil {
|
if resp.AuthorityKey != nil {
|
||||||
teaconst.IsPlus = true
|
teaconst.IsPlus = true
|
||||||
} else {
|
} else {
|
||||||
teaconst.IsPlus = false
|
teaconst.IsPlus = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if oldState != teaconst.IsPlus {
|
||||||
|
_ = configs.WritePlusConfig(&configs.PlusConfig{IsPlus: teaconst.IsPlus})
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
132
internal/web/actions/default/clusters/cluster/boards/index.go
Normal file
132
internal/web/actions/default/clusters/cluster/boards/index.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package boards
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IndexAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IndexAction) Init() {
|
||||||
|
this.Nav("", "board", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *IndexAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
}) {
|
||||||
|
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeClusterBoard(this.AdminContext(), &pb.ComposeServerStatNodeClusterBoardRequest{NodeClusterId: params.ClusterId})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["board"] = maps.Map{
|
||||||
|
"countUsers": resp.CountUsers,
|
||||||
|
"countActiveNodes": resp.CountActiveNodes,
|
||||||
|
"countInactiveNodes": resp.CountInactiveNodes,
|
||||||
|
"countServers": resp.CountServers,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 24小时流量趋势
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.HourlyTrafficStats {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"bytes": stat.Bytes,
|
||||||
|
"cachedBytes": stat.CachedBytes,
|
||||||
|
"countRequests": stat.CountRequests,
|
||||||
|
"countCachedRequests": stat.CountCachedRequests,
|
||||||
|
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
|
||||||
|
"hour": stat.Hour[8:],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["hourlyStats"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15天流量趋势
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.DailyTrafficStats {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"bytes": stat.Bytes,
|
||||||
|
"cachedBytes": stat.CachedBytes,
|
||||||
|
"countRequests": stat.CountRequests,
|
||||||
|
"countCachedRequests": stat.CountCachedRequests,
|
||||||
|
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["dailyStats"] = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.CpuNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["cpuValues"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.MemoryNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["memoryValues"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load
|
||||||
|
{
|
||||||
|
var statMaps = []maps.Map{}
|
||||||
|
for _, stat := range resp.LoadNodeValues {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
|
||||||
|
"value": types.Float32(string(stat.ValueJSON)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["loadValues"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||||
"fmt"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"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/logs"
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
"github.com/iwind/TeaGo/types"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -18,207 +13,15 @@ type IndexAction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) Init() {
|
func (this *IndexAction) Init() {
|
||||||
this.Nav("", "node", "index")
|
this.Nav("", "", "")
|
||||||
this.SecondMenu("nodes")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct {
|
func (this *IndexAction) RunGet(params struct {
|
||||||
ClusterId int64
|
ClusterId int64
|
||||||
GroupId int64
|
|
||||||
RegionId int64
|
|
||||||
InstalledState int
|
|
||||||
ActiveState int
|
|
||||||
Keyword string
|
|
||||||
}) {
|
}) {
|
||||||
this.Data["groupId"] = params.GroupId
|
if teaconst.IsPlus {
|
||||||
this.Data["regionId"] = params.RegionId
|
this.RedirectURL("/clusters/cluster/boards?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
|
||||||
this.Data["installState"] = params.InstalledState
|
} else {
|
||||||
this.Data["activeState"] = params.ActiveState
|
this.RedirectURL("/clusters/cluster/nodes?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
|
||||||
this.Data["keyword"] = params.Keyword
|
|
||||||
|
|
||||||
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
|
||||||
NodeClusterId: params.ClusterId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.Data["countAll"] = countAllResp.Count
|
|
||||||
|
|
||||||
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
|
||||||
NodeClusterId: params.ClusterId,
|
|
||||||
NodeGroupId: params.GroupId,
|
|
||||||
NodeRegionId: params.RegionId,
|
|
||||||
InstallState: types.Int32(params.InstalledState),
|
|
||||||
ActiveState: types.Int32(params.ActiveState),
|
|
||||||
Keyword: params.Keyword,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
page := this.NewPage(countResp.Count)
|
|
||||||
this.Data["page"] = page.AsHTML()
|
|
||||||
|
|
||||||
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), &pb.ListEnabledNodesMatchRequest{
|
|
||||||
Offset: page.Offset,
|
|
||||||
Size: page.Size,
|
|
||||||
NodeClusterId: params.ClusterId,
|
|
||||||
NodeGroupId: params.GroupId,
|
|
||||||
NodeRegionId: params.RegionId,
|
|
||||||
InstallState: types.Int32(params.InstalledState),
|
|
||||||
ActiveState: types.Int32(params.ActiveState),
|
|
||||||
Keyword: params.Keyword,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nodeMaps := []maps.Map{}
|
|
||||||
for _, node := range nodesResp.Nodes {
|
|
||||||
// 状态
|
|
||||||
isSynced := false
|
|
||||||
status := &nodeconfigs.NodeStatus{}
|
|
||||||
if len(node.StatusJSON) > 0 {
|
|
||||||
err = json.Unmarshal(node.StatusJSON, &status)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
|
|
||||||
isSynced = status.ConfigVersion == node.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP
|
|
||||||
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
|
|
||||||
NodeId: node.Id,
|
|
||||||
Role: nodeconfigs.NodeRoleNode,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ipAddresses := []maps.Map{}
|
|
||||||
for _, addr := range ipAddressesResp.Addresses {
|
|
||||||
ipAddresses = append(ipAddresses, maps.Map{
|
|
||||||
"id": addr.Id,
|
|
||||||
"name": addr.Name,
|
|
||||||
"ip": addr.Ip,
|
|
||||||
"canAccess": addr.CanAccess,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分组
|
|
||||||
var groupMap maps.Map = nil
|
|
||||||
if node.NodeGroup != nil {
|
|
||||||
groupMap = maps.Map{
|
|
||||||
"id": node.NodeGroup.Id,
|
|
||||||
"name": node.NodeGroup.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 区域
|
|
||||||
var regionMap maps.Map = nil
|
|
||||||
if node.NodeRegion != nil {
|
|
||||||
regionMap = maps.Map{
|
|
||||||
"id": node.NodeRegion.Id,
|
|
||||||
"name": node.NodeRegion.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNS
|
|
||||||
dnsRouteNames := []string{}
|
|
||||||
for _, route := range node.DnsRoutes {
|
|
||||||
dnsRouteNames = append(dnsRouteNames, route.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeMaps = append(nodeMaps, maps.Map{
|
|
||||||
"id": node.Id,
|
|
||||||
"name": node.Name,
|
|
||||||
"isInstalled": node.IsInstalled,
|
|
||||||
"isOn": node.IsOn,
|
|
||||||
"isUp": node.IsUp,
|
|
||||||
"installStatus": maps.Map{
|
|
||||||
"isRunning": node.InstallStatus.IsRunning,
|
|
||||||
"isFinished": node.InstallStatus.IsFinished,
|
|
||||||
"isOk": node.InstallStatus.IsOk,
|
|
||||||
"error": node.InstallStatus.Error,
|
|
||||||
},
|
|
||||||
"status": maps.Map{
|
|
||||||
"isActive": status.IsActive,
|
|
||||||
"updatedAt": status.UpdatedAt,
|
|
||||||
"hostname": status.Hostname,
|
|
||||||
"cpuUsage": status.CPUUsage,
|
|
||||||
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
|
|
||||||
"memUsage": status.MemoryUsage,
|
|
||||||
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
|
||||||
},
|
|
||||||
"cluster": maps.Map{
|
|
||||||
"id": node.NodeCluster.Id,
|
|
||||||
"name": node.NodeCluster.Name,
|
|
||||||
},
|
|
||||||
"isSynced": isSynced,
|
|
||||||
"ipAddresses": ipAddresses,
|
|
||||||
"group": groupMap,
|
|
||||||
"region": regionMap,
|
|
||||||
"dnsRouteNames": dnsRouteNames,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.Data["nodes"] = nodeMaps
|
|
||||||
|
|
||||||
// 所有分组
|
|
||||||
groupMaps := []maps.Map{}
|
|
||||||
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
|
|
||||||
NodeClusterId: params.ClusterId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, group := range groupsResp.NodeGroups {
|
|
||||||
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
countNodes := countResp.Count
|
|
||||||
groupName := group.Name
|
|
||||||
if countNodes > 0 {
|
|
||||||
groupName += "(" + strconv.FormatInt(countNodes, 10) + ")"
|
|
||||||
}
|
|
||||||
groupMaps = append(groupMaps, maps.Map{
|
|
||||||
"id": group.Id,
|
|
||||||
"name": groupName,
|
|
||||||
"countNodes": countNodes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.Data["groups"] = groupMaps
|
|
||||||
|
|
||||||
// 所有区域
|
|
||||||
regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledAndOnNodeRegions(this.AdminContext(), &pb.FindAllEnabledAndOnNodeRegionsRequest{})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
regionMaps := []maps.Map{}
|
|
||||||
for _, region := range regionsResp.NodeRegions {
|
|
||||||
regionMaps = append(regionMaps, maps.Map{
|
|
||||||
"id": region.Id,
|
|
||||||
"name": region.Name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.Data["regions"] = regionMaps
|
|
||||||
|
|
||||||
// 记录最近访问
|
|
||||||
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
|
|
||||||
ItemType: "cluster",
|
|
||||||
ItemId: params.ClusterId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
this.ErrorPage(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Show()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cluster
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/boards"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups"
|
"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"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/monitor"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/monitor"
|
||||||
@@ -18,6 +19,7 @@ func init() {
|
|||||||
Helper(clusters.NewClusterHelper()).
|
Helper(clusters.NewClusterHelper()).
|
||||||
Prefix("/clusters/cluster").
|
Prefix("/clusters/cluster").
|
||||||
Get("", new(IndexAction)).
|
Get("", new(IndexAction)).
|
||||||
|
Get("/nodes", new(NodesAction)).
|
||||||
GetPost("/installNodes", new(InstallNodesAction)).
|
GetPost("/installNodes", new(InstallNodesAction)).
|
||||||
GetPost("/installRemote", new(InstallRemoteAction)).
|
GetPost("/installRemote", new(InstallRemoteAction)).
|
||||||
Post("/installStatus", new(InstallStatusAction)).
|
Post("/installStatus", new(InstallStatusAction)).
|
||||||
@@ -56,6 +58,9 @@ func init() {
|
|||||||
Post("/groups/sort", new(groups.SortAction)).
|
Post("/groups/sort", new(groups.SortAction)).
|
||||||
GetPost("/groups/selectPopup", new(groups.SelectPopupAction)).
|
GetPost("/groups/selectPopup", new(groups.SelectPopupAction)).
|
||||||
|
|
||||||
|
// 看板相关
|
||||||
|
Get("/boards", new(boards.IndexAction)).
|
||||||
|
|
||||||
EndAll()
|
EndAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
224
internal/web/actions/default/clusters/cluster/nodes.go
Normal file
224
internal/web/actions/default/clusters/cluster/nodes.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
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/logs"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodesAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NodesAction) Init() {
|
||||||
|
this.Nav("", "node", "index")
|
||||||
|
this.SecondMenu("nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *NodesAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
GroupId int64
|
||||||
|
RegionId int64
|
||||||
|
InstalledState int
|
||||||
|
ActiveState int
|
||||||
|
Keyword string
|
||||||
|
}) {
|
||||||
|
this.Data["groupId"] = params.GroupId
|
||||||
|
this.Data["regionId"] = params.RegionId
|
||||||
|
this.Data["installState"] = params.InstalledState
|
||||||
|
this.Data["activeState"] = params.ActiveState
|
||||||
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
|
countAllResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
||||||
|
NodeClusterId: params.ClusterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["countAll"] = countAllResp.Count
|
||||||
|
|
||||||
|
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
|
||||||
|
NodeClusterId: params.ClusterId,
|
||||||
|
NodeGroupId: params.GroupId,
|
||||||
|
NodeRegionId: params.RegionId,
|
||||||
|
InstallState: types.Int32(params.InstalledState),
|
||||||
|
ActiveState: types.Int32(params.ActiveState),
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := this.NewPage(countResp.Count)
|
||||||
|
this.Data["page"] = page.AsHTML()
|
||||||
|
|
||||||
|
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), &pb.ListEnabledNodesMatchRequest{
|
||||||
|
Offset: page.Offset,
|
||||||
|
Size: page.Size,
|
||||||
|
NodeClusterId: params.ClusterId,
|
||||||
|
NodeGroupId: params.GroupId,
|
||||||
|
NodeRegionId: params.RegionId,
|
||||||
|
InstallState: types.Int32(params.InstalledState),
|
||||||
|
ActiveState: types.Int32(params.ActiveState),
|
||||||
|
Keyword: params.Keyword,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeMaps := []maps.Map{}
|
||||||
|
for _, node := range nodesResp.Nodes {
|
||||||
|
// 状态
|
||||||
|
isSynced := false
|
||||||
|
status := &nodeconfigs.NodeStatus{}
|
||||||
|
if len(node.StatusJSON) > 0 {
|
||||||
|
err = json.Unmarshal(node.StatusJSON, &status)
|
||||||
|
if err != nil {
|
||||||
|
logs.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
|
||||||
|
isSynced = status.ConfigVersion == node.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP
|
||||||
|
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledIPAddressesWithNodeIdRequest{
|
||||||
|
NodeId: node.Id,
|
||||||
|
Role: nodeconfigs.NodeRoleNode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ipAddresses := []maps.Map{}
|
||||||
|
for _, addr := range ipAddressesResp.Addresses {
|
||||||
|
ipAddresses = append(ipAddresses, maps.Map{
|
||||||
|
"id": addr.Id,
|
||||||
|
"name": addr.Name,
|
||||||
|
"ip": addr.Ip,
|
||||||
|
"canAccess": addr.CanAccess,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分组
|
||||||
|
var groupMap maps.Map = nil
|
||||||
|
if node.NodeGroup != nil {
|
||||||
|
groupMap = maps.Map{
|
||||||
|
"id": node.NodeGroup.Id,
|
||||||
|
"name": node.NodeGroup.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区域
|
||||||
|
var regionMap maps.Map = nil
|
||||||
|
if node.NodeRegion != nil {
|
||||||
|
regionMap = maps.Map{
|
||||||
|
"id": node.NodeRegion.Id,
|
||||||
|
"name": node.NodeRegion.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS
|
||||||
|
dnsRouteNames := []string{}
|
||||||
|
for _, route := range node.DnsRoutes {
|
||||||
|
dnsRouteNames = append(dnsRouteNames, route.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeMaps = append(nodeMaps, maps.Map{
|
||||||
|
"id": node.Id,
|
||||||
|
"name": node.Name,
|
||||||
|
"isInstalled": node.IsInstalled,
|
||||||
|
"isOn": node.IsOn,
|
||||||
|
"isUp": node.IsUp,
|
||||||
|
"installStatus": maps.Map{
|
||||||
|
"isRunning": node.InstallStatus.IsRunning,
|
||||||
|
"isFinished": node.InstallStatus.IsFinished,
|
||||||
|
"isOk": node.InstallStatus.IsOk,
|
||||||
|
"error": node.InstallStatus.Error,
|
||||||
|
},
|
||||||
|
"status": maps.Map{
|
||||||
|
"isActive": status.IsActive,
|
||||||
|
"updatedAt": status.UpdatedAt,
|
||||||
|
"hostname": status.Hostname,
|
||||||
|
"cpuUsage": status.CPUUsage,
|
||||||
|
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
|
||||||
|
"memUsage": status.MemoryUsage,
|
||||||
|
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
||||||
|
},
|
||||||
|
"cluster": maps.Map{
|
||||||
|
"id": node.NodeCluster.Id,
|
||||||
|
"name": node.NodeCluster.Name,
|
||||||
|
},
|
||||||
|
"isSynced": isSynced,
|
||||||
|
"ipAddresses": ipAddresses,
|
||||||
|
"group": groupMap,
|
||||||
|
"region": regionMap,
|
||||||
|
"dnsRouteNames": dnsRouteNames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["nodes"] = nodeMaps
|
||||||
|
|
||||||
|
// 所有分组
|
||||||
|
groupMaps := []maps.Map{}
|
||||||
|
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
|
||||||
|
NodeClusterId: params.ClusterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, group := range groupsResp.NodeGroups {
|
||||||
|
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countNodes := countResp.Count
|
||||||
|
groupName := group.Name
|
||||||
|
if countNodes > 0 {
|
||||||
|
groupName += "(" + strconv.FormatInt(countNodes, 10) + ")"
|
||||||
|
}
|
||||||
|
groupMaps = append(groupMaps, maps.Map{
|
||||||
|
"id": group.Id,
|
||||||
|
"name": groupName,
|
||||||
|
"countNodes": countNodes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["groups"] = groupMaps
|
||||||
|
|
||||||
|
// 所有区域
|
||||||
|
regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledAndOnNodeRegions(this.AdminContext(), &pb.FindAllEnabledAndOnNodeRegionsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
regionMaps := []maps.Map{}
|
||||||
|
for _, region := range regionsResp.NodeRegions {
|
||||||
|
regionMaps = append(regionMaps, maps.Map{
|
||||||
|
"id": region.Id,
|
||||||
|
"name": region.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["regions"] = regionMaps
|
||||||
|
|
||||||
|
// 记录最近访问
|
||||||
|
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
|
||||||
|
ItemType: "cluster",
|
||||||
|
ItemId: params.ClusterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ package metrics
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
@@ -59,15 +60,17 @@ func (this *CreatePopupAction) RunGet(params struct {
|
|||||||
var exists = existsResp.Exists
|
var exists = existsResp.Exists
|
||||||
|
|
||||||
itemMaps = append(itemMaps, maps.Map{
|
itemMaps = append(itemMaps, maps.Map{
|
||||||
"id": item.Id,
|
"id": item.Id,
|
||||||
"name": item.Name,
|
"name": item.Name,
|
||||||
"isOn": item.IsOn,
|
"isOn": item.IsOn,
|
||||||
"period": item.Period,
|
"period": item.Period,
|
||||||
"periodUnit": item.PeriodUnit,
|
"periodUnit": item.PeriodUnit,
|
||||||
"keys": item.Keys,
|
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
|
||||||
"value": item.Value,
|
"keys": item.Keys,
|
||||||
"category": item.Category,
|
"value": item.Value,
|
||||||
"isChecked": exists,
|
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
|
||||||
|
"category": item.Category,
|
||||||
|
"isChecked": exists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.Data["items"] = itemMaps
|
this.Data["items"] = itemMaps
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package metrics
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,14 +39,16 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
var itemMaps = []maps.Map{}
|
var itemMaps = []maps.Map{}
|
||||||
for _, item := range itemsResp.MetricItems {
|
for _, item := range itemsResp.MetricItems {
|
||||||
itemMaps = append(itemMaps, maps.Map{
|
itemMaps = append(itemMaps, maps.Map{
|
||||||
"id": item.Id,
|
"id": item.Id,
|
||||||
"name": item.Name,
|
"name": item.Name,
|
||||||
"isOn": item.IsOn,
|
"isOn": item.IsOn,
|
||||||
"period": item.Period,
|
"period": item.Period,
|
||||||
"periodUnit": item.PeriodUnit,
|
"periodUnit": item.PeriodUnit,
|
||||||
"keys": item.Keys,
|
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
|
||||||
"value": item.Value,
|
"keys": item.Keys,
|
||||||
"category": item.Category,
|
"value": item.Value,
|
||||||
|
"valueName": serverconfigs.FindMetricValueName(item.Category, item.Value),
|
||||||
|
"category": item.Category,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.Data["items"] = itemMaps
|
this.Data["items"] = itemMaps
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
|
|||||||
|
|
||||||
tabbar := actionutils.NewTabbar()
|
tabbar := actionutils.NewTabbar()
|
||||||
tabbar.Add("集群列表", "", "/clusters", "", false)
|
tabbar.Add("集群列表", "", "/clusters", "", false)
|
||||||
tabbar.Add("集群节点", "", "/clusters/cluster?clusterId="+clusterIdString, "server", selectedTabbar == "node")
|
if teaconst.IsPlus {
|
||||||
|
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board")
|
||||||
|
}
|
||||||
|
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
|
||||||
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
|
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
|
||||||
tabbar.Add("删除集群", "", "/clusters/cluster/delete?clusterId="+clusterIdString, "trash", selectedTabbar == "delete")
|
tabbar.Add("删除集群", "", "/clusters/cluster/delete?clusterId="+clusterIdString, "trash", selectedTabbar == "delete")
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 服务数
|
||||||
|
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: cluster.Id})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
}
|
||||||
|
|
||||||
clusterMaps = append(clusterMaps, maps.Map{
|
clusterMaps = append(clusterMaps, maps.Map{
|
||||||
"id": cluster.Id,
|
"id": cluster.Id,
|
||||||
"name": cluster.Name,
|
"name": cluster.Name,
|
||||||
@@ -125,6 +131,7 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
"dnsDomainId": cluster.DnsDomainId,
|
"dnsDomainId": cluster.DnsDomainId,
|
||||||
"dnsName": cluster.DnsName,
|
"dnsName": cluster.DnsName,
|
||||||
"dnsDomainName": dnsDomainName,
|
"dnsDomainName": dnsDomainName,
|
||||||
|
"countServers": countServersResp.Count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ func InitItem(parent *actionutils.ParentAction, itemId int64) (*pb.MetricItem, e
|
|||||||
if item == nil {
|
if item == nil {
|
||||||
return nil, errors.New("metric item not found")
|
return nil, errors.New("metric item not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
countChartsResp, err := client.MetricChartRPC().CountEnabledMetricCharts(parent.AdminContext(), &pb.CountEnabledMetricChartsRequest{MetricItemId: item.Id})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var countCharts = countChartsResp.Count
|
||||||
|
|
||||||
parent.Data["item"] = maps.Map{
|
parent.Data["item"] = maps.Map{
|
||||||
"id": item.Id,
|
"id": item.Id,
|
||||||
"name": item.Name,
|
"name": item.Name,
|
||||||
@@ -36,6 +43,7 @@ func InitItem(parent *actionutils.ParentAction, itemId int64) (*pb.MetricItem, e
|
|||||||
"periodUnit": item.PeriodUnit,
|
"periodUnit": item.PeriodUnit,
|
||||||
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
|
"periodUnitName": serverconfigs.FindMetricPeriodUnitName(item.PeriodUnit),
|
||||||
"category": item.Category,
|
"category": item.Category,
|
||||||
|
"countCharts": countCharts,
|
||||||
}
|
}
|
||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
var tipKeyMap = map[string]bool{}
|
var tipKeyMap = map[string]bool{}
|
||||||
var tipKeyLocker = sync.Mutex{}
|
var tipKeyLocker = sync.Mutex{}
|
||||||
var tipConfigFile = "tip.json"
|
var tipConfigFile = "tip.cache.json"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||||
|
|||||||
@@ -353,5 +353,122 @@ window.teaweb = {
|
|||||||
},
|
},
|
||||||
reload: function () {
|
reload: function () {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
|
},
|
||||||
|
renderBarChart: function (options) {
|
||||||
|
let chartId = options.id
|
||||||
|
let name = options.name
|
||||||
|
let values = options.values
|
||||||
|
let xFunc = options.x
|
||||||
|
let tooltipFunc = options.tooltip
|
||||||
|
let axis = options.axis
|
||||||
|
let valueFunc = options.value
|
||||||
|
|
||||||
|
let chartBox = document.getElementById(chartId)
|
||||||
|
if (chartBox == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: values.map(xFunc),
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + axis.unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: function (args) {
|
||||||
|
return tooltipFunc.apply(this, [args, values])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
top: 10,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
type: "bar",
|
||||||
|
data: values.map(valueFunc),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
barWidth: "20em"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animation: true
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
chart.resize()
|
||||||
|
},
|
||||||
|
renderLineChart: function (options) {
|
||||||
|
let chartId = options.id
|
||||||
|
let name = options.name
|
||||||
|
let values = options.values
|
||||||
|
let xFunc = options.x
|
||||||
|
let tooltipFunc = options.tooltip
|
||||||
|
let axis = options.axis
|
||||||
|
let valueFunc = options.value
|
||||||
|
let max = options.max
|
||||||
|
let interval = options.interval
|
||||||
|
|
||||||
|
let chartBox = document.getElementById(chartId)
|
||||||
|
if (chartBox == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: values.map(xFunc),
|
||||||
|
axisLabel: {
|
||||||
|
interval: interval
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + axis.unit
|
||||||
|
}
|
||||||
|
},
|
||||||
|
max: max
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: function (args) {
|
||||||
|
return tooltipFunc.apply(this, [args, values])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
top: 10,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
type: "line",
|
||||||
|
data: values.map(valueFunc),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animation: true
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
chart.resize()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<second-menu>
|
<second-menu>
|
||||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId" code="index">节点列表</menu-item>
|
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId" code="index">节点列表</menu-item>
|
||||||
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
|
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
|
||||||
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
|
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
|
||||||
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
|
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
|
||||||
|
|||||||
32
web/views/@default/clusters/cluster/boards/index.css
Normal file
32
web/views/@default/clusters/cluster/boards/index.css
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.grid {
|
||||||
|
margin-top: 2em !important;
|
||||||
|
margin-left: 2em !important;
|
||||||
|
}
|
||||||
|
.grid .column {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border-right: 1px #eee solid;
|
||||||
|
}
|
||||||
|
.grid .column div.value {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
.grid .column div.value span {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
.grid .column.no-border {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
.grid h4 a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.grid .column:hover a {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
h4 span {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=index.css.map */
|
||||||
1
web/views/@default/clusters/cluster/boards/index.css.map
Normal file
1
web/views/@default/clusters/cluster/boards/index.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA;;AAGD,EACC;EACC,gBAAA;EACA,WAAA","file":"index.css"}
|
||||||
69
web/views/@default/clusters/cluster/boards/index.html
Normal file
69
web/views/@default/clusters/cluster/boards/index.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{$layout}
|
||||||
|
{$template "/echarts"}
|
||||||
|
|
||||||
|
<div class="ui four columns grid">
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>在线节点<link-icon :href="'/clusters/cluster/nodes?clusterId=' + clusterId"></link-icon></h4>
|
||||||
|
<div class="value"><span>{{board.countActiveNodes}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>离线节点<link-icon :href="'/clusters/cluster/nodes?clusterId=' + clusterId"></link-icon></h4>
|
||||||
|
<div class="value"><span :class="{red: board.countInactiveNodes > 0}">{{board.countInactiveNodes}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>服务</h4>
|
||||||
|
<div class="value"><span>{{board.countServers}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui column no-border">
|
||||||
|
<h4>用户</h4>
|
||||||
|
<div class="value"><span>{{board.countUsers}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui menu tabular">
|
||||||
|
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势</a>
|
||||||
|
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 按小时统计流量 -->
|
||||||
|
<div class="chart-box" id="hourly-traffic-chart" v-show="trafficTab == 'hourly'"></div>
|
||||||
|
|
||||||
|
<!-- 按日统计流量 -->
|
||||||
|
<div class="chart-box" id="daily-traffic-chart" v-show="trafficTab == 'daily'"></div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<div class="ui menu tabular">
|
||||||
|
<a href="" class="item" :class="{active: requestsTab == 'hourly'}" @click.prevent="selectRequestsTab('hourly')">24小时访问量趋势</a>
|
||||||
|
<a href="" class="item" :class="{active: requestsTab == 'daily'}" @click.prevent="selectRequestsTab('daily')">15天访问量趋势</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 按小时统计访问量 -->
|
||||||
|
<div class="chart-box" id="hourly-requests-chart" v-show="requestsTab == 'hourly'"></div>
|
||||||
|
|
||||||
|
<!-- 按日统计访问量 -->
|
||||||
|
<div class="chart-box" id="daily-requests-chart" v-show="requestsTab == 'daily'"></div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<!-- 域名排行 -->
|
||||||
|
<h4>域名排行 <span>(24小时)</span></h4>
|
||||||
|
<div class="chart-box" id="top-domains-chart"></div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<!-- 节点排行 -->
|
||||||
|
<h4>节点排行 <span>(24小时)</span></h4>
|
||||||
|
<div class="chart-box" id="top-nodes-chart"></div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<div class="ui menu tabular">
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'cpu'}" @click.prevent="selectNodeStatusTab('cpu')">节点CPU</a>
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'memory'}" @click.prevent="selectNodeStatusTab('memory')">节点内存</a>
|
||||||
|
<a href="" class="item" :class="{active: nodeStatusTab == 'load'}" @click.prevent="selectNodeStatusTab('load')">节点负载</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-box" id="cpu-chart" v-show="nodeStatusTab == 'cpu'"></div>
|
||||||
|
<div class="chart-box" id="memory-chart" v-show="nodeStatusTab == 'memory'"></div>
|
||||||
|
<div class="chart-box" id="load-chart" v-show="nodeStatusTab == 'load'"></div>
|
||||||
405
web/views/@default/clusters/cluster/boards/index.js
Normal file
405
web/views/@default/clusters/cluster/boards/index.js
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
/**
|
||||||
|
* 流量统计
|
||||||
|
*/
|
||||||
|
this.trafficTab = "hourly"
|
||||||
|
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyTrafficChart()
|
||||||
|
this.reloadHourlyRequestsChart()
|
||||||
|
this.reloadTopNodeStatsChart()
|
||||||
|
this.reloadTopDomainsChart()
|
||||||
|
this.reloadCPUChart()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.selectTrafficTab = function (tab) {
|
||||||
|
this.trafficTab = tab
|
||||||
|
if (tab == "hourly") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyTrafficChart()
|
||||||
|
})
|
||||||
|
} else if (tab == "daily") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadDailyTrafficChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadHourlyTrafficChart = function () {
|
||||||
|
let stats = this.hourlyStats
|
||||||
|
this.reloadTrafficChart("hourly-traffic-chart", "流量统计", stats, function (args) {
|
||||||
|
if (args.seriesIndex == 0) {
|
||||||
|
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时 流量: " + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
}
|
||||||
|
if (args.seriesIndex == 1) {
|
||||||
|
let ratio = 0
|
||||||
|
if (stats[args.dataIndex].bytes > 0) {
|
||||||
|
ratio = Math.round(stats[args.dataIndex].cachedBytes * 10000 / stats[args.dataIndex].bytes) / 100
|
||||||
|
}
|
||||||
|
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时 缓存流量: " + teaweb.formatBytes(stats[args.dataIndex].cachedBytes) + ", 命中率:" + ratio + "%"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDailyTrafficChart = function () {
|
||||||
|
let stats = this.dailyStats
|
||||||
|
this.reloadTrafficChart("daily-traffic-chart", "流量统计", stats, function (args) {
|
||||||
|
if (args.seriesIndex == 0) {
|
||||||
|
return stats[args.dataIndex].day + " 流量: " + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
}
|
||||||
|
if (args.seriesIndex == 1) {
|
||||||
|
let ratio = 0
|
||||||
|
if (stats[args.dataIndex].bytes > 0) {
|
||||||
|
ratio = Math.round(stats[args.dataIndex].cachedBytes * 10000 / stats[args.dataIndex].bytes) / 100
|
||||||
|
}
|
||||||
|
return stats[args.dataIndex].day + " 缓存流量: " + teaweb.formatBytes(stats[args.dataIndex].cachedBytes) + ", 命中率:" + ratio + "%"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadTrafficChart = function (chartId, name, stats, tooltipFunc) {
|
||||||
|
let chartBox = document.getElementById(chartId)
|
||||||
|
if (chartBox == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let axis = teaweb.bytesAxis(stats, function (v) {
|
||||||
|
return Math.max(v.bytes, v.cachedBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
if (v.hour != null) {
|
||||||
|
return v.hour
|
||||||
|
}
|
||||||
|
return v.day
|
||||||
|
})
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + axis.unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: tooltipFunc
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 50,
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "流量",
|
||||||
|
type: "line",
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
return v.bytes / axis.divider
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "缓存流量",
|
||||||
|
type: "line",
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
return v.cachedBytes / axis.divider
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#61A0A8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#61A0A8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
legend: {
|
||||||
|
data: ['流量', '缓存流量']
|
||||||
|
},
|
||||||
|
animation: true
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求数统计
|
||||||
|
*/
|
||||||
|
this.requestsTab = "hourly"
|
||||||
|
|
||||||
|
this.selectRequestsTab = function (tab) {
|
||||||
|
this.requestsTab = tab
|
||||||
|
if (tab == "hourly") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyRequestsChart()
|
||||||
|
})
|
||||||
|
} else if (tab == "daily") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadDailyRequestsChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadHourlyRequestsChart = function () {
|
||||||
|
let stats = this.hourlyStats
|
||||||
|
this.reloadRequestsChart("hourly-requests-chart", "请求数统计", stats, function (args) {
|
||||||
|
if (args.seriesIndex == 0) {
|
||||||
|
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时 请求数: " + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||||
|
}
|
||||||
|
if (args.seriesIndex == 1) {
|
||||||
|
let ratio = 0
|
||||||
|
if (stats[args.dataIndex].countRequests > 0) {
|
||||||
|
ratio = Math.round(stats[args.dataIndex].countCachedRequests * 10000 / stats[args.dataIndex].countRequests) / 100
|
||||||
|
}
|
||||||
|
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时 缓存请求数: " + teaweb.formatNumber(stats[args.dataIndex].countCachedRequests) + ", 命中率:" + ratio + "%"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDailyRequestsChart = function () {
|
||||||
|
let stats = this.dailyStats
|
||||||
|
this.reloadRequestsChart("daily-requests-chart", "请求数统计", stats, function (args) {
|
||||||
|
if (args.seriesIndex == 0) {
|
||||||
|
return stats[args.dataIndex].day + " 请求数: " + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||||
|
}
|
||||||
|
if (args.seriesIndex == 1) {
|
||||||
|
let ratio = 0
|
||||||
|
if (stats[args.dataIndex].countRequests > 0) {
|
||||||
|
ratio = Math.round(stats[args.dataIndex].countCachedRequests * 10000 / stats[args.dataIndex].countRequests) / 100
|
||||||
|
}
|
||||||
|
return stats[args.dataIndex].day + " 缓存请求数: " + teaweb.formatNumber(stats[args.dataIndex].countCachedRequests) + ", 命中率:" + ratio + "%"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadRequestsChart = function (chartId, name, stats, tooltipFunc) {
|
||||||
|
let chartBox = document.getElementById(chartId)
|
||||||
|
if (chartBox == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let axis = teaweb.countAxis(stats, function (v) {
|
||||||
|
return Math.max(v.countRequests, v.countCachedRequests)
|
||||||
|
})
|
||||||
|
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
if (v.hour != null) {
|
||||||
|
return v.hour
|
||||||
|
}
|
||||||
|
if (v.day != null) {
|
||||||
|
return v.day
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (value) {
|
||||||
|
return value + axis.unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: tooltipFunc
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 50,
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "请求数",
|
||||||
|
type: "line",
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
return v.countRequests / axis.divider
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "缓存请求数",
|
||||||
|
type: "line",
|
||||||
|
data: stats.map(function (v) {
|
||||||
|
return v.countCachedRequests / axis.divider
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#61A0A8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#61A0A8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
legend: {
|
||||||
|
data: ['请求数', '缓存请求数']
|
||||||
|
},
|
||||||
|
animation: true
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点排行
|
||||||
|
this.reloadTopNodeStatsChart = function () {
|
||||||
|
let that = this
|
||||||
|
let axis = teaweb.countAxis(this.topNodeStats, function (v) {
|
||||||
|
return v.countRequests
|
||||||
|
})
|
||||||
|
teaweb.renderBarChart({
|
||||||
|
id: "top-nodes-chart",
|
||||||
|
name: "节点",
|
||||||
|
values: this.topNodeStats,
|
||||||
|
x: function (v) {
|
||||||
|
return v.nodeName
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].nodeName + "<br/>请求数:" + " " + teaweb.formatNumber(stats[args.dataIndex].countRequests) + "<br/>流量:" + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.countRequests / axis.divider;
|
||||||
|
},
|
||||||
|
axis: axis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 域名排行
|
||||||
|
this.reloadTopDomainsChart = function () {
|
||||||
|
let axis = teaweb.countAxis(this.topDomainStats, function (v) {
|
||||||
|
return v.countRequests
|
||||||
|
})
|
||||||
|
teaweb.renderBarChart({
|
||||||
|
id: "top-domains-chart",
|
||||||
|
name: "域名",
|
||||||
|
values: this.topDomainStats,
|
||||||
|
x: function (v) {
|
||||||
|
return v.domain
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].domain + "<br/>请求数:" + " " + teaweb.formatNumber(stats[args.dataIndex].countRequests) + "<br/>流量:" + teaweb.formatBytes(stats[args.dataIndex].bytes)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.countRequests / axis.divider;
|
||||||
|
},
|
||||||
|
axis: axis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统信息
|
||||||
|
*/
|
||||||
|
this.nodeStatusTab = "cpu"
|
||||||
|
|
||||||
|
this.selectNodeStatusTab = function (tab) {
|
||||||
|
this.nodeStatusTab = tab
|
||||||
|
this.$delay(function () {
|
||||||
|
switch (tab) {
|
||||||
|
case "cpu":
|
||||||
|
this.reloadCPUChart()
|
||||||
|
break
|
||||||
|
case "memory":
|
||||||
|
this.reloadMemoryChart()
|
||||||
|
break
|
||||||
|
case "load":
|
||||||
|
this.reloadLoadChart()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadCPUChart = function () {
|
||||||
|
let axis = {unit: "", divider: 1}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "cpu-chart",
|
||||||
|
name: "CPU",
|
||||||
|
values: this.cpuValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%"
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value * 100;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadMemoryChart = function () {
|
||||||
|
let axis = {unit: "", divider: 1}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "memory-chart",
|
||||||
|
name: "内存",
|
||||||
|
values: this.memoryValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%"
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value * 100;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadLoadChart = function () {
|
||||||
|
let axis = {unit: "", divider: 1}
|
||||||
|
let max = this.loadValues.$map(function (k, v) {
|
||||||
|
return v.value
|
||||||
|
}).$max()
|
||||||
|
if (max < 10) {
|
||||||
|
max = 10
|
||||||
|
} else if (max < 20) {
|
||||||
|
max = 20
|
||||||
|
} else if (max < 100) {
|
||||||
|
max = 100
|
||||||
|
} else {
|
||||||
|
max = null
|
||||||
|
}
|
||||||
|
teaweb.renderLineChart({
|
||||||
|
id: "load-chart",
|
||||||
|
name: "负载",
|
||||||
|
values: this.loadValues,
|
||||||
|
x: function (v) {
|
||||||
|
return v.time
|
||||||
|
},
|
||||||
|
tooltip: function (args, stats) {
|
||||||
|
return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100) / 100)
|
||||||
|
},
|
||||||
|
value: function (v) {
|
||||||
|
return v.value;
|
||||||
|
},
|
||||||
|
axis: axis,
|
||||||
|
max: max
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
45
web/views/@default/clusters/cluster/boards/index.less
Normal file
45
web/views/@default/clusters/cluster/boards/index.less
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.grid {
|
||||||
|
margin-top: 2em !important;
|
||||||
|
margin-left: 2em !important;
|
||||||
|
|
||||||
|
.column {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border-right: 1px #eee solid;
|
||||||
|
|
||||||
|
div.value {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.no-border {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column:hover {
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
span {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Tea.context(function () {
|
Tea.context(function () {
|
||||||
this.success = NotifySuccess("保存成功", "/clusters/cluster?clusterId=" + this.clusterId)
|
this.success = NotifySuccess("保存成功", "/clusters/cluster/nodes?clusterId=" + this.clusterId)
|
||||||
|
|
||||||
this.$delay(function () {
|
this.$delay(function () {
|
||||||
this.$refs.ipList.focus()
|
this.$refs.ipList.focus()
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Tea.context(function () {
|
Tea.context(function () {
|
||||||
this.success = NotifySuccess("保存成功", "/clusters/cluster?clusterId=" + this.clusterId);
|
this.success = NotifySuccess("保存成功", "/clusters/cluster/nodes?clusterId=" + this.clusterId);
|
||||||
});
|
});
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA","file":"index.css"}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<second-menu>
|
<second-menu>
|
||||||
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">节点列表</menu-item>
|
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId">节点列表</menu-item>
|
||||||
<span class="item">|</span>
|
<span class="item">|</span>
|
||||||
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + nodeId" code="node">节点详情</menu-item>
|
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + nodeId" code="node">节点详情</menu-item>
|
||||||
<menu-item :href="'/clusters/cluster/node/monitor?clusterId=' + clusterId + '&nodeId=' + nodeId" code="monitor" v-if="teaIsPlus">监控图表</menu-item>
|
<menu-item :href="'/clusters/cluster/node/monitor?clusterId=' + clusterId + '&nodeId=' + nodeId" code="monitor" v-if="teaIsPlus">监控图表</menu-item>
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
a .red {
|
a .red {
|
||||||
border-bottom: 1px #db2828 dashed;
|
border-bottom: 1px #db2828 dashed;
|
||||||
}
|
}
|
||||||
/*# sourceMappingURL=index.css.map */
|
/*# sourceMappingURL=nodes.css.map */
|
||||||
1
web/views/@default/clusters/cluster/nodes.css.map
Normal file
1
web/views/@default/clusters/cluster/nodes.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA","file":"nodes.css"}
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
<tr v-for="item in items">
|
<tr v-for="item in items">
|
||||||
<td>{{item.name}}</td>
|
<td>{{item.name}}</td>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="item.keys != null" v-for="key in item.keys" class="ui label basic small">{{key}}</span>
|
<div v-if="item.keys != null" v-for="key in item.keys" style="margin-top: 0.2em; margin-bottom: 0.2em"><metric-key-label :v-key="key"></metric-key-label></div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{item.period}} {{item.periodUnit}}
|
{{item.period}} {{item.periodUnitName}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="ui label small basic">{{item.value}}</span>
|
<span class="ui label small basic">{{item.valueName}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<label-on :v-is-on="item.isOn"></label-on>
|
<label-on :v-is-on="item.isOn"></label-on>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<span class="item disabled">|</span>
|
<span class="item disabled">|</span>
|
||||||
<menu-item @click.prevent="createItem">[添加指标]</menu-item>
|
<menu-item @click.prevent="createItem">[添加指标]</menu-item>
|
||||||
<span class="item disabled">|</span>
|
<span class="item disabled">|</span>
|
||||||
<span class="item"><tip-icon content="在这里设置的指标,会自动应用到部署在当前集群上的所有服务上。"></tip-icon></span>
|
<span class="item"><tip-icon content="在这里设置的指标,会自动应用到部署在当前集群上的所有服务上。<br/><br/>指标收集和运算通常需要消耗一定量的边缘节点系统资源,所以请谨慎选择。"></tip-icon></span>
|
||||||
</first-menu>
|
</first-menu>
|
||||||
|
|
||||||
<p class="comment" v-if="items.length == 0">暂时还没有添加指标。</p>
|
<p class="comment" v-if="items.length == 0">暂时还没有添加指标。</p>
|
||||||
@@ -28,13 +28,13 @@
|
|||||||
<tr v-for="item in items">
|
<tr v-for="item in items">
|
||||||
<td>{{item.name}}</td>
|
<td>{{item.name}}</td>
|
||||||
<td>
|
<td>
|
||||||
<span v-if="item.keys != null" v-for="key in item.keys" class="ui label basic small">{{key}}</span>
|
<div v-if="item.keys != null" v-for="key in item.keys" style="margin-top: 0.2em; margin-bottom: 0.2em"><metric-key-label :v-key="key"></metric-key-label></div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{item.period}} {{item.periodUnit}}
|
{{item.period}} {{item.periodUnitName}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="ui label small basic">{{item.value}}</span>
|
<span class="ui label small basic">{{item.valueName}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<label-on :v-is-on="item.isOn"></label-on>
|
<label-on :v-is-on="item.isOn"></label-on>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<th>集群名称</th>
|
<th>集群名称</th>
|
||||||
<th class="center width10">节点数</th>
|
<th class="center width10">节点数</th>
|
||||||
<th class="center width10">在线节点数</th>
|
<th class="center width10">在线节点数</th>
|
||||||
|
<th class="center width10">服务数</th>
|
||||||
<th>DNS域名</th>
|
<th>DNS域名</th>
|
||||||
<th class="two op">操作</th>
|
<th class="two op">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
<tr v-for="cluster in clusters">
|
<tr v-for="cluster in clusters">
|
||||||
<td><keyword :v-word="keyword">{{cluster.name}}</keyword></td>
|
<td><keyword :v-word="keyword">{{cluster.name}}</keyword></td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id" v-if="cluster.countAllNodes > 0"><span :class="{red:cluster.countAllNodes > cluster.countActiveNodes}">{{cluster.countAllNodes}}</span></a>
|
<a :href="'/clusters/cluster/nodes?clusterId=' + cluster.id" v-if="cluster.countAllNodes > 0"><span :class="{red:cluster.countAllNodes > cluster.countActiveNodes}">{{cluster.countAllNodes}}</span></a>
|
||||||
<span class="disabled" v-else="">-</span>
|
<span class="disabled" v-else="">-</span>
|
||||||
|
|
||||||
<div v-if="cluster.countUpgradeNodes > 0" style="margin-top:0.5em">
|
<div v-if="cluster.countUpgradeNodes > 0" style="margin-top:0.5em">
|
||||||
@@ -50,7 +51,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
<a :href="'/clusters/cluster?clusterId=' + cluster.id + '&activeState=1'" v-if="cluster.countActiveNodes > 0"><span class="green">{{cluster.countActiveNodes}}</span></a>
|
<a :href="'/clusters/cluster/nodes?clusterId=' + cluster.id + '&activeState=1'" v-if="cluster.countActiveNodes > 0"><span class="green">{{cluster.countActiveNodes}}</span></a>
|
||||||
|
<span class="disabled" v-else>-</span>
|
||||||
|
</td>
|
||||||
|
<td class="center">
|
||||||
|
<span v-if="cluster.countServers > 0">{{cluster.countServers}}</span>
|
||||||
<span class="disabled" v-else>-</span>
|
<span class="disabled" v-else>-</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAMH;EACC,YAAA","file":"index.css"}
|
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA","file":"index.css"}
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
<div class="ui menu tabular">
|
<div class="ui menu tabular">
|
||||||
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势(GB)</a>
|
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势(GBps)</a>
|
||||||
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势(GB)</a>
|
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势(GBps)</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 按小时统计 -->
|
<!-- 按小时统计 -->
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
Tea.context(function () {
|
Tea.context(function () {
|
||||||
this.trafficTab = "hourly"
|
this.trafficTab = "hourly"
|
||||||
|
|
||||||
|
|
||||||
this.$delay(function () {
|
this.$delay(function () {
|
||||||
this.reloadHourlyTrafficChart()
|
this.reloadHourlyTrafficChart()
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chart-box {
|
.chart-box {
|
||||||
height: 20em;
|
height: 20em;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<first-menu>
|
<first-menu>
|
||||||
<menu-item href="/servers" code="index">服务列表</menu-item>
|
<menu-item href="/servers" code="index">服务列表</menu-item>
|
||||||
<menu-item href="/servers?auditingFlag=1" code="auditing">审核中<span :class="{red: countAuditing > 0}">({{countAuditing}})</span></menu-item>
|
<menu-item href="/servers?auditingFlag=1" code="auditing">审核中<span :class="{red: countAuditing > 0}">({{countAuditing}})</span></menu-item>
|
||||||
<menu-item href="/servers/create" code="create">创建服务</menu-item>
|
<menu-item href="/servers/create" code="create">创建网站服务</menu-item>
|
||||||
</first-menu>
|
</first-menu>
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
<span class="item disabled">|</span>
|
<span class="item disabled">|</span>
|
||||||
<menu-item :href="'/servers/metrics/item?itemId=' + item.id" code="item">"{{item.name}}"详情</menu-item>
|
<menu-item :href="'/servers/metrics/item?itemId=' + item.id" code="item">"{{item.name}}"详情</menu-item>
|
||||||
<menu-item :href="'/servers/metrics/update?itemId=' + item.id" code="update">修改</menu-item>
|
<menu-item :href="'/servers/metrics/update?itemId=' + item.id" code="update">修改</menu-item>
|
||||||
<menu-item :href="'/servers/metrics/charts?itemId=' + item.id" code="chart">图表</menu-item>
|
<menu-item :href="'/servers/metrics/charts?itemId=' + item.id" code="chart">图表({{item.countCharts}})</menu-item>
|
||||||
<menu-item :href="'/servers/metrics/stats?itemId=' + item.id" code="stat">数据</menu-item>
|
<menu-item :href="'/servers/metrics/stats?itemId=' + item.id" code="stat">数据</menu-item>
|
||||||
</first-menu>
|
</first-menu>
|
||||||
Reference in New Issue
Block a user