实现节点看板(仅对企业版开放)

This commit is contained in:
GoEdgeLab
2021-07-06 20:06:20 +08:00
parent b86453408c
commit 4599fc2709
34 changed files with 1313 additions and 296 deletions

View File

@@ -47,13 +47,13 @@
<div class="ui divider"></div>
<!-- 域名排行 -->
<h4>域名排行 <span>24小时</span></h4>
<h4>域名访问排行 <span>24小时</span></h4>
<div class="chart-box" id="top-domains-chart"></div>
<div class="ui divider"></div>
<!-- 节点排行 -->
<h4>节点排行 <span>24小时</span></h4>
<h4>节点访问排行 <span>24小时</span></h4>
<div class="chart-box" id="top-nodes-chart"></div>
<div class="ui divider"></div>
@@ -66,4 +66,10 @@
<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>
<div class="chart-box" id="load-chart" v-show="nodeStatusTab == 'load'"></div>
<!-- 指标 -->
<div class="ui divider" v-if="metricCharts.length > 0"></div>
<metric-board>
<metric-chart v-for="chart in metricCharts" :key="chart.id" :v-chart="chart.chart" :v-stats="chart.stats" :v-period="chart.item.period" :v-period-unit="chart.item.periodUnit" :v-value-type="chart.item.valueType"></metric-chart>
</metric-board>

View File

@@ -7,7 +7,7 @@ Tea.context(function () {
this.$delay(function () {
this.reloadHourlyTrafficChart()
this.reloadHourlyRequestsChart()
this.reloadTopNodeStatsChart()
this.reloadTopNodesChart()
this.reloadTopDomainsChart()
this.reloadCPUChart()
})
@@ -265,7 +265,7 @@ Tea.context(function () {
}
// 节点排行
this.reloadTopNodeStatsChart = function () {
this.reloadTopNodesChart = function () {
let that = this
let axis = teaweb.countAxis(this.topNodeStats, function (v) {
return v.countRequests
@@ -283,7 +283,10 @@ Tea.context(function () {
value: function (v) {
return v.countRequests / axis.divider;
},
axis: axis
axis: axis,
click: function (args, stats) {
window.location = "/clusters/cluster/node?nodeId=" + stats[args.dataIndex].nodeId + "&clusterId=" + that.clusterId
}
})
}
@@ -332,7 +335,7 @@ Tea.context(function () {
}
this.reloadCPUChart = function () {
let axis = {unit: "", divider: 1}
let axis = {unit: "%", divider: 1}
teaweb.renderLineChart({
id: "cpu-chart",
name: "CPU",
@@ -352,7 +355,7 @@ Tea.context(function () {
}
this.reloadMemoryChart = function () {
let axis = {unit: "", divider: 1}
let axis = {unit: "%", divider: 1}
teaweb.renderLineChart({
id: "memory-chart",
name: "内存",

View File

@@ -1,10 +1,12 @@
<second-menu>
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId">节点列表</menu-item>
<span class="item">|</span>
<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/thresholds?clusterId=' + clusterId + '&nodeId=' + nodeId" code="threshold" v-if="teaIsPlus">阈值设置</menu-item>
<menu-item :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + nodeId" code="log">运行日志</menu-item>
<menu-item :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + nodeId" code="update">修改设置</menu-item>
<menu-item :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + nodeId" code="install">安装节点</menu-item>
<menu-item :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id" code="node"
v-if="!teaIsPlus">"{{node.name}}"节点详情</menu-item>
<menu-item :href="'/clusters/cluster/node/boards?clusterId=' + clusterId + '&nodeId=' + node.id" code="board" v-if="teaIsPlus">"{{node.name}}" 节点看板</menu-item>
<menu-item :href="'/clusters/cluster/node/detail?clusterId=' + clusterId + '&nodeId=' + node.id" code="node" v-if="teaIsPlus">节点详情</menu-item>
<menu-item :href="'/clusters/cluster/node/thresholds?clusterId=' + clusterId + '&nodeId=' + node.id" code="threshold" v-if="teaIsPlus">阈值设置</menu-item>
<menu-item :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + node.id" code="log">运行日志</menu-item>
<menu-item :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + node.id" code="update">修改设置</menu-item>
<menu-item :href="'/clusters/cluster/node/install?clusterId=' + clusterId + '&nodeId=' + node.id" code="install">安装节点</menu-item>
</second-menu>

View 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 */

View 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"}

View File

@@ -0,0 +1,109 @@
{$layout}
{$template "../node_menu"}
{$template "/echarts"}
<!-- 概况 -->
<div class="ui four columns grid">
<div class="ui column">
<h4>在线状态</h4>
<div class="value">
<span class="green" v-if="board.isActive">在线</span>
<span class="red" v-else>离线</span>
</div>
</div>
<div class="ui column">
<h4>下行流量</h4>
<div class="value"><span>{{board.trafficOutBytes}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>上行流量</h4>
<div class="value"><span>{{board.trafficInBytes}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>连接数</h4>
<div class="value"><span>{{board.countConnections}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>当前访问量</h4>
<div class="value"><span>{{board.countRequests}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>当前攻击访问量</h4>
<div class="value"><span :class="{red: board.countAttackRequests != '0'}">{{board.countAttackRequests}}</span>/分钟</div>
</div>
<div class="ui column">
<h4>磁盘缓存用量</h4>
<div class="value"><span>{{board.cacheDiskSize}}</span></div>
</div>
<div class="ui column">
<h4>内存缓存用量</h4>
<div class="value"><span>{{board.cacheMemorySize}}</span></div>
</div>
<div class="ui column">
<h4>CPU</h4>
<div class="value"><span :class="{red: board.cpuUsage > 80}">{{board.cpuUsage}}</span>%</div>
</div>
<div class="ui column">
<h4>内存</h4>
<div class="value"><span :class="{red: board.memoryUsage > 80}">{{board.memoryUsage}}</span>%</div>
</div>
<div class="ui column">
<h4>总内存</h4>
<div class="value"><span>{{board.memoryTotalSize}}</span>G</div>
</div>
<div class="ui column">
<h4>负载</h4>
<div class="value"><span :class="{red: board.load > 20}">{{board.load}}</span>/分钟</div>
</div>
</div>
<div class="ui divider"></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>
<!-- CPU、内存、负载 -->
<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>
<!-- 指标 -->
<div class="ui divider" v-if="metricCharts.length > 0"></div>
<metric-board>
<metric-chart v-for="chart in metricCharts" :key="chart.id" :v-chart="chart.chart" :v-stats="chart.stats" :v-period="chart.item.period" :v-period-unit="chart.item.periodUnit" :v-value-type="chart.item.valueType"></metric-chart>
</metric-board>

View File

@@ -0,0 +1,403 @@
Tea.context(function () {
this.formatCount = function (count) {
if (count < 1000) {
return count.toString()
}
if (count < 1000 * 1000) {
return (Math.round(count / 1000 * 100) / 100) + "K"
}
return (Math.round(count / 1000 / 1000 * 100) / 100) + "M"
}
this.board.trafficInBytes = teaweb.formatBytes(this.board.trafficInBytes)
this.board.trafficOutBytes = teaweb.formatBytes(this.board.trafficOutBytes)
this.board.countConnections = this.formatCount(this.board.countConnections)
this.board.countRequests = this.formatCount(this.board.countRequests)
this.board.countAttackRequests = this.formatCount(this.board.countAttackRequests)
this.board.cpuUsage = Math.round(this.board.cpuUsage * 100 * 100) / 100
this.board.memoryUsage = Math.round(this.board.memoryUsage * 100 * 100) / 100
this.board.memoryTotalSize = Math.round(this.board.memoryTotalSize / 1024 / 1024 / 1024)
this.board.load = Math.round(this.board.load * 100) / 100
this.board.cacheDiskSize = teaweb.formatBytes(this.board.cacheDiskSize)
this.board.cacheMemorySize = teaweb.formatBytes(this.board.cacheMemorySize)
/**
* 流量统计
*/
this.trafficTab = "hourly"
this.$delay(function () {
this.reloadHourlyTrafficChart()
this.reloadHourlyRequestsChart()
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.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
})
}
})

View 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;
}
}

View File

@@ -1,4 +1,4 @@
a.underline {
border-bottom: 1px #db2828 dashed;
}
/*# sourceMappingURL=index.css.map */
/*# sourceMappingURL=detail.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["detail.less"],"names":[],"mappings":"AAAA,CAAC;EACA,iCAAA","file":"detail.css"}

View File

@@ -1 +0,0 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,CAAC;EACA,iCAAA","file":"index.css"}