mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	增加节点列表
This commit is contained in:
		@@ -36,6 +36,7 @@ func (this *NodesAction) RunGet(params struct {
 | 
			
		||||
	MemoryOrder     string
 | 
			
		||||
	TrafficInOrder  string
 | 
			
		||||
	TrafficOutOrder string
 | 
			
		||||
	LoadOrder       string
 | 
			
		||||
}) {
 | 
			
		||||
	this.Data["groupId"] = params.GroupId
 | 
			
		||||
	this.Data["regionId"] = params.RegionId
 | 
			
		||||
@@ -43,6 +44,7 @@ func (this *NodesAction) RunGet(params struct {
 | 
			
		||||
	this.Data["activeState"] = params.ActiveState
 | 
			
		||||
	this.Data["keyword"] = params.Keyword
 | 
			
		||||
	this.Data["level"] = params.Level
 | 
			
		||||
	this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0
 | 
			
		||||
 | 
			
		||||
	// 集群是否已经设置了线路
 | 
			
		||||
	clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
 | 
			
		||||
@@ -106,6 +108,10 @@ func (this *NodesAction) RunGet(params struct {
 | 
			
		||||
		req.TrafficOutAsc = true
 | 
			
		||||
	} else if params.TrafficOutOrder == "desc" {
 | 
			
		||||
		req.TrafficOutDesc = true
 | 
			
		||||
	} else if params.LoadOrder == "asc" {
 | 
			
		||||
		req.LoadAsc = true
 | 
			
		||||
	} else if params.LoadOrder == "desc" {
 | 
			
		||||
		req.LoadDesc = true
 | 
			
		||||
	}
 | 
			
		||||
	nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -204,6 +210,7 @@ func (this *NodesAction) RunGet(params struct {
 | 
			
		||||
				"memUsageText":    fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
 | 
			
		||||
				"trafficInBytes":  status.TrafficInBytes,
 | 
			
		||||
				"trafficOutBytes": status.TrafficOutBytes,
 | 
			
		||||
				"load1m":          fmt.Sprintf("%.2f", status.Load1m),
 | 
			
		||||
			},
 | 
			
		||||
			"cluster": maps.Map{
 | 
			
		||||
				"id":   node.NodeCluster.Id,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ func (this *CreateAction) RunGet(params struct{}) {
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["hasDomains"] = hasDomainsResp.Exist
 | 
			
		||||
 | 
			
		||||
	// 集群总数
 | 
			
		||||
	// 菜单:集群总数
 | 
			
		||||
	totalResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
@@ -35,6 +35,14 @@ func (this *CreateAction) RunGet(params struct{}) {
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["totalNodeClusters"] = totalResp.Count
 | 
			
		||||
 | 
			
		||||
	// 菜单:节点总数
 | 
			
		||||
	totalNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodes(this.AdminContext(), &pb.CountAllEnabledNodesRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["totalNodes"] = totalNodesResp.Count
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
package clusters
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"github.com/iwind/TeaGo/types"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type IndexAction struct {
 | 
			
		||||
@@ -24,18 +21,26 @@ func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
	Keyword    string
 | 
			
		||||
	SearchType string
 | 
			
		||||
}) {
 | 
			
		||||
	isSearching := len(params.Keyword) > 0
 | 
			
		||||
	var isSearching = len(params.Keyword) > 0
 | 
			
		||||
	this.Data["keyword"] = params.Keyword
 | 
			
		||||
	this.Data["searchType"] = params.SearchType
 | 
			
		||||
	this.Data["isSearching"] = isSearching
 | 
			
		||||
 | 
			
		||||
	// 集群总数
 | 
			
		||||
	totalResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{})
 | 
			
		||||
	totalClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["totalNodeClusters"] = totalResp.Count
 | 
			
		||||
	this.Data["totalNodeClusters"] = totalClustersResp.Count
 | 
			
		||||
 | 
			
		||||
	// 节点总数
 | 
			
		||||
	totalNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodes(this.AdminContext(), &pb.CountAllEnabledNodesRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["totalNodes"] = totalNodesResp.Count
 | 
			
		||||
 | 
			
		||||
	// 常用的集群
 | 
			
		||||
	latestClusterMaps := []maps.Map{}
 | 
			
		||||
@@ -54,12 +59,6 @@ func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["latestClusters"] = latestClusterMaps
 | 
			
		||||
 | 
			
		||||
	// 搜索节点
 | 
			
		||||
	if params.SearchType == "node" && len(params.Keyword) > 0 {
 | 
			
		||||
		this.searchNodes(params.Keyword)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 搜索集群
 | 
			
		||||
	countResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{
 | 
			
		||||
		Keyword: params.Keyword,
 | 
			
		||||
@@ -166,149 +165,3 @@ func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *IndexAction) searchNodes(keyword string) {
 | 
			
		||||
	// 搜索节点
 | 
			
		||||
	countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
 | 
			
		||||
		Keyword: keyword,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	count := countResp.Count
 | 
			
		||||
	page := this.NewPage(count)
 | 
			
		||||
	this.Data["page"] = page.AsHTML()
 | 
			
		||||
	this.Data["countNodes"] = count
 | 
			
		||||
 | 
			
		||||
	nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), &pb.ListEnabledNodesMatchRequest{
 | 
			
		||||
		Offset:  page.Offset,
 | 
			
		||||
		Size:    page.Size,
 | 
			
		||||
		Keyword: 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 {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
 | 
			
		||||
			isSynced = status.ConfigVersion == node.Version
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// IP
 | 
			
		||||
		ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
 | 
			
		||||
			NodeId: node.Id,
 | 
			
		||||
			Role:   nodeconfigs.NodeRoleNode,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ipAddresses := []maps.Map{}
 | 
			
		||||
		for _, addr := range ipAddressesResp.NodeIPAddresses {
 | 
			
		||||
			ipAddresses = append(ipAddresses, maps.Map{
 | 
			
		||||
				"id":        addr.Id,
 | 
			
		||||
				"name":      addr.Name,
 | 
			
		||||
				"ip":        addr.Ip,
 | 
			
		||||
				"canAccess": addr.CanAccess,
 | 
			
		||||
				"isOn":      addr.IsOn,
 | 
			
		||||
				"isUp":      addr.IsUp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 分组
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 从集群
 | 
			
		||||
		var secondaryClusterMaps []maps.Map
 | 
			
		||||
		for _, secondaryCluster := range node.SecondaryNodeClusters {
 | 
			
		||||
			secondaryClusterMaps = append(secondaryClusterMaps, maps.Map{
 | 
			
		||||
				"id":   secondaryCluster.Id,
 | 
			
		||||
				"name": secondaryCluster.Name,
 | 
			
		||||
				"isOn": secondaryCluster.IsOn,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
			},
 | 
			
		||||
			"secondaryClusters": secondaryClusterMaps,
 | 
			
		||||
			"isSynced":          isSynced,
 | 
			
		||||
			"ipAddresses":       ipAddresses,
 | 
			
		||||
			"group":             groupMap,
 | 
			
		||||
			"region":            regionMap,
 | 
			
		||||
			"dnsRouteNames":     dnsRouteNames,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["nodes"] = nodeMaps
 | 
			
		||||
 | 
			
		||||
	this.Data["clusters"] = []maps.Map{}
 | 
			
		||||
 | 
			
		||||
	// 搜索集群
 | 
			
		||||
	{
 | 
			
		||||
		countResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{
 | 
			
		||||
			Keyword: keyword,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		this.Data["countClusters"] = countResp.Count
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ func init() {
 | 
			
		||||
			Get("", new(IndexAction)).
 | 
			
		||||
			GetPost("/create", new(CreateAction)).
 | 
			
		||||
			Post("/pin", new(PinAction)).
 | 
			
		||||
			Get("/nodes", new(NodesAction)).
 | 
			
		||||
 | 
			
		||||
			// 只要登录即可访问的Action
 | 
			
		||||
			EndHelpers().
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										293
									
								
								internal/web/actions/default/clusters/nodes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								internal/web/actions/default/clusters/nodes.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,293 @@
 | 
			
		||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
			
		||||
 | 
			
		||||
package clusters
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
 | 
			
		||||
	"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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *NodesAction) RunGet(params struct {
 | 
			
		||||
	ClusterId      int64
 | 
			
		||||
	GroupId        int64
 | 
			
		||||
	RegionId       int64
 | 
			
		||||
	InstalledState int
 | 
			
		||||
	ActiveState    int
 | 
			
		||||
	Keyword        string
 | 
			
		||||
	Level          int32
 | 
			
		||||
 | 
			
		||||
	CpuOrder        string
 | 
			
		||||
	MemoryOrder     string
 | 
			
		||||
	TrafficInOrder  string
 | 
			
		||||
	TrafficOutOrder string
 | 
			
		||||
	LoadOrder       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
 | 
			
		||||
	this.Data["level"] = params.Level
 | 
			
		||||
	this.Data["clusterId"] = params.ClusterId
 | 
			
		||||
	this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0
 | 
			
		||||
 | 
			
		||||
	// 集群是否已经设置了线路
 | 
			
		||||
	clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["hasClusterDNS"] = clusterDNSResp.Domain != nil
 | 
			
		||||
 | 
			
		||||
	// 数量
 | 
			
		||||
	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,
 | 
			
		||||
		Level:         params.Level,
 | 
			
		||||
		InstallState:  types.Int32(params.InstalledState),
 | 
			
		||||
		ActiveState:   types.Int32(params.ActiveState),
 | 
			
		||||
		Keyword:       params.Keyword,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var page = this.NewPage(countResp.Count)
 | 
			
		||||
	this.Data["page"] = page.AsHTML()
 | 
			
		||||
 | 
			
		||||
	var req = &pb.ListEnabledNodesMatchRequest{
 | 
			
		||||
		Offset:        page.Offset,
 | 
			
		||||
		Size:          page.Size,
 | 
			
		||||
		NodeClusterId: params.ClusterId,
 | 
			
		||||
		NodeGroupId:   params.GroupId,
 | 
			
		||||
		NodeRegionId:  params.RegionId,
 | 
			
		||||
		Level:         params.Level,
 | 
			
		||||
		InstallState:  types.Int32(params.InstalledState),
 | 
			
		||||
		ActiveState:   types.Int32(params.ActiveState),
 | 
			
		||||
		Keyword:       params.Keyword,
 | 
			
		||||
	}
 | 
			
		||||
	if params.CpuOrder == "asc" {
 | 
			
		||||
		req.CpuAsc = true
 | 
			
		||||
	} else if params.CpuOrder == "desc" {
 | 
			
		||||
		req.CpuDesc = true
 | 
			
		||||
	} else if params.MemoryOrder == "asc" {
 | 
			
		||||
		req.MemoryAsc = true
 | 
			
		||||
	} else if params.MemoryOrder == "desc" {
 | 
			
		||||
		req.MemoryDesc = true
 | 
			
		||||
	} else if params.TrafficInOrder == "asc" {
 | 
			
		||||
		req.TrafficInAsc = true
 | 
			
		||||
	} else if params.TrafficInOrder == "desc" {
 | 
			
		||||
		req.TrafficInDesc = true
 | 
			
		||||
	} else if params.TrafficOutOrder == "asc" {
 | 
			
		||||
		req.TrafficOutAsc = true
 | 
			
		||||
	} else if params.TrafficOutOrder == "desc" {
 | 
			
		||||
		req.TrafficOutDesc = true
 | 
			
		||||
	} else if params.LoadOrder == "asc" {
 | 
			
		||||
		req.LoadAsc = true
 | 
			
		||||
	} else if params.LoadOrder == "desc" {
 | 
			
		||||
		req.LoadDesc = true
 | 
			
		||||
	}
 | 
			
		||||
	nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var 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().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
 | 
			
		||||
			NodeId: node.Id,
 | 
			
		||||
			Role:   nodeconfigs.NodeRoleNode,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ipAddresses := []maps.Map{}
 | 
			
		||||
		for _, addr := range ipAddressesResp.NodeIPAddresses {
 | 
			
		||||
			ipAddresses = append(ipAddresses, maps.Map{
 | 
			
		||||
				"id":        addr.Id,
 | 
			
		||||
				"name":      addr.Name,
 | 
			
		||||
				"ip":        addr.Ip,
 | 
			
		||||
				"canAccess": addr.CanAccess,
 | 
			
		||||
				"isUp":      addr.IsUp,
 | 
			
		||||
				"isOn":      addr.IsOn,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 分组
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 从集群
 | 
			
		||||
		var secondaryClusterMaps []maps.Map
 | 
			
		||||
		for _, secondaryCluster := range node.SecondaryNodeClusters {
 | 
			
		||||
			secondaryClusterMaps = append(secondaryClusterMaps, maps.Map{
 | 
			
		||||
				"id":   secondaryCluster.Id,
 | 
			
		||||
				"name": secondaryCluster.Name,
 | 
			
		||||
				"isOn": secondaryCluster.IsOn,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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),
 | 
			
		||||
				"trafficInBytes":  status.TrafficInBytes,
 | 
			
		||||
				"trafficOutBytes": status.TrafficOutBytes,
 | 
			
		||||
				"load1m":          fmt.Sprintf("%.2f", status.Load1m),
 | 
			
		||||
			},
 | 
			
		||||
			"cluster": maps.Map{
 | 
			
		||||
				"id":   node.NodeCluster.Id,
 | 
			
		||||
				"name": node.NodeCluster.Name,
 | 
			
		||||
			},
 | 
			
		||||
			"secondaryClusters": secondaryClusterMaps,
 | 
			
		||||
			"isSynced":          isSynced,
 | 
			
		||||
			"ipAddresses":       ipAddresses,
 | 
			
		||||
			"group":             groupMap,
 | 
			
		||||
			"region":            regionMap,
 | 
			
		||||
			"dnsRouteNames":     dnsRouteNames,
 | 
			
		||||
			"level":             node.Level,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["nodes"] = nodeMaps
 | 
			
		||||
 | 
			
		||||
	// 所有分组
 | 
			
		||||
	var 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 {
 | 
			
		||||
		countNodesInGroupResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		countNodes := countNodesInGroupResp.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
 | 
			
		||||
	}
 | 
			
		||||
	var regionMaps = []maps.Map{}
 | 
			
		||||
	for _, region := range regionsResp.NodeRegions {
 | 
			
		||||
		regionMaps = append(regionMaps, maps.Map{
 | 
			
		||||
			"id":   region.Id,
 | 
			
		||||
			"name": region.Name,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["regions"] = regionMaps
 | 
			
		||||
 | 
			
		||||
	// 级别
 | 
			
		||||
	this.Data["levels"] = []maps.Map{}
 | 
			
		||||
	if teaconst.IsPlus {
 | 
			
		||||
		this.Data["levels"] = nodeconfigs.FindAllNodeLevels()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 集群总数
 | 
			
		||||
	totalClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.AdminContext(), &pb.CountAllEnabledNodeClustersRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["totalNodeClusters"] = totalClustersResp.Count
 | 
			
		||||
 | 
			
		||||
	// 节点总数
 | 
			
		||||
	this.Data["totalNodes"] = countResp.Count
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,7 @@ Vue.component("node-cluster-combo-box", {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	template: `<div v-if="clusters.length > 0">
 | 
			
		||||
	template: `<div v-if="clusters.length > 0" style="min-width: 10.4em">
 | 
			
		||||
	<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
 | 
			
		||||
</div>`
 | 
			
		||||
})
 | 
			
		||||
@@ -144,7 +144,7 @@ Vue.component("combo-box", {
 | 
			
		||||
	<!-- 当前选中 -->
 | 
			
		||||
	<div v-if="selectedItem != null">
 | 
			
		||||
		<input type="hidden" :name="name" :value="selectedItem.value"/>
 | 
			
		||||
		<a href="" class="ui label basic" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}:{{selectedItem.name}}</span>
 | 
			
		||||
		<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}:{{selectedItem.name}}</span>
 | 
			
		||||
			<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
 | 
			
		||||
		</a>
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,13 @@
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h4 {
 | 
			
		||||
		color: grey;
 | 
			
		||||
		position: relative;
 | 
			
		||||
 | 
			
		||||
		a {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			right: 0.1em;
 | 
			
		||||
			font-size: 1.26em;
 | 
			
		||||
			display: none;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +41,7 @@
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.column:hover {
 | 
			
		||||
		background: rgba(0, 0, 0, .05)!important;
 | 
			
		||||
		background: rgba(0, 0, 0, .03)!important;
 | 
			
		||||
 | 
			
		||||
		a {
 | 
			
		||||
			display: inline;
 | 
			
		||||
 
 | 
			
		||||
@@ -128,14 +128,19 @@ body.expanded .right-box {
 | 
			
		||||
  border-right: 1px rgba(0, 0, 0, 0.1) solid;
 | 
			
		||||
}
 | 
			
		||||
.grid.counter-chart h4 {
 | 
			
		||||
  color: grey;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  font-size: 1em;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
}
 | 
			
		||||
.grid.counter-chart h4 a {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 0.1em;
 | 
			
		||||
  font-size: 1.26em;
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.grid.counter-chart .column:hover {
 | 
			
		||||
  background: rgba(0, 0, 0, 0.05) !important;
 | 
			
		||||
  background: rgba(0, 0, 0, 0.03) !important;
 | 
			
		||||
}
 | 
			
		||||
.grid.counter-chart .column:hover a {
 | 
			
		||||
  display: inline;
 | 
			
		||||
@@ -426,7 +431,7 @@ body.expanded .main {
 | 
			
		||||
  left: 22em;
 | 
			
		||||
  top: 5.6em;
 | 
			
		||||
  padding-bottom: 5em;
 | 
			
		||||
  padding-right: 1em;
 | 
			
		||||
  padding-right: 0.2em;
 | 
			
		||||
  right: 1em;
 | 
			
		||||
}
 | 
			
		||||
@media screen and (max-width: 512px) {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -367,7 +367,7 @@ body.expanded .main {
 | 
			
		||||
	left: 22em;
 | 
			
		||||
	top: 5.6em;
 | 
			
		||||
	padding-bottom: 5em;
 | 
			
		||||
	padding-right: 1em;
 | 
			
		||||
	padding-right: 0.2em;
 | 
			
		||||
	right: 1em;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,4 +48,10 @@ select.dropdown {
 | 
			
		||||
body.swal2-shown {
 | 
			
		||||
  overflow: auto !important;
 | 
			
		||||
}
 | 
			
		||||
.grid {
 | 
			
		||||
  margin-right: 0!important;
 | 
			
		||||
}
 | 
			
		||||
.fields button {
 | 
			
		||||
  min-width: 5em;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=@layout_override.css.map */
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"sources":["@layout_override.less"],"names":[],"mappings":"AACA,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,QAAO;EACrG,oCAAA;;AAGD,GAAG,OAAO,SAAU,MAAK,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,QAAS,QAAO;EACzF,oCAAA;;AAGD,GAAG,MAAM;EACR,kCAAA;;AAGD,GAAG,MAAM,MAAM;EACd,iCAAA;;AAID,IACC;EACC,2BAAA;;AAKF,KAAK;EACJ,sBAAA;;AAGD,KAAK,KAAK;EACT,yBAAA;;AAID,KACC,GAAE;AADH,KACY,GAAE;EACZ,6BAAA;EACA,0BAAA;EACA,2BAAA;;AAJF,KAOC,GAAE;EACD,WAAA;;AARF,KAWC,GAAE;EACD,UAAA;;AAZF,KAeC,GAAE;EACD,UAAA;;AAKF,QAAQ;EACP,qBAAA;;AAID,MAAM;EACL,uBAAA;;AAID,QACC,MAAK;EACJ,yBAAA;;AAKF,IAAI;EACH,yBAAA","file":"@layout_override.css"}
 | 
			
		||||
{"version":3,"sources":["@layout_override.less"],"names":[],"mappings":"AACA,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,QAAO;EACrG,yBAAA;;AAGD,GAAG,OAAO,SAAU,MAAK,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,QAAS,QAAO;EACzF,yBAAA;;AAGD,GAAG,MAAM;EACR,kCAAA;;AAGD,GAAG,MAAM,MAAM;EACd,sBAAA;;AAID,IACC;EACC,2BAAA;;AAKF,KAAK;EACJ,sBAAA;;AAGD,KAAK,KAAK;EACT,cAAA;;AAID,KACC,GAAE;AADH,KACY,GAAE;EACZ,6BAAA;EACA,0BAAA;EACA,2BAAA;;AAJF,KAOC,GAAE;EACD,WAAA;;AARF,KAWC,GAAE;EACD,UAAA;;AAZF,KAeC,GAAE;EACD,UAAA;;AAKF,QAAQ;EACP,qBAAA;;AAID,MAAM;EACL,uBAAA;;AAID,QACC,MAAK;EACJ,yBAAA;;AAKF,IAAI;EACH,yBAAA;;AAID;EACC,yBAAA;;AAID,OACC;EACC,cAAA","file":"@layout_override.css"}
 | 
			
		||||
@@ -72,4 +72,16 @@ select.dropdown {
 | 
			
		||||
// popup
 | 
			
		||||
body.swal2-shown {
 | 
			
		||||
	overflow: auto !important;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// grid
 | 
			
		||||
.grid {
 | 
			
		||||
	margin-right: 0!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fields
 | 
			
		||||
.fields {
 | 
			
		||||
	button {
 | 
			
		||||
		min-width: 5em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
<first-menu>
 | 
			
		||||
	<menu-item href="/clusters" code="index">集群列表({{totalNodeClusters}})</menu-item>
 | 
			
		||||
 | 
			
		||||
	<menu-item href="/clusters/create" code="create">创建集群</menu-item>
 | 
			
		||||
    <menu-item href="/clusters" code="index">集群 <span class="small">({{totalNodeClusters}})</span></menu-item>
 | 
			
		||||
    <menu-item href="/clusters/nodes" code="node">节点 <span class="small">({{totalNodes}})</span></menu-item>
 | 
			
		||||
    <span class="disabled item">|</span>
 | 
			
		||||
	<menu-item href="/clusters/create" code="create">[创建集群]</menu-item>
 | 
			
		||||
</first-menu>
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,7 @@
 | 
			
		||||
		<th class="width5 center">CPU<sort-arrow name="cpuOrder"></sort-arrow></th>
 | 
			
		||||
		<th class="width5 center">内存<sort-arrow name="memoryOrder"></sort-arrow></th>
 | 
			
		||||
		<th class="center" style="width: 7em">下行流量<sort-arrow name="trafficOutOrder"></sort-arrow></th>
 | 
			
		||||
        <th class="center" style="width: 7em">负载<sort-arrow name="loadOrder"></sort-arrow></th>
 | 
			
		||||
		<th class="two wide center">状态</th>
 | 
			
		||||
		<th class="two op">操作</th>
 | 
			
		||||
	</tr>
 | 
			
		||||
@@ -117,7 +118,11 @@
 | 
			
		||||
			<span v-else class="disabled">-</span>
 | 
			
		||||
		</td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes)}}<br/><span class="grey small">/分钟</span></span>
 | 
			
		||||
            <span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}<br/><span class="grey small">/分钟</span></span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive">{{node.status.load1m}}<br/><span class="grey small"></span></span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
		<td class="center">
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
<div class="ui tabular menu" v-if="isSearching">
 | 
			
		||||
    <a :href="'/clusters?searchType=cluster&keyword=' + keyword" class="item" :class="{active: searchType == '' || searchType == 'cluster'}">集群({{countClusters}})</a>
 | 
			
		||||
    <a :href="'/clusters?searchType=node&keyword=' + keyword" class="item" :class="{active: searchType == 'node'}">节点({{countNodes}})</a>
 | 
			
		||||
    <a :href="'/clusters/nodes?keyword=' + keyword" class="item" :class="{active: searchType == 'node'}">节点({{countNodes}})</a>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 集群 -->
 | 
			
		||||
@@ -81,91 +81,4 @@
 | 
			
		||||
    </table>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div v-show="searchType == 'node'">
 | 
			
		||||
    <p class="comment" v-if="nodes.length == 0">暂时还没有节点。</p>
 | 
			
		||||
    <table class="ui table selectable celled" v-if="nodes.length > 0">
 | 
			
		||||
        <thead>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <th>节点名称</th>
 | 
			
		||||
            <th>所属区域</th>
 | 
			
		||||
            <th>所属分组</th>
 | 
			
		||||
            <th>IP</th>
 | 
			
		||||
            <th class="width10">DNS线路</th>
 | 
			
		||||
            <th class="width5 center">CPU</th>
 | 
			
		||||
            <th class="width5 center">内存</th>
 | 
			
		||||
            <!--<th>流量</th>
 | 
			
		||||
            <th>连接数</th>-->
 | 
			
		||||
            <th class="two wide center">状态</th>
 | 
			
		||||
            <th class="two op">操作</th>
 | 
			
		||||
        </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tr v-for="node in nodes">
 | 
			
		||||
            <td><a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id"><keyword :v-word="keyword">{{node.name}}</keyword></a>
 | 
			
		||||
                <div style="margin-top: 0.5em">
 | 
			
		||||
                    <node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters" size="tiny"></node-clusters-labels>
 | 
			
		||||
                </div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <span v-if="node.region != null">{{node.region.name}}</span>
 | 
			
		||||
                <span v-else class="disabled">-</span>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <span v-if="node.group != null">{{node.group.name}}</span>
 | 
			
		||||
                <span v-else class="disabled">-</span>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <span v-if="node.ipAddresses.length == 0" class="disabled">-</span>
 | 
			
		||||
                <div v-else class="address-box">
 | 
			
		||||
                    <div v-for="addr in node.ipAddresses" style="margin-bottom:0.3em">
 | 
			
		||||
                        <div class="ui label tiny basic"><keyword :v-word="keyword">{{addr.ip}}</keyword>
 | 
			
		||||
                            <span class="small" v-if="addr.name.length > 0">({{addr.name}}<span v-if="!addr.canAccess">,不可访问</span>)</span>
 | 
			
		||||
                            <span class="small" v-if="addr.name.length == 0 && !addr.canAccess">(不可访问)</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <div v-if="node.dnsRouteNames.length > 0">
 | 
			
		||||
                    <div v-for="routeName in node.dnsRouteNames">
 | 
			
		||||
                        <tiny-basic-label>{{routeName}}</tiny-basic-label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <span v-else class="disabled">-</span>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="center">
 | 
			
		||||
                <span v-if="node.status.isActive" :class="{red:node.status.cpuUsage > 0.80}">{{node.status.cpuUsageText}}</span>
 | 
			
		||||
                <span v-else class="disabled">-</span>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="center">
 | 
			
		||||
                <span v-if="node.status.isActive" :class="{red:node.status.memUsage > 0.80}">{{node.status.memUsageText}}</span>
 | 
			
		||||
                <span v-else class="disabled">-</span>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="center">
 | 
			
		||||
                <div v-if="!node.isUp">
 | 
			
		||||
                    <span class="red">健康问题</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-else-if="!node.isOn">
 | 
			
		||||
                    <label-on :v-is-on="node.isOn"></label-on>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-else-if="node.isInstalled">
 | 
			
		||||
                    <div v-if="node.status.isActive">
 | 
			
		||||
                        <span v-if="!node.isSynced" class="red">同步中</span>
 | 
			
		||||
                        <span v-else class="green">运行中</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <span v-else-if="node.status.updatedAt > 0" class="red">已断开</span>
 | 
			
		||||
                    <span v-else-if="node.status.updatedAt == 0" class="red">未连接</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-else>
 | 
			
		||||
                    <span v-if="node.installStatus.isRunning" class="red">安装中</span>
 | 
			
		||||
                    <a v-if="node.installStatus.isFinished && !node.installStatus.isOk" :href="'/clusters/cluster/node/install?clusterId=' + node.cluster.id + '&nodeId=' + node.id" title="点击看安装错误"><span class="red">安装出错</span></a>
 | 
			
		||||
                    <a v-else class="red" :href="'/clusters/cluster/node/install?clusterId=' + node.cluster.id + '&nodeId=' + node.id" title="点击进安装界面"><span class="red">未安装</span></a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id">详情</a>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<page-box></page-box>
 | 
			
		||||
							
								
								
									
										153
									
								
								web/views/@default/clusters/nodes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								web/views/@default/clusters/nodes.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
{$layout}
 | 
			
		||||
{$template "menu"}
 | 
			
		||||
 | 
			
		||||
<div class="margin"></div>
 | 
			
		||||
 | 
			
		||||
<form class="ui form" action="/clusters/nodes">
 | 
			
		||||
    <div class="ui fields inline">
 | 
			
		||||
        <div class="ui field">
 | 
			
		||||
            <node-cluster-combo-box :v-cluster-id="clusterId"></node-cluster-combo-box>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field" v-if="groups.length > 0">
 | 
			
		||||
            <select class="ui dropdown" name="groupId" v-model="groupId">
 | 
			
		||||
                <option value="0">[全部分组]</option>
 | 
			
		||||
                <option v-for="group in groups" :value="group.id">{{group.name}}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field" v-if="regions.length > 0">
 | 
			
		||||
            <select class="ui dropdown" name="regionId" v-model="regionId">
 | 
			
		||||
                <option value="0">[全部区域]</option>
 | 
			
		||||
                <option v-for="region in regions" :value="region.id">{{region.name}}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field">
 | 
			
		||||
            <select class="ui dropdown" name="activeState" v-model="activeState">
 | 
			
		||||
                <option value="0">[在线状态]</option>
 | 
			
		||||
                <option value="1">在线</option>
 | 
			
		||||
                <option value="2">不在线</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field">
 | 
			
		||||
            <select class="ui dropdown" name="installedState" v-model="installState">
 | 
			
		||||
                <option value="0">[安装状态]</option>
 | 
			
		||||
                <option value="1">已安装</option>
 | 
			
		||||
                <option value="2">未安装</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field" v-if="teaIsPlus && levels.length > 0">
 | 
			
		||||
            <select class="ui dropdown" name="level" v-model="level">
 | 
			
		||||
                <option value="0">[级别]</option>
 | 
			
		||||
                <option v-for="levelInfo in levels" :value="levelInfo.code">{{levelInfo.name}}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field">
 | 
			
		||||
            <input type="text" name="keyword" placeholder="关键词" v-model="keyword" style="width:10em"/>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ui field">
 | 
			
		||||
            <button class="ui button" type="submit">搜索</button>  
 | 
			
		||||
            <a :href="'/clusters/nodes'" v-if="clusterId > 0 || regionId > 0 || groupId > 0 || installState > 0 || activeState > 0 || keyword.length > 0 || level > 0 || hasOrder">[清除条件]</a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
<p class="comment" v-if="nodes.length == 0">暂时还没有节点。</p>
 | 
			
		||||
 | 
			
		||||
<table class="ui table selectable celled" v-if="nodes.length > 0">
 | 
			
		||||
    <thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th>节点名称</th>
 | 
			
		||||
        <th>IP</th>
 | 
			
		||||
        <th class="width10">DNS线路</th>
 | 
			
		||||
        <th class="width5 center">CPU<sort-arrow name="cpuOrder"></sort-arrow></th>
 | 
			
		||||
        <th class="width5 center">内存<sort-arrow name="memoryOrder"></sort-arrow></th>
 | 
			
		||||
        <th class="center" style="width: 7em">下行流量<sort-arrow name="trafficOutOrder"></sort-arrow></th>
 | 
			
		||||
        <th class="center" style="width: 7em">负载<sort-arrow name="loadOrder"></sort-arrow></th>
 | 
			
		||||
        <th class="two wide center">状态</th>
 | 
			
		||||
        <th class="one op">操作</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tr v-for="node in nodes">
 | 
			
		||||
        <td><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue">  L{{node.level}}</span></sup></a>
 | 
			
		||||
            <div v-if="node.region != null">
 | 
			
		||||
                <grey-label>区域:{{node.region.name}}</grey-label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-if="node.group != null">
 | 
			
		||||
                <grey-label>分组:{{node.group.name}}</grey-label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-if="node.secondaryClusters != null && node.secondaryClusters.length > 0">
 | 
			
		||||
                <node-clusters-labels :v-primary-cluster="node.cluster" :v-secondary-clusters="node.secondaryClusters" size="tiny"></node-clusters-labels>
 | 
			
		||||
            </div>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <span v-if="node.ipAddresses.length == 0" class="disabled">-</span>
 | 
			
		||||
            <div v-else class="address-box">
 | 
			
		||||
                <div v-for="addr in node.ipAddresses" style="margin-bottom:0.3em">
 | 
			
		||||
                    <div class="ui label tiny basic">{{addr.ip}}
 | 
			
		||||
                        <span class="small" v-if="addr.name.length > 0">({{addr.name}}<span v-if="!addr.canAccess">,不可访问</span>)</span>
 | 
			
		||||
                        <span class="small" v-if="addr.name.length == 0 && !addr.canAccess">(不可访问)</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="routes-box" :class="{'show-link': node.dnsRouteNames.length == 0 &&  hasClusterDNS}">
 | 
			
		||||
            <div v-if="node.dnsRouteNames.length > 0">
 | 
			
		||||
                <div v-for="routeName in node.dnsRouteNames">
 | 
			
		||||
                    <tiny-basic-label>{{routeName}}</tiny-basic-label>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <a href="" @click.prevent="updateNodeDNS(node.id)" class="small">[修改]</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <span v-else-if="hasClusterDNS">
 | 
			
		||||
                <a href="" @click.prevent="updateNodeDNS(node.id)" class="small">[设置]</a>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive" :class="{red:node.status.cpuUsage > 0.80}">{{node.status.cpuUsageText}}</span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive" :class="{red:node.status.memUsage > 0.80}">{{node.status.memUsageText}}</span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}<br/><span class="grey small">/s</span></span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <span v-if="node.status.isActive">{{node.status.load1m}}<br/><span class="grey small"></span></span>
 | 
			
		||||
            <span v-else class="disabled">-</span>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="center">
 | 
			
		||||
            <div v-if="!node.isUp">
 | 
			
		||||
                <span class="red">健康问题下线</span>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <a href="" @click.prevent="upNode(node.id)">[上线]</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else-if="!node.isOn">
 | 
			
		||||
                <label-on :v-is-on="node.isOn"></label-on>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else-if="node.isInstalled">
 | 
			
		||||
                <div v-if="node.status.isActive">
 | 
			
		||||
                    <span v-if="!node.isSynced" class="red">同步中</span>
 | 
			
		||||
                    <span v-else class="green">运行中</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <span v-else-if="node.status.updatedAt > 0" class="red">已断开</span>
 | 
			
		||||
                <span v-else-if="node.status.updatedAt == 0" class="red">未连接</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else>
 | 
			
		||||
                <span v-if="node.installStatus.isRunning" class="red">安装中</span>
 | 
			
		||||
                <a v-if="node.installStatus.isFinished && !node.installStatus.isOk" :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" title="点击看安装错误"><span class="red">安装出错</span></a>
 | 
			
		||||
                <a v-else class="red" :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" title="点击进安装界面"><span class="red">未安装</span></a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">详情</a><!--   <a href="" @click.prevent="deleteNode(node.id)">删除</a>-->
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div class="page" v-html="page"></div>
 | 
			
		||||
							
								
								
									
										37
									
								
								web/views/@default/clusters/nodes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web/views/@default/clusters/nodes.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
	this.teaweb = teaweb
 | 
			
		||||
 | 
			
		||||
	this.deleteNode = function (nodeId) {
 | 
			
		||||
		teaweb.confirm("确定要删除这个节点吗?", function () {
 | 
			
		||||
			this.$post("/cluster/nodes/delete")
 | 
			
		||||
				.params({
 | 
			
		||||
					clusterId: this.clusterId,
 | 
			
		||||
					nodeId: nodeId
 | 
			
		||||
				})
 | 
			
		||||
				.refresh();
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.upNode = function (nodeId) {
 | 
			
		||||
		teaweb.confirm("确定要手动上线此节点吗?", function () {
 | 
			
		||||
			this.$post("/clusters/cluster/node/up")
 | 
			
		||||
				.params({
 | 
			
		||||
					nodeId: nodeId
 | 
			
		||||
				})
 | 
			
		||||
				.refresh()
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.updateNodeDNS = function (nodeId) {
 | 
			
		||||
		let that = this
 | 
			
		||||
		teaweb.popup("/clusters/cluster/node/updateDNSPopup?clusterId=" + this.clusterId + "&nodeId=" + nodeId, {
 | 
			
		||||
			width: "46em",
 | 
			
		||||
			height: "26em",
 | 
			
		||||
			callback: function () {
 | 
			
		||||
				teaweb.success("保存成功", function () {
 | 
			
		||||
					teaweb.reload()
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
@@ -5,17 +5,31 @@
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart {
 | 
			
		||||
  margin-left: 0.4em !important;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart .column {
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
  border-right: 1px rgba(0, 0, 0, 0.1) solid;
 | 
			
		||||
  border: 1px rgba(0, 0, 0, 0.1) solid;
 | 
			
		||||
  border-right: none;
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart .column.no-border {
 | 
			
		||||
  border-right: 0;
 | 
			
		||||
.grid.realtime-chart .column.with-border {
 | 
			
		||||
  border-right: 1px rgba(0, 0, 0, 0.1) solid;
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart .chart {
 | 
			
		||||
  height: 10em;
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart a {
 | 
			
		||||
  display: none;
 | 
			
		||||
  font-size: 0.85em;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 1em;
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart .column:hover {
 | 
			
		||||
  background: rgba(0, 0, 0, 0.03);
 | 
			
		||||
}
 | 
			
		||||
.grid.realtime-chart .column:hover a {
 | 
			
		||||
  display: inline;
 | 
			
		||||
}
 | 
			
		||||
.chart-box {
 | 
			
		||||
  height: 14em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"sources":["index_plus.less"],"names":[],"mappings":"AAAA,GAAG,QACF,EACC,MAAK;EACJ,kBAAA;EACA,UAAA;EACA,QAAA;;AAKH,KAAK;EACJ,kBAAA;;AADD,KAAK,eAGJ;EACC,kBAAA;EACA,0CAAA;;AALF,KAAK,eAQJ,QAAO;EACN,eAAA;;AATF,KAAK,eAYJ;EACC,YAAA;;AAIF;EACC,YAAA;;AAGD;EACC,YAAA;;AADD,gBAGC,IAAG;EACF,UAAA;;AAIF,EACC;EACC,gBAAA;EACA,WAAA;;AAIF;EACC,kBAAA;EACA,kBAAA;;AAFD,YAIC;EACC,kBAAA;EACA,QAAA;EACA,gBAAA;EACA,OAAA;EACA,QAAA;EACA,gBAAA;;AAVF,YAIC,QAQC;EACC,WAAA;;AAbH,YAIC,QAYC;EACC,gBAAA;;AAjBH,YAqBC;EACC,WAAA;EACA,iBAAA;;AAvBF,YAqBC,SAIC;EACC,gBAAA;EACA,mBAAA;;AA3BH,YAqBC,SASC;EACC,gBAAA","file":"index_plus.css"}
 | 
			
		||||
{"version":3,"sources":["index_plus.less"],"names":[],"mappings":"AAAA,GAAG,QACF,EACC,MAAK;EACJ,kBAAA;EACA,UAAA;EACA,QAAA;;AAKH,KAAK;EACJ,kBAAA;EACA,kBAAA;;AAFD,KAAK,eAIJ;EACC,kBAAA;EACA,oCAAA;EACA,kBAAA;;AAPF,KAAK,eAUJ,QAAO;EACN,0CAAA;;AAXF,KAAK,eAcJ;EACC,YAAA;;AAfF,KAAK,eAkBJ;EACC,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,UAAA;;AAtBF,KAAK,eAyBJ,QAAO;EACN,+BAAA;;AA1BF,KAAK,eAyBJ,QAAO,MAGN;EACC,eAAA;;AAKH;EACC,YAAA;;AAGD;EACC,YAAA;;AADD,gBAGC,IAAG;EACF,UAAA;;AAIF,EACC;EACC,gBAAA;EACA,WAAA;;AAIF;EACC,kBAAA;EACA,kBAAA;;AAFD,YAIC;EACC,kBAAA;EACA,QAAA;EACA,gBAAA;EACA,OAAA;EACA,QAAA;EACA,gBAAA;;AAVF,YAIC,QAQC;EACC,WAAA;;AAbH,YAIC,QAYC;EACC,gBAAA;;AAjBH,YAqBC;EACC,WAAA;EACA,iBAAA;;AAvBF,YAqBC,SAIC;EACC,gBAAA;EACA,mBAAA;;AA3BH,YAqBC,SASC;EACC,gBAAA","file":"index_plus.css"}
 | 
			
		||||
@@ -10,19 +10,36 @@
 | 
			
		||||
 | 
			
		||||
.grid.realtime-chart {
 | 
			
		||||
	margin-left: 0.4em !important;
 | 
			
		||||
	position: relative;
 | 
			
		||||
 | 
			
		||||
	.column {
 | 
			
		||||
		margin-bottom: 1em;
 | 
			
		||||
		border-right: 1px rgba(0, 0, 0, .1) solid;
 | 
			
		||||
		border: 1px rgba(0, 0, 0, .1) solid;
 | 
			
		||||
		border-right: none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.column.no-border {
 | 
			
		||||
		border-right: 0;
 | 
			
		||||
	.column.with-border {
 | 
			
		||||
		border-right: 1px rgba(0, 0, 0, .1) solid;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.chart {
 | 
			
		||||
		height: 10em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a {
 | 
			
		||||
		display: none;
 | 
			
		||||
		font-size: 0.85em;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		right: 1em;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.column:hover {
 | 
			
		||||
		background: rgba(0, 0, 0, .03);
 | 
			
		||||
 | 
			
		||||
		a {
 | 
			
		||||
			display: inline;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chart-box {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,30 +3,6 @@
 | 
			
		||||
  right: 1em;
 | 
			
		||||
  top: 2em;
 | 
			
		||||
}
 | 
			
		||||
.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: 14em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF,EACC;EACC,kBAAA;EACA,UAAA;EACA,QAAA;;AAKH;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"}
 | 
			
		||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF,EACC;EACC,kBAAA;EACA,UAAA;EACA,QAAA;;AAKH;EACC,YAAA;;AAGD,EACC;EACC,gBAAA;EACA,WAAA","file":"index.css"}
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- 统计图表 -->
 | 
			
		||||
<div class="ui three columns grid" v-if="!isLoading">
 | 
			
		||||
<div class="ui three columns grid counter-chart" v-if="!isLoading">
 | 
			
		||||
    <div class="ui column">
 | 
			
		||||
        <h4>集群<link-icon href="/clusters" v-if="dashboard.canGoNodes"></link-icon></h4>
 | 
			
		||||
        <div class="value"><span>{{dashboard.countNodeClusters}}</span>个</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,41 +8,6 @@
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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: 14em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user