Compare commits

..
6 Commits
Author SHA1 Message Date
nap0o f32f127dfc improve: status-server主题network页 (#422) 2024-09-11 21:50:40 +08:00
nap0o f78ba281fb Default主题fixbug (#421)
* default主题fixbug
1.补上误删的网页title
2.修复network页纵坐标数值显示不全
3.修复network,多个legend导致的遮挡主图表问题

* fix

* fix

* fix

* fix

* fix

* fix
2024-09-07 14:44:30 +08:00
github-actions[bot] 7f9f9a9129 update contributors[no ci] 2024-09-06 15:31:54 +00:00
nap0o b701efd9b5 server-status和default主题:feat & improve & fix (#420)
* feat & improve & fix
1. 增加WebAppManifest文件,实现把哪吒监控网页伪装成app放在移动端桌面
2. vps地图分布图增加数据异步加载loading效果
3. 修复echart图表纵坐标轴因数值过大显示不全bug

* 刷新CDN缓存

* fix footer

* 4.提升service页echarts图表相关体验
5.用设置基准1vh的方法解决footer页脚位置在移动端各种浏览器显示不一致的问题
6.修复section标签的使用位置

* 刷新CDN缓存
2024-09-06 23:31:38 +08:00
UUBulb 511f43784e fix dashboard install script (#419) 2024-09-03 08:54:07 +08:00
UUBulb 3f6112a919 fix: docker image (#418) 2024-09-02 23:19:43 +08:00
23 changed files with 388 additions and 191 deletions
+4
View File
@@ -1,8 +1,12 @@
FROM alpine AS certs
RUN apk update && apk add ca-certificates
FROM busybox:stable-musl
ARG TARGETOS
ARG TARGETARCH
COPY --from=certs /etc/ssl/certs /etc/ssl/certs
COPY ./script/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
+2 -2
View File
@@ -4,7 +4,7 @@
<br>
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
<br><br>
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.18.3-brightgreen?style=for-the-badge&logo=linux">
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard">&nbsp;<img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github">&nbsp;<img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge">&nbsp;<img src="https://img.shields.io/badge/Installer-v0.19.0-brightgreen?style=for-the-badge&logo=linux">
<br>
<br>
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
@@ -44,8 +44,8 @@ You can change the dashboard language in the settings page (`/setting`) after th
<a href="https://github.com/AkkiaS7" title="Akkia"><img src="https://avatars.githubusercontent.com/u/68485070?v=4" width="50;" alt="Akkia"/></a>
<a href="https://github.com/Erope" title="卖女孩的小火柴"><img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/></a>
<a href="https://github.com/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></a>
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
<a href="https://github.com/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></a>
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
<a href="https://github.com/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></a>
<a href="https://github.com/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></a>
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+36
View File
@@ -0,0 +1,36 @@
{
"name": "Nezha",
"short_name": "Nezha",
"icons": [
{
"src": "/static/manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"lang": "eu-US",
"dir": "auto"
}
+36
View File
@@ -0,0 +1,36 @@
{
"name": "哪吒监控",
"short_name": "哪吒监控",
"icons": [
{
"src": "/static/manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"lang": "zh-CN",
"dir": "auto"
}
+11
View File
@@ -13,7 +13,18 @@ const mixinsVue = {
this.isMobile = this.checkIsMobile();
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
},
mounted() {
this.initDropdown();
},
methods: {
initDropdown() {
if(this.isMobile) $('.ui.dropdown').dropdown({
action: 'hide',
on: 'click',
duration: 100,
direction: 'direction'
});
},
toggleTemplate(template) {
if( template != this.preferredTemplate){
this.preferredTemplate = template;
+7 -1
View File
@@ -381,6 +381,7 @@ td.ping-network-quality {
}
.network-box .chartTitle {
cursor: pointer;
text-align: center;
font-size: 18px;
margin: 18px 0px 15px 0px;
@@ -480,7 +481,12 @@ footer p{
padding-top:60px !important;
}
.nezha {
min-height: calc(74.5vh);
min-height: calc(100vh - 90px);
min-height: calc(var(--vh, 1vh) * 100 - 90px);
}
#chartbox {
min-height: calc(100vh - 170px);
min-height: calc(var(--vh, 1vh) * 100 - 170px);
}
.content {
padding: 0;
+6
View File
@@ -26,7 +26,9 @@ const mixinsVue = {
this.semiTransparent = this.initSemiTransparent();
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
this.setBenchmarkHeight();
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.setBenchmarkHeight());
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll);
@@ -152,6 +154,10 @@ const mixinsVue = {
},
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
return window.innerWidth <= 768;
},
setBenchmarkHeight() {
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
}
}
+14 -5
View File
@@ -3,12 +3,21 @@
<html lang="{{.Conf.Language}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Title }}</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#000000" />
{{ if eq .Conf.Language "zh-CN" }}
<link rel="manifest" href="/static/manifest-zh-CN.json?v20240905" />
{{ else }}
<link rel="manifest" href="/static/manifest-en-US.json?v20240905" />
{{ end }}
<link rel="apple-touch-startup-image" href="/static/logo.svg" />
<link rel="shortcut icon" type="image/png" href="/static/logo.svg">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
@@ -21,7 +30,7 @@
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
<script src="/static/theme-default/js/mixin.js?v20240911"></script>
<script>
document.documentElement.setAttribute('nz-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
</script>
+1 -1
View File
@@ -194,7 +194,7 @@
]
},
mixins: [mixinsVue],
created() {
created() {
this.servers = JSON.parse('{{.Servers}}').servers;
this.group()
},
+2 -2
View File
@@ -7,7 +7,7 @@
</a>
<a class='item' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
<template v-if="isMobile">
<div class="item ui simple dropdown">
<div class="item ui dropdown" :class="{ simple: !isMobile }">
<div class="text"><i class="bi bi-gear-wide-connected icon" style="margin-right:3px;"></i>{{tr "Feature" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
<div class="menu">
<a href="/service" class="item"><i class="rss icon"></i>{{tr "Services" }}</a>
@@ -20,7 +20,7 @@
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
</template>
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
<div class="item ui simple dropdown">
<div class="item ui dropdown" :class="{ simple: !isMobile }">
<div class="text"><i class="bi bi-incognito icon" style="margin-right:3px;"></i>{{tr "Template" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
<div class="menu">
<a v-for="(item, index) in adaptedTemplates" :key="index" @click="toggleTemplate(item.key)" class="item">
+22 -14
View File
@@ -18,7 +18,7 @@
</div>
</div>
<div class="ui container">
<div ref="chartDom" style="margin-top: 15px;height: 520px;overflow: hidden"></div>
<div ref="chartDom" style="margin-top: 15px;height: auto;overflow: hidden"></div>
</div>
</div>
@@ -36,7 +36,11 @@
defaultTemplate: {{.Conf.Site.Theme}},
templates: {{.Themes}},
servers: initData,
option: {
option: {}
},
mixins: [mixinsVue],
created() {
this.option = {
tooltip: {
trigger: 'axis',
position: function (pt) {
@@ -69,17 +73,9 @@
right: this.isMobile ? '8%' : '3.8%',
},
backgroundColor: '',
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
type: 'slider',
start: 0,
end: 100
}
@@ -93,10 +89,8 @@
boundaryGap: false
},
series: [],
},
chartOnOff: true,
}
},
mixins: [mixinsVue],
mounted() {
this.renderChart();
this.parseMonitorInfo(monitorInfo);
@@ -236,6 +230,20 @@
this.option.title.text = monitorInfo.result[0].server_name;
this.option.series = tSeries;
this.option.legend.data = tLegendData;
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 2;
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
const autoIncrement = Math.floor((tLegendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 28 : 34);
const height = 520 + autoIncrement;
const gridTop = 60 + autoIncrement;
this.option.grid = {
left: this.isMobile ? '8%' : '3.8%',
right: this.isMobile ? '8%' : '3.8%',
top: gridTop
};
this.myChart.resize({
width: 'auto',
height: height
});
this.myChart.clear();
this.myChart.setOption(this.option);
},
+1 -1
View File
@@ -1,5 +1,5 @@
{{define "theme-server-status/footer"}}
</div>
</section>
<footer class="container-fluid">
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
</footer>
+15 -5
View File
@@ -2,11 +2,21 @@
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">
<head>
<title>{{ .Title }}</title>
<title>{{.Title}}</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta content="telephone=no" name="format-detection">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#000000" />
{{if eq .Conf.Language "zh-CN"}}
<link rel="manifest" href="/static/manifest-zh-CN.json?v20240905" />
{{else}}
<link rel="manifest" href="/static/manifest-en-US.json?v20240905" />
{{end}}
<link rel="apple-touch-startup-image" href="/static/logo.svg" />
<link rel="shortcut icon" type="image/png" href="/static/logo.svg">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
@@ -16,10 +26,10 @@
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v202408016">
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240909">
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
<script src="/static/theme-server-status/js/mixin.js?v20240811"></script>
<script src="/static/theme-server-status/js/mixin.js?v20240907"></script>
</head>
<body>
<div id="app">
@@ -18,7 +18,7 @@
<tbody id="servers">
<template v-for="(node,index) in nodesNoTag">
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle" :class="index % 2 === 0 ? 'odd': 'even'"
aria-expanded="false" @click="showCharts($event, node.ID)">
aria-expanded="false" @click="showCharts(node.ID)">
<td class="node-cell status center">
<div class="status-container">
<div v-if="node.online" class="status-icon online"></div>
@@ -143,7 +143,7 @@
@#node.host.Version#@
</span>
<span class="node-echarts-expand">
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
<div class="chartbox" :id="`chart-${node.ID}`" chartbox-show="0" :key="node.ID" style="width: 100%; height: auto;"></div>
</span>
</div>
</div>
+2 -2
View File
@@ -21,7 +21,7 @@
<tbody id="servers">
<template v-for="(node,index) in group.data">
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
:class="index % 2 === 0 ? 'odd': 'even'" aria-expanded="false" @click="showCharts($event, node.ID)">
:class="index % 2 === 0 ? 'odd': 'even'" aria-expanded="false" @click="showCharts(node.ID)">
<td class="node-cell status center">
<div class="status-container">
<div v-if="node.online" class="status-icon online"></div>
@@ -146,7 +146,7 @@
@#node.host.Version#@
</span>
<span class="node-echarts-expand">
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
<div class="chartbox" :id="`chart-${node.ID}`" chartbox-show="0" :key="node.ID" style="width: 100%; height: auto;"></div>
</span>
</div>
</div>
+163 -95
View File
@@ -3,15 +3,15 @@
{{template "theme-server-status/menu" .}}
<!-- showGroup true -->
<template v-if="showGroup">
<section class="container-fluid table-responsive content" v-for="group in nodesTag">
<div class="container-fluid table-responsive content" v-for="group in nodesTag">
{{template "theme-server-status/home-group-true" .}}
</section>
</div>
</template>
<!-- showGroup false -->
<template v-else>
<section class="container-fluid table-responsive content">
<div class="container-fluid table-responsive content">
{{template "theme-server-status/home-group-false" .}}
</section>
</div>
</template>
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true" style="padding-left:0">
<div class="modal-dialog modal-lg modal-dialog-centered">
@@ -162,7 +162,10 @@
const tooltipBackgroundColor = this.theme == "dark" ? "#ffffff" : '#ffffff';
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
const fontSize = this.isMobile ? 10 : 12;
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
const showLoadingMaskColor = this.theme == "dark" ? 'rgba(28, 29, 38, 1)' : 'rgba(249, 249, 249, 1)';
const showLoadingTextColor = this.theme == "dark" ? 'rgba(241, 241, 241, 1)' : 'rgba(0, 0, 0, 1)';
const showLoadingColor = inRangeColor;
const chartContainer = document.getElementById('mapChart');
const mapChart = echarts.init(chartContainer, '', { // init图表
renderer: 'canvas',
@@ -170,6 +173,13 @@
width: width,
height: height,
});
mapChart.showLoading({
text: 'loading',
textColor: showLoadingTextColor,
color: showLoadingColor,
maskColor: showLoadingMaskColor,
zlevel: 2
});
fetch(this.staticUrl + '/maps/' + this.countryMapGeoFile)
.then(response => response.json())
.then(worldMap => {
@@ -224,7 +234,10 @@
}
]
};
mapChart.setOption(option);
setTimeout(() => {
mapChart.hideLoading();
mapChart.setOption(option);
}, 1000);
});
},
isWindowsPlatform(str) {
@@ -470,24 +483,27 @@
// 如果所有元素的 Temperature 都为 0,则返回一个默认值 0
return 0;
},
showCharts(event, id) {
const chartContainer = this.$refs[`chart${id}`][0];
const chartboxShow = chartContainer.getAttribute('chartbox-show');
chartContainer.setAttribute('chartbox-show', chartboxShow === '0' ? '1' : '0');
const isAriaExpandedFalse = event.currentTarget.getAttribute('aria-expanded') === 'false';
if (!isAriaExpandedFalse) return;
showCharts(id,changeChartboxShow=true) {
const chartContainer = document.getElementById(`chart-${id}`);
if(changeChartboxShow){
const chartboxShow = chartContainer.getAttribute('chartbox-show');
chartContainer.setAttribute('chartbox-show', chartboxShow === '0' ? '1' : '0');
const collapseContainer = document.getElementById(`r${id}`);
const isAriaExpandedFalse = collapseContainer.getAttribute('aria-expanded') === 'false';
if (!isAriaExpandedFalse) return;
}
// 发起数据请求
const url = `/api/v1/monitor/${id}`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.result) { // 数据请求成功,更新数据并渲染图表
this.chartDataList[id - 1] = data.result;
this.chartDataList[id] = data.result;
this.$nextTick(() => {
this.renderCharts(id);
});
} else {
console.log('this agent (id:'+ id + ') has no monitor.');
console.log('this server (id:'+ id + ') has no monitor.');
}
})
.catch(error => {
@@ -495,76 +511,116 @@
});
},
renderCharts(id, reload = false) {
if (!this.chartDataList[id - 1]) return;
const chartData = this.chartDataList[id - 1];
const chartContainer = this.$refs[`chart${id}`][0];
if (reload) { //点击切换亮色/暗色风格模式时,重新载入echarts图表的逻辑,
// 第一步,查找已经渲染出的图表容器,并销毁它
if (!this.chartDataList[id]) return;
const chartData = this.chartDataList[id];
const chartContainer = document.getElementById(`chart-${id}`);
if (reload) {
const existingChart = echarts.getInstanceByDom(chartContainer);
if (existingChart) existingChart.dispose();
// 第二步,如果图表容器处于不可见状态chartboxShow=0,不重新渲染出新的图表,
// 如果图表容器处于可见状态chartboxShow=1,重新渲染出新的图表
const chartboxShow = chartContainer.getAttribute('chartbox-show');
if ( chartboxShow === '0' ) return;
}
// 定义图表参数值
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
const fontSize = this.isMobile ? 10 : 14;
const gridLeft = this.isMobile ? 25 : 36;
const gridLeft = (MaxTCPPingValue > 500) ? (this.isMobile ? 36 : 42) : (this.isMobile ? 25 : 36);
const gridRight = this.isMobile ? 5 : 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 systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
const chartTheme = theme == "dark" ? "dark" : "default";
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
const chartTheme = this.theme == "dark" ? "dark" : "default";
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
const backgroundColor = 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 chart = echarts.init(chartContainer, chartTheme, {
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
const chart = echarts.init(chartContainer, chartTheme, { // init图表
renderer: 'canvas',
useDirtyRect: false,
width: 'auto',
height: 300,
height: 300
});
const xAxisData = chartData[0].created_at.map(time => new Date(time).toLocaleString());
const seriesData = chartData.map(item => {
let legendData = [];
let seriesData = [];
chartData.forEach((item,key)=> {
let loss = 0;
const data = item.avg_delay.map((avgDelay, index) => {
if(avgDelay > 0 && avgDelay < MaxTCPPingValue){
loss += avgDelay > 0.9 * MaxTCPPingValue ? 1 : 0;
return [item.created_at[index], avgDelay.toFixed(2)];
}else{
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]
);
}
});
const lossRate = ((loss / item.created_at.length) * 100).toFixed(1);
item.monitor_name = item.monitor_name.includes("%") ? item.monitor_name : `${item.monitor_name} ${lossRate}%`;
return {
name: item.monitor_name,
type: 'line',
smooth: true,
symbol: 'none',
data: data,
connectNulls: true,
legendHoverLink: false,
emphasis: {
disabled: true
},
lineStyle: {
width: lineStyleWidth
}
};
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
}
}
}
);
});
const legendData = chartData.map(item => item.monitor_name);
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 autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
const height = 300 + autoIncrement;
const gridTop = 40 + autoIncrement;
const legendIcon = this.isMobile ? 'rect' : "";
@@ -574,39 +630,24 @@
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
},
tooltip: {
trigger: 'axis',
backgroundColor: tooltipBackgroundColor,
borderColor: tooltipBorderColor,
textStyle: {
fontSize: fontSize,
color: fontColor
}
},
legend: {
data: legendData,
show: true,
icon: legendIcon,
textStyle: {
fontSize: fontSize,
color: fontColor
},
top: legendTop,
bottom: 0,
left: legendLeft,
padding: legendPadding,
itemWidth: itemWidth,
itemHeight: itemHeight,
show: false,
},
series: seriesData.flat(),
xAxis: {
type: 'time',
data: xAxisData,
axisLabel: {
textStyle: {
fontSize: fontSize
@@ -626,30 +667,57 @@
}
}
},
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
}
],
series: seriesData,
textStyle: {
fontSize: fontSize,
color: fontColor
},
grid: {
top: gridTop,
left: gridLeft,
right: gridRight
}
]
};
chart.setOption(option);
},
reloadCharts() { // 重新加载所有图表
reloadCharts() {
this.servers.forEach(node => {
const id = node.ID;
const chartData = this.chartDataList[id - 1];
const chartData = this.chartDataList[id];
if (chartData) {
this.renderCharts(id,true);
}
+1 -1
View File
@@ -64,6 +64,6 @@
</nav>
</div>
</header>
<div class="nezha">
<section class="nezha">
{{end}}
+49 -46
View File
@@ -15,7 +15,7 @@
</li>
</ul>
</div>
<div class="chartTitle"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</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" .}}
@@ -32,7 +32,8 @@
chartTitle: '',
chartCountryCode: '',
chart: null,
currentServerId: ''
currentServerId: '',
nextServerId: '',
},
mixins: [mixinsVue],
created() {
@@ -66,37 +67,47 @@
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 - 200) : (window.innerHeight - 250);
const autoheight = this.isMobile ? (window.innerHeight - 180) : (window.innerHeight - 250);
const fontSize = this.isMobile ? 10 : 14;
const gridLeft = this.isMobile ? 30 : 36;
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 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 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;
this.chart = echarts.init(chartContainer, '', { // init图表
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)=> {
@@ -111,9 +122,6 @@
loss += 1;
const lossrate = 100 * loss / (index + 1);
if(lossrate != 100) {
data['main'].push(
[item.created_at[index], MaxTCPPingValue, lossrate]
);
data['markLine'].push({
xAxis: item.created_at[index],
label: { show: false },
@@ -142,11 +150,9 @@
);
}
});
// 处理legendData
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
legendName = `${item.monitor_name} ${totalLossRate}%`;
legendData.push(legendName);
// 处理seriesData
seriesData.push(
{
name: legendName,
@@ -182,10 +188,9 @@
itemStyle: {
opacity: markPointItemStyleOpacity
},
symbolSize: 30,
symbolSize: markPointSymbolSize,
label: {
fontSize: 8,
color: markPointLabelColor,
fontSize: markPointFontSize,
formatter: function (params) {
return Math.round(params.value);
}
@@ -198,10 +203,9 @@
itemStyle: {
opacity: markPointItemStyleOpacity
},
symbolSize: 30,
symbolSize: markPointSymbolSize,
label: {
fontSize: 8,
color: markPointLabelColor,
fontSize: markPointFontSize,
offset: [0, 8],
formatter: function (params) {
return Math.round(params.value);
@@ -218,7 +222,8 @@
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 = 40 + 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;
@@ -228,28 +233,22 @@
});
// 设置图表配置项
const option = {
// 全局调色盘
color: this.colors,
// 背景颜色
backgroundColor: backgroundColor,
// 文字样式
textStyle: {
fontSize: fontSize,
color: fontColor
},
// 图表网格设置
grid: {
top: gridTop,
left: gridLeft,
right: gridRight
right: gridRight,
bottom: gridBottom
},
// 图表标题设置
title: {
show: false,
},
// 图表系列数据设置
series: seriesData.flat(),
// X轴设置
xAxis: {
type: 'time',
axisLabel: {
@@ -258,7 +257,6 @@
}
}
},
// Y轴设置
yAxis: {
type: 'value',
axisLabel: {
@@ -268,12 +266,10 @@
},
splitLine: {
lineStyle: {
width: splitLineWidth,
color: splitLineColor
width: splitLineWidth
}
}
},
// 图例设置
legend: {
data: legendData,
show: true,
@@ -289,7 +285,6 @@
itemWidth: itemWidth,
itemHeight: itemHeight,
},
// 提示框设置
tooltip: {
trigger: 'axis',
backgroundColor: tooltipBackgroundColor,
@@ -312,7 +307,6 @@
return tooltipContent;
}
},
// 数据缩放设置
dataZoom: [
{
type: 'slider',
@@ -321,17 +315,16 @@
}
]
};
// 设置图表的配置选项
this.chart.setOption(option);
setTimeout(() => {
this.chart.hideLoading();
this.chart.setOption(option);
}, 1000);
},
reloadCharts() { // 重新加载所有图表
this.servers.forEach(node => {
const id = node.ID;
const chartData = this.chartDataList[id];
if (chartData) {
this.renderCharts(id,true);
}
});
reloadCharts() {
const chartData = this.chartDataList[this.currentServerId];
if (chartData) {
this.renderCharts(this.currentServerId,true);
}
},
disposeCharts(chart){
chart.dispose();
@@ -341,6 +334,16 @@
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();
+10 -10
View File
@@ -3,24 +3,24 @@
{{template "theme-server-status/menu" .}}
<!-- showGroup true -->
<template v-if="showGroup">
<section v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
<div v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
</section>
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
</div>
<div v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
{{template "theme-server-status/service-group-true" .}}
</section>
</div>
</template>
<!-- showGroup false -->
<template v-else>
<section v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
<div v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
</section>
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
</div>
<div v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
{{template "theme-server-status/service-group-false" .}}
</section>
</div>
</template>
{{if .CycleTransferStats}}
<section class="container-fluid content table-responsive">
<div class="container-fluid content table-responsive">
<table class="table table-striped table-condensed table-hover">
<thead>
<tr class="node-group-tag">
@@ -67,7 +67,7 @@
{{end}}
</tbody>
</table>
</section>
</div>
{{end}}
{{template "theme-server-status/footer" .}}
<script>
+2 -2
View File
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.18.3"
NZ_VERSION="v0.19.0"
red='\033[0;31m'
green='\033[0;32m'
@@ -604,7 +604,7 @@ restart_and_update_standalone() {
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
fi
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq -o $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm $NZ_DASHBOARD_PATH/app.zip
if [ "$os_alpine" != 1 ]; then
sudo systemctl enable nezha-dashboard
+2 -2
View File
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
NZ_VERSION="v0.18.3"
NZ_VERSION="v0.19.0"
red='\033[0;31m'
green='\033[0;32m'
@@ -598,7 +598,7 @@ restart_and_update_standalone() {
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/$version/dashboard-linux-$os_arch.zip"
fi
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dist/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm -r $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_PATH/dist
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip $NZ_DASHBOARD_URL >/dev/null 2>&1 && sudo unzip -qq -o $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm $NZ_DASHBOARD_PATH/app.zip
if [ "$os_alpine" != 1 ]; then
sudo systemctl enable nezha-dashboard