2024-02-12 01:16:04 -05:00
|
|
|
{{define "theme-server-status/network"}}
|
|
|
|
{{template "theme-server-status/header" .}}
|
2024-08-09 09:49:45 -04:00
|
|
|
{{template "theme-server-status/menu" .}}
|
2024-08-10 11:19:52 -04:00
|
|
|
<div class="container-fluid content network-box">
|
|
|
|
<div class="network-box-header btn-group">
|
|
|
|
<div class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
|
|
<i class="bi bi-list-ul"></i> {{tr "NetworkSpiterList"}} <i class="bi bi-chevron-compact-down"></i>
|
|
|
|
</div>
|
|
|
|
<ul class="dropdown-menu">
|
|
|
|
<li class="input-group fixed-top">
|
|
|
|
<input type="text" id="dropdown-search" class="form-control" placeholder="Search...">
|
|
|
|
</li>
|
|
|
|
<li class="dropdown-item" v-for="server in servers" @click="showCharts(server.ID)">
|
|
|
|
<a><i :class="'fi fi-' + (server.Host.CountryCode || 'rb')"></i> @#server.Name#@ <i v-if="server.ID == currentServerId" class="check icon"></i></a>
|
2024-08-16 23:14:28 -04:00
|
|
|
</li>
|
2024-08-10 11:19:52 -04:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
<div class="chartTitle"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
|
|
|
|
<div id="chartbox" style="width:100%;height:auto;"></div>
|
2024-08-09 09:49:45 -04:00
|
|
|
</div>
|
|
|
|
{{template "theme-server-status/footer" .}}
|
2024-02-12 01:16:04 -05:00
|
|
|
<script>
|
|
|
|
new Vue({
|
|
|
|
el: '#app',
|
|
|
|
delimiters: ['@#', '#@'],
|
|
|
|
data: {
|
2024-02-25 10:16:57 -05:00
|
|
|
page: 'network',
|
2024-08-10 11:19:52 -04:00
|
|
|
defaultTemplate: "{{.Conf.Site.Theme}}",
|
|
|
|
templates: "{{.Themes }}",
|
|
|
|
servers: [],
|
|
|
|
chartDataList: [],
|
|
|
|
chartTitle: '',
|
|
|
|
chartCountryCode: '',
|
|
|
|
chart: null,
|
|
|
|
currentServerId: ''
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
|
|
|
mixins: [mixinsVue],
|
2024-08-10 11:19:52 -04:00
|
|
|
created() {
|
|
|
|
this.servers = JSON.parse('{{.Servers}}').servers;
|
|
|
|
this.showCharts(this.servers[0].ID);
|
|
|
|
},
|
2024-02-12 01:16:04 -05:00
|
|
|
mounted() {
|
2024-08-10 11:19:52 -04:00
|
|
|
this.initSearch();
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
|
|
|
methods: {
|
2024-08-10 11:19:52 -04:00
|
|
|
showCharts(id) {
|
|
|
|
// 发起数据请求
|
|
|
|
const url = `/api/v1/monitor/${id}`;
|
|
|
|
fetch(url)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
|
|
|
this.chartDataList[id] = data.result;
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.renderCharts(id);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
console.log('this server (id:'+ id + ') has no monitor.');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error('Error fetching data:', error);
|
|
|
|
});
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
2024-08-10 11:19:52 -04:00
|
|
|
renderCharts(id, reload = false) {
|
2024-08-16 23:14:28 -04:00
|
|
|
if(!this.chartDataList[id]) return;
|
|
|
|
if(this.chart) this.disposeCharts(this.chart);
|
2024-08-10 11:19:52 -04:00
|
|
|
this.currentServerId = id;
|
|
|
|
this.chartCountryCode = this.getServerCountryCode(id);
|
2024-08-16 23:14:28 -04:00
|
|
|
this.chartTitle = this.chartDataList[id][0].server_name;
|
2024-08-10 11:19:52 -04:00
|
|
|
const chartData = this.chartDataList[id];
|
|
|
|
const chartContainer = document.getElementById('chartbox');
|
|
|
|
// 定义图表参数值
|
|
|
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
|
|
|
const autoheight = this.isMobile ? (window.innerHeight - 200) : (window.innerHeight - 250);
|
|
|
|
const fontSize = this.isMobile ? 10 : 14;
|
|
|
|
const gridLeft = this.isMobile ? 30 : 36;
|
|
|
|
const gridRight = this.isMobile ? 12 : 20;
|
|
|
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
|
|
|
const legendTop = this.isMobile ? 5 : 5;
|
|
|
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
|
|
|
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
|
|
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
|
|
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
|
|
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
|
|
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
|
|
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
|
|
|
const splitLineColor = this.theme == "dark" ? "rgba(110, 112, 121, 0.95)" : "rgba(224, 230, 241, 0.95)";
|
|
|
|
const markPointLabelColor = this.theme == "dark" ? "#111111" : "#000000";
|
|
|
|
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
|
|
|
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
|
|
|
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
|
|
|
this.chart = echarts.init(chartContainer, '', { // init图表
|
|
|
|
renderer: 'canvas',
|
|
|
|
useDirtyRect: false,
|
|
|
|
width: 'auto',
|
|
|
|
height: autoheight,
|
|
|
|
});
|
|
|
|
// 获取图表数据
|
|
|
|
let legendData = [];
|
|
|
|
let seriesData = [];
|
|
|
|
chartData.forEach((item,key)=> {
|
2024-02-12 01:16:04 -05:00
|
|
|
let loss = 0;
|
2024-08-10 11:19:52 -04:00
|
|
|
let totalLossRate = 0;
|
|
|
|
let legendName = '';
|
|
|
|
let data = { main: [], markLine: []};
|
|
|
|
item.avg_delay.forEach((avgDelay, index) => {
|
|
|
|
const threshold = 0.9 * MaxTCPPingValue; // 定义阀值,用于判断是否丢包
|
|
|
|
// 定义丢包 1. avgDelay==0 2. avgDelay>=MaxTCPPingValue 3. avgDelay>=threshold
|
|
|
|
if(avgDelay == 0 || avgDelay >= MaxTCPPingValue){ //绝对丢包
|
2024-03-23 21:22:13 -04:00
|
|
|
loss += 1;
|
2024-08-10 11:19:52 -04:00
|
|
|
const lossrate = 100 * loss / (index + 1);
|
2024-08-16 23:14:28 -04:00
|
|
|
if(lossrate != 100) {
|
|
|
|
data['main'].push(
|
|
|
|
[item.created_at[index], MaxTCPPingValue, lossrate]
|
|
|
|
);
|
|
|
|
data['markLine'].push({
|
|
|
|
xAxis: item.created_at[index],
|
|
|
|
label: { show: false },
|
|
|
|
emphasis: { disabled: true },
|
|
|
|
lineStyle: { type: "solid" }
|
|
|
|
});
|
|
|
|
}
|
2024-08-10 11:19:52 -04:00
|
|
|
} else if (avgDelay >= threshold && avgDelay < MaxTCPPingValue){ // 相对丢包
|
|
|
|
loss += 1;
|
|
|
|
const lossrate = 100 * loss / (index + 1);
|
2024-08-16 23:14:28 -04:00
|
|
|
if(lossrate != 100) {
|
|
|
|
data['main'].push(
|
|
|
|
[item.created_at[index], avgDelay, lossrate]
|
|
|
|
);
|
|
|
|
data['markLine'].push({
|
|
|
|
xAxis: item.created_at[index],
|
|
|
|
label: { show: false },
|
|
|
|
emphasis: { disabled: true },
|
|
|
|
lineStyle: { type: "solid" }
|
|
|
|
});
|
|
|
|
}
|
2024-08-10 11:19:52 -04:00
|
|
|
} else { // 未丢包
|
|
|
|
const lossrate = 100 * loss / (index + 1);
|
|
|
|
data['main'].push(
|
|
|
|
[item.created_at[index], avgDelay, lossrate]
|
|
|
|
);
|
2024-03-23 21:22:13 -04:00
|
|
|
}
|
2024-08-10 11:19:52 -04:00
|
|
|
});
|
|
|
|
// 处理legendData
|
|
|
|
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
|
|
|
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
|
|
|
legendData.push(legendName);
|
|
|
|
// 处理seriesData
|
|
|
|
seriesData.push(
|
|
|
|
{
|
2024-02-12 01:16:04 -05:00
|
|
|
name: legendName,
|
|
|
|
type: 'line',
|
|
|
|
smooth: true,
|
|
|
|
symbol: 'none',
|
2024-08-10 11:19:52 -04:00
|
|
|
connectNulls: true,
|
2024-08-16 23:14:28 -04:00
|
|
|
legendHoverLink: false,
|
|
|
|
emphasis: {
|
|
|
|
disabled: true
|
|
|
|
},
|
2024-08-10 11:19:52 -04:00
|
|
|
lineStyle: {
|
|
|
|
width: lineStyleWidth
|
|
|
|
},
|
|
|
|
data: data['main'],
|
2024-03-24 07:21:32 -04:00
|
|
|
markLine: {
|
|
|
|
symbol: "none",
|
|
|
|
symbolSize :0,
|
2024-08-10 11:19:52 -04:00
|
|
|
data: data['markLine'],
|
|
|
|
itemStyle: {
|
|
|
|
opacity: markLineItemStyleOpacity
|
|
|
|
},
|
|
|
|
lineStyle:{
|
|
|
|
width: markLineLineStyleWidth
|
|
|
|
}
|
2024-03-24 07:21:32 -04:00
|
|
|
},
|
2024-02-25 10:16:57 -05:00
|
|
|
markPoint: {
|
|
|
|
data: [
|
2024-08-10 11:19:52 -04:00
|
|
|
{
|
|
|
|
name: 'Max',
|
|
|
|
type: 'max',
|
|
|
|
symbol: 'pin',
|
|
|
|
itemStyle: {
|
|
|
|
opacity: markPointItemStyleOpacity
|
|
|
|
},
|
|
|
|
symbolSize: 30,
|
|
|
|
label: {
|
|
|
|
fontSize: 8,
|
|
|
|
color: markPointLabelColor,
|
|
|
|
formatter: function (params) {
|
|
|
|
return Math.round(params.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Min',
|
|
|
|
type: 'min',
|
|
|
|
symbol: 'pin',
|
|
|
|
itemStyle: {
|
|
|
|
opacity: markPointItemStyleOpacity
|
|
|
|
},
|
|
|
|
symbolSize: 30,
|
|
|
|
label: {
|
|
|
|
fontSize: 8,
|
|
|
|
color: markPointLabelColor,
|
|
|
|
offset: [0, 8],
|
|
|
|
formatter: function (params) {
|
|
|
|
return Math.round(params.value);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
symbolRotate: 180
|
|
|
|
}
|
2024-02-25 10:16:57 -05:00
|
|
|
]
|
|
|
|
}
|
2024-08-10 11:19:52 -04:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
|
|
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
2024-08-16 23:14:28 -04:00
|
|
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
2024-08-10 11:19:52 -04:00
|
|
|
const height = autoheight + autoIncrement;
|
|
|
|
const gridTop = 40 + autoIncrement;
|
|
|
|
const legendIcon = this.isMobile ? 'rect' : "";
|
|
|
|
const itemWidth = this.isMobile ? 10 : 25;
|
|
|
|
const itemHeight = this.isMobile ? 10 : 14;
|
|
|
|
this.chart.resize({
|
|
|
|
width: 'auto',
|
|
|
|
height: height
|
|
|
|
});
|
|
|
|
// 设置图表配置项
|
|
|
|
const option = {
|
|
|
|
// 全局调色盘
|
|
|
|
color: this.colors,
|
|
|
|
// 背景颜色
|
|
|
|
backgroundColor: backgroundColor,
|
|
|
|
// 文字样式
|
|
|
|
textStyle: {
|
|
|
|
fontSize: fontSize,
|
|
|
|
color: fontColor
|
|
|
|
},
|
|
|
|
// 图表网格设置
|
|
|
|
grid: {
|
|
|
|
top: gridTop,
|
|
|
|
left: gridLeft,
|
|
|
|
right: gridRight
|
|
|
|
},
|
|
|
|
// 图表标题设置
|
|
|
|
title: {
|
|
|
|
show: false,
|
|
|
|
},
|
|
|
|
// 图表系列数据设置
|
|
|
|
series: seriesData.flat(),
|
|
|
|
// X轴设置
|
|
|
|
xAxis: {
|
|
|
|
type: 'time',
|
|
|
|
axisLabel: {
|
|
|
|
textStyle: {
|
|
|
|
fontSize: fontSize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Y轴设置
|
|
|
|
yAxis: {
|
|
|
|
type: 'value',
|
|
|
|
axisLabel: {
|
|
|
|
textStyle: {
|
|
|
|
fontSize: fontSize
|
|
|
|
}
|
|
|
|
},
|
|
|
|
splitLine: {
|
|
|
|
lineStyle: {
|
|
|
|
width: splitLineWidth,
|
|
|
|
color: splitLineColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// 图例设置
|
|
|
|
legend: {
|
|
|
|
data: legendData,
|
|
|
|
show: true,
|
|
|
|
icon: legendIcon,
|
|
|
|
textStyle: {
|
|
|
|
fontSize: fontSize,
|
|
|
|
color: fontColor
|
|
|
|
},
|
|
|
|
top: legendTop,
|
|
|
|
bottom: 0,
|
|
|
|
left: legendLeft,
|
|
|
|
padding: legendPadding,
|
|
|
|
itemWidth: itemWidth,
|
|
|
|
itemHeight: itemHeight,
|
|
|
|
},
|
|
|
|
// 提示框设置
|
|
|
|
tooltip: {
|
|
|
|
trigger: 'axis',
|
|
|
|
backgroundColor: tooltipBackgroundColor,
|
|
|
|
borderColor: tooltipBorderColor,
|
|
|
|
textStyle: {
|
|
|
|
fontSize: fontSize,
|
|
|
|
color: fontColor
|
|
|
|
},
|
|
|
|
formatter: function (params) {
|
|
|
|
let tooltipContent = '';
|
|
|
|
const formattedTime = new Date(params[0].value[0]).toLocaleString();
|
|
|
|
tooltipContent += `<span style="line-height:2em">${formattedTime}</span><br>`;
|
|
|
|
params.forEach(param => {
|
|
|
|
const formattedTime = new Date(param.value[0]).toLocaleString();
|
|
|
|
if (!param.seriesName.includes('stack')) {
|
|
|
|
const name = param.seriesName.replace(/\s\d+(\.\d+)?%$/, '');
|
|
|
|
tooltipContent += `<span style="line-height:2em">${param.marker} ${name} ${param.value[2].toFixed(1)}% ${param.value[1].toFixed(2)}</span><br>`;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return tooltipContent;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// 数据缩放设置
|
|
|
|
dataZoom: [
|
|
|
|
{
|
|
|
|
type: 'slider',
|
|
|
|
start: 0,
|
|
|
|
end: 100
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
// 设置图表的配置选项
|
|
|
|
this.chart.setOption(option);
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
2024-08-10 11:19:52 -04:00
|
|
|
reloadCharts() { // 重新加载所有图表
|
|
|
|
this.servers.forEach(node => {
|
|
|
|
const id = node.ID;
|
|
|
|
const chartData = this.chartDataList[id];
|
|
|
|
if (chartData) {
|
|
|
|
this.renderCharts(id,true);
|
|
|
|
}
|
|
|
|
});
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
2024-08-16 23:14:28 -04:00
|
|
|
disposeCharts(chart){
|
|
|
|
chart.dispose();
|
|
|
|
chart = null;
|
2024-02-12 01:16:04 -05:00
|
|
|
},
|
2024-08-10 11:19:52 -04:00
|
|
|
getServerCountryCode(id){
|
|
|
|
const result = this.servers.find(item => item.ID == id);
|
|
|
|
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
|
|
|
|
},
|
|
|
|
initSearch() {
|
|
|
|
$('#dropdown-search').on('keyup', function() {
|
|
|
|
var searchTerm = $(this).val().toLowerCase();
|
|
|
|
$('.dropdown-menu .dropdown-item').each(function() {
|
|
|
|
var text = $(this).text().toLowerCase();
|
|
|
|
if (text.indexOf(searchTerm) > -1) {
|
|
|
|
$(this).removeClass('hidden').addClass('visible'); // 显示元素
|
|
|
|
} else {
|
|
|
|
$(this).removeClass('visible').addClass('hidden'); // 隐藏元素
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-02-12 01:16:04 -05:00
|
|
|
});
|
|
|
|
</script>
|
|
|
|
{{end}}
|