nezha/resource/template/theme-server-status/network.html

364 lines
18 KiB
HTML
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "theme-server-status/network"}}
{{template "theme-server-status/header" .}}
{{template "theme-server-status/menu" .}}
<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>
</li>
</ul>
</div>
<div class="chartTitle" @click="showCharts(nextServerId)"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
<div id="chartbox" style="width:100%;height:auto;"></div>
</div>
{{template "theme-server-status/footer" .}}
<script>
new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
page: 'network',
defaultTemplate: "{{.Conf.Site.Theme}}",
templates: "{{.Themes }}",
servers: [],
chartDataList: [],
chartTitle: '',
chartCountryCode: '',
chart: null,
currentServerId: '',
nextServerId: '',
},
mixins: [mixinsVue],
created() {
this.servers = JSON.parse('{{.Servers}}').servers;
this.showCharts(this.servers[0].ID);
},
mounted() {
this.initSearch();
},
methods: {
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);
});
},
renderCharts(id, reload = false) {
if(!this.chartDataList[id]) return;
if(this.chart) this.disposeCharts(this.chart);
this.currentServerId = id;
this.nextServerId = this.getNextServerId(id);
this.chartCountryCode = this.getServerCountryCode(id);
this.chartTitle = this.chartDataList[id][0].server_name;
const chartData = this.chartDataList[id];
const chartContainer = document.getElementById('chartbox');
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
const autoheight = this.isMobile ? (window.innerHeight - 180) : (window.innerHeight - 250);
const fontSize = this.isMobile ? 10 : 14;
const gridLeft = (MaxTCPPingValue > 500) ? (this.isMobile ? 36 : 42) : (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 chartTheme = this.theme == "dark" ? "dark" : "default";
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 markPointSymbolSize = this.isMobile ? 36 : 42;
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 1;
const markPointFontSize = this.isMobile ? 8 : 10;
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
const showLoadingMaskColor = this.theme == "dark" ? 'rgba(0, 0, 0, 0)' : 'rgba(255, 255, 255, 0)';
const showLoadingTextColor = this.theme == "dark" ? 'rgba(241, 241, 241, 1)' : 'rgba(0, 0, 0, 1)';
const showLoadingColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
this.chart = echarts.init(chartContainer, chartTheme, { // init图表
renderer: 'canvas',
useDirtyRect: false,
width: 'auto',
height: autoheight,
});
this.chart.showLoading({
text: 'loading',
textColor: showLoadingTextColor,
color: showLoadingColor,
maskColor: showLoadingMaskColor,
zlevel: 2
});
let legendData = [];
let seriesData = [];
chartData.forEach((item,key)=> {
let loss = 0;
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){ //绝对丢包
loss += 1;
const lossrate = 100 * loss / (index + 1);
if(lossrate != 100) {
data['markLine'].push({
xAxis: item.created_at[index],
label: { show: false },
emphasis: { disabled: true },
lineStyle: { type: "solid" }
});
}
} else if (avgDelay >= threshold && avgDelay < MaxTCPPingValue){ // 相对丢包
loss += 1;
const lossrate = 100 * loss / (index + 1);
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" }
});
}
} else { // 未丢包
const lossrate = 100 * loss / (index + 1);
data['main'].push(
[item.created_at[index], avgDelay, lossrate]
);
}
});
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
legendName = `${item.monitor_name} ${totalLossRate}%`;
legendData.push(legendName);
seriesData.push(
{
name: legendName,
type: 'line',
smooth: true,
symbol: 'none',
connectNulls: true,
legendHoverLink: false,
emphasis: {
disabled: true
},
lineStyle: {
width: lineStyleWidth
},
data: data['main'],
markLine: {
symbol: "none",
symbolSize :0,
data: data['markLine'],
itemStyle: {
opacity: markLineItemStyleOpacity
},
lineStyle:{
width: markLineLineStyleWidth
}
},
markPoint: {
data: [
{
name: 'Max',
type: 'max',
symbol: 'pin',
itemStyle: {
opacity: markPointItemStyleOpacity
},
symbolSize: markPointSymbolSize,
label: {
fontSize: markPointFontSize,
formatter: function (params) {
return Math.round(params.value);
}
}
},
{
name: 'Min',
type: 'min',
symbol: 'pin',
itemStyle: {
opacity: markPointItemStyleOpacity
},
symbolSize: markPointSymbolSize,
label: {
fontSize: markPointFontSize,
offset: [0, 8],
formatter: function (params) {
return Math.round(params.value);
}
},
symbolRotate: 180
}
]
}
}
);
});
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
const height = autoheight + autoIncrement;
const gridTop = this.isMobile ? ( 60 + autoIncrement) : (80 + autoIncrement);
const gridBottom = this.isMobile ? 70 : 90;
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,
bottom: gridBottom
},
title: {
show: false,
},
series: seriesData.flat(),
xAxis: {
type: 'time',
axisLabel: {
textStyle: {
fontSize: fontSize
}
}
},
yAxis: {
type: 'value',
axisLabel: {
textStyle: {
fontSize: fontSize
}
},
splitLine: {
lineStyle: {
width: splitLineWidth
}
}
},
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
}
]
};
setTimeout(() => {
this.chart.hideLoading();
this.chart.setOption(option);
}, 1000);
},
reloadCharts() {
const chartData = this.chartDataList[this.currentServerId];
if (chartData) {
this.renderCharts(this.currentServerId,true);
}
},
disposeCharts(chart){
chart.dispose();
chart = null;
},
getServerCountryCode(id){
const result = this.servers.find(item => item.ID == id);
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
},
getNextServerId(id) {
const currentIndex = this.servers.findIndex(item => item.ID === id);
if (currentIndex === -1) {
return this.servers[0].ID;
}
// 判断是否有下一个元素
const nextIndex = currentIndex + 1;
// 如果有下一个元素,返回下一个元素的 ID否则返回第一个元素的 ID
return nextIndex < this.servers.length ? this.servers[nextIndex].ID : this.servers[0].ID;
},
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'); // 隐藏元素
}
});
});
}
}
});
</script>
{{end}}