优化default和serverstatus主题模版 (#327)

1.优化default主题模版
2.优化serverstatus主题模版
This commit is contained in:
nap0o
2024-02-25 10:16:57 -05:00
committed by GitHub
parent 07e0382598
commit 8df863a3e0
31 changed files with 1710 additions and 510 deletions

View File

@@ -0,0 +1,12 @@
{{define "theme-default/footer"}}
</div>
<div class="ui inverted vertical footer segment">
<div class="ui center aligned is-size-7 container">
<b>&copy; <a style="color: white;" href="/">{{.Conf.Site.Brand}}</a></b> | <small>Powered by <a
href="https://github.com/naiba/nezha" style="color: white;" target="_blank">{{tr "NezhaMonitoring"}}</a>
{{.Version}}</small>
</div>
</div>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,25 @@
{{define "theme-default/header"}}
<!DOCTYPE html>
<html lang="{{.Conf.Language}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta content="telephone=no" name="format-detection">
<title>{{.Title}}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-logos@0.17/assets/font-logos.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.0.0/css/flag-icons.min.css">
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
<link rel="stylesheet" type="text/css" href="/static/theme-default/css/main.css?v20240222">
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.js"></script>
<script src="/static/semantic-ui-alerts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script src="/static/theme-default/js/mixin.js?v20240225"></script>
</head>
<body>
{{end}}

View File

@@ -1,10 +1,10 @@
{{define "theme-default/home"}}
{{template "common/header" .}}
{{template "theme-default/header" .}}
{{if ts .CustomCode}} {{.CustomCode|safe}} {{end}}
{{template "common/menu" .}}
{{template "theme-default/menu" .}}
<div class="nb-container">
<div class="ui container">
<div id="app">
<div class="ui container">
<template v-if="groups">
<div class="ui styled fluid accordion" v-for="group in groups">
<div class="active title">
<i class="dropdown icon"></i>
@@ -15,12 +15,13 @@
<div v-for="server in group.data" :id="server.ID" class="ui card">
<div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
<div class="header">
<i :class="server.Host.CountryCode + ' flag'"></i>&nbsp;<i v-if='server.Host.Platform == "darwin"'
<i :class="'fi fi-' + server.Host.CountryCode"></i>&nbsp;<i v-if='server.Host.Platform == "darwin"'
class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)'
class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i>
@#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@
<i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
<div class="ui content popup" style="margin-bottom: 0">
<i @click="togglePopup($event, server.ID)" aria-expanded="false" class="nezha-secondary-font info circle icon" style="height: 28px"></i>
<div class="ui content popup" :class="{ 'visible': isActive(server.ID) }" style="margin-bottom: 0;">
<i class="closePopup window close icon" @click="closePopup(server.ID)"></i>
{{tr "Platform"}}: @#server.Host.Platform#@-@#server.Host.PlatformVersion#@
[<span
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
@@ -40,7 +41,8 @@
{{tr "ConnCount"}}: TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
{{tr "BootTime"}}: @# formatTimestamp(server.Host.BootTime) #@<br />
{{tr "LastActive"}}: @# new Date(server.LastActive).toLocaleString() #@<br />
{{tr "Version"}}: @#server.Host.Version#@<br />
{{tr "Version"}}: @#server.Host.Version#@
<div class="chartbox" :key="server.ID" :ref="`chart${server.ID}`" style="width: 100%; height: auto; margin-bottom: 2px;"></div>
</div>
<div class="ui divider" style="margin-bottom: 5px"></div>
</div>
@@ -72,13 +74,6 @@
</div>
</div>
</div>
<div class="three wide column">{{tr "NetSpeed"}}</div>
<div class="thirteen wide column">
<i class="arrow alternate circle down outline icon"></i>
@#formatByteSize(server.State.NetInSpeed)#@/s
<i class="arrow alternate circle up outline icon"></i>
@#formatByteSize(server.State.NetOutSpeed)#@/s
</div>
<div class="three wide column">{{tr "DiskUsed"}}</div>
<div class="thirteen wide column">
<div :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class">
@@ -88,6 +83,36 @@
</div>
</div>
</div>
<div class="three wide column">{{tr "NetSpeed"}}</div>
<div class="thirteen wide column">
<i class="arrow alternate circle down outline icon"></i>
@#formatByteSize(server.State.NetInSpeed)#@/s
<i class="arrow alternate circle up outline icon"></i>
@#formatByteSize(server.State.NetOutSpeed)#@/s
</div>
<div class="three wide column">流量</div>
<div class="thirteen wide column">
<i class="arrow circle down icon"></i>
@#formatByteSize(server.State.NetInTransfer)#@
&nbsp;
<i class="arrow circle up icon"></i>
@#formatByteSize(server.State.NetOutTransfer)#@
</div>
<div class="three wide column">信息</div>
<div class="thirteen wide column">
<i class="bi bi-cpu-fill" style="font-size: 1.1rem; color: #4a86e8;"></i> @#getCoreAndGHz(server.Host.CPU)#@
&nbsp;
<i class="bi bi-memory" style="font-size: 1.1rem; color: #00ac0d;"></i> @#getK2Gb(server.Host.MemTotal)#@
&nbsp;
<i class="bi bi-hdd" style="font-size: 1.1rem; color: #e41e10"></i> @#getK2Gb(server.Host.DiskTotal)#@
</div>
<div class="three wide column">{{tr "Load"}}</div>
<div class="thirteen wide column">
<i class="bi bi-activity" style="font-size: 1.1rem; color: #e41e10;"></i>
@# toFixed2(server.State.Load1) #@ |
@# toFixed2(server.State.Load5) #@ |
@# toFixed2(server.State.Load15) #@
</div>
<div class="three wide column">{{tr "Uptime"}}</div>
<div class="thirteen wide column">
<i class="clock icon"></i>@#secondToDate(server.State.Uptime)#@
@@ -103,30 +128,171 @@
</div>
</div>
</div>
</div>
</template>
</div>
</div>
{{template "common/footer" .}}
{{template "theme-default/footer" .}}
<script>
const initData = JSON.parse('{{.Servers}}').servers;
var statusCards = new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
data: initData,
page: 'index',
templates: {{.Themes}},
data: [],
groups: [],
cache: [],
chartDataList: [],
activePopup: null,
},
mixins: [mixinsVue],
created() {
this.data = JSON.parse('{{.Servers}}').servers;
this.group()
},
mounted() {
$('.nezha-secondary-font.info.icon').popup({
popup: '.ui.content.popup',
exclusive: true,
});
},
methods: {
togglePopup(event, id) {
// 切换弹出层的激活状态
this.activePopup = this.activePopup === id ? null : id;
this.showCharts(id);
},
isActive(id) {
// 检查弹出层是否处于激活状态
return this.activePopup === id;
},
closePopup(id) {
this.activePopup = null;
},
showCharts(id) {
// 发起数据请求
const url = `/api/v1/monitor/${id}`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.result) { // 数据请求成功,更新数据并渲染图表
this.chartDataList[id - 1] = data.result;
this.$nextTick(() => {
this.renderCharts(id);
});
} else {
console.log('this agent (id:'+ id + ') has no monitor.');
}
})
.catch(error => {
console.error('Error fetching data:', error);
});
},
renderCharts(id) {
if (!this.chartDataList[id - 1]) return;
const MaxTCPPingValue = {{.MaxTCPPingValue}} ? {{.MaxTCPPingValue}} : 300;
const isMobile = this.checkIsMobile();
const fontSize = isMobile ? 10 : 9;
const itemGap = isMobile ? 5 : 2;
const itemWidth = isMobile ? 20 : 15;
const gridLeft = 25;
const gridRight = 12;
const fontColor = "rgba(0, 0, 0, 0.68)";
const backgroundColor = '';
const borderColor = "#ffffff";
const chartData = this.chartDataList[id - 1];
const chartContainer = this.$refs[`chart${id}`][0];
const chart = echarts.init(chartContainer, null, {
renderer: 'canvas',
useDirtyRect: false,
width: 'auto',
height: 120,
});
const xAxisData = chartData[0].created_at.map(time => new Date(time).toLocaleString());
const seriesData = chartData.map(item => {
let loss = 0;
const data = item.avg_delay.map((avgDelay, index) => {
if (avgDelay > 0.9 * MaxTCPPingValue) {
loss += 1;
}
return [new Date(item.created_at[index]).toLocaleString(), avgDelay];
});
const lossRate = ((loss / item.created_at.length) * 100).toFixed(1);
item.monitor_name = item.monitor_name + " " + lossRate + "%";
return {
name: item.monitor_name,
type: 'line',
smooth: true,
symbol: 'none',
data: data
};
});
const option = {
backgroundColor: backgroundColor,
title: {
show: false
},
tooltip: {
show: true,
trigger: 'axis',
textStyle: {
fontSize: fontSize,
color: fontColor
}
},
legend: {
data: chartData.map(item => item.monitor_name),
show: true,
textStyle: {
fontSize: fontSize,
color: fontColor
},
lineStyle: {
cap: 'butt'
},
top: 0,
bottom: 0,
itemGap: itemGap,
itemWidth: itemWidth,
padding: [5,0,5,0]
},
xAxis: {
type: 'time',
data: xAxisData,
axisLabel: {
textStyle: {
fontSize: fontSize
}
}
},
yAxis: {
type: 'value',
axisLabel: {
textStyle: {
fontSize: fontSize
}
}
},
dataZoom: [
{
show: false,
type: 'slider',
start: 0,
end: 100
}
],
series: seriesData,
textStyle: {
fontSize: fontSize,
color: fontColor
},
grid: {
top: '40',
bottom: '20',
left: gridLeft,
right: gridRight
}
};
chart.setOption(option);
},
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
return window.innerWidth <= 768;
},
toFixed2(f) {
return f.toFixed(2)
},
@@ -227,7 +393,7 @@
secondToDate(s) {
var d = Math.floor(s / 3600 / 24);
if (d > 0) {
return d + ' {{tr "Day"}}'
return d + " {{tr "Day"}}"
}
var h = Math.floor(s / 3600 % 24);
var m = Math.floor(s / 60 % 60);
@@ -238,8 +404,48 @@
return new Date(t * 1000).toLocaleString()
},
formatByteSize(bs) {
const x = readableBytes(bs)
const x = this.readableBytes(bs)
return x != "NaN undefined" ? x : '0B'
},
readableBytes(bytes) {
if (!bytes) {
return '0B'
}
const i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
},
getCoreAndGHz(str){
if((str || []).hasOwnProperty(0) === false){
return '';
}
str = str[0];
let GHz = str.match(/(\d|\.)+GHz/g);
let Core = str.match(/(\d|\.)+ Physical/g);
GHz = GHz!==null?GHz.hasOwnProperty(0)===false?'':GHz[0]:''
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
if(Core === '?'){
let Core = str.match(/(\d|\.)+ Virtual/g);
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
return Core.replace('Virtual','Core')
}
return Core.replace('Physical','Core');
},
getK2Gb(bs){
bs = bs / 1024 /1024 /1024;
if(bs>=1){
return Math.ceil(bs.toFixed(2)) + 'GB';
}else{
bs = bs * 1024;
return Math.ceil(bs.toFixed(2)) + 'MB';
}
},
listTipsMouseenter(obj,strs,tipsNum=1){
this.layerIndex = layer.tips(strs, '#'+obj,{tips: [tipsNum, 'rgb(0 0 0 / 85%)'],time:0});
$('#'+obj).attr('layerIndex',this.layerIndex)
},
listTipsMouseleave(obj){
layer.close(this.layerIndex)
}
}
})

View File

@@ -0,0 +1,47 @@
{{define "theme-default/menu"}}
<div id="app">
<div class="ui large top fixed menu nb-menu" style="z-index:9999999;">
<div class="ui container">
<a class="item" href="/">
<img src="/static/logo.svg?v20210804">
</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="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>
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
</div>
</div>
</template>
<template v-else>
<a href="/service" class='item'><i class="rss icon"></i>{{tr "Services" }}</a>
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
</template>
<div class="item ui simple dropdown">
<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="(value, key) in templates" :key="key" @click="toggleTemplate(key)" class="item"><i class="th large icon"></i>@#value#@
<i class="check icon" v-if="preferredTemplate === key"></i>
</a>
</div>
</div>
{{if .Admin}}
<div class="item right item-right ui simple dropdown">
<div class="text">
<i class="user icon" style="margin-right:3px"></i>{{.Admin.Name}}
<i class="dropdown icon"></i>
</div>
<div class="menu">
<a class="item" href="/server"><i class="terminal icon"></i>{{tr "AdminPanel"}}</a>
<a class="item" @click="logOut({{.Admin.ID}})"><i class="logout icon"></i>{{tr "Logout"}}</a>
</div>
</div>
{{else}}
<a href="/login" class="item right item-right" style="padding-right:1.2rem"><i class="sign-in icon"></i>{{tr "Login"}}</a>
{{end}}
</div>
</div>
{{template "component/confirm" .}}
{{end}}

View File

@@ -1,10 +1,10 @@
{{define "theme-default/network"}}
{{template "common/header" .}}
{{template "theme-default/header" .}}
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
{{template "common/menu" .}}
<div class="nb-container" id="app">
{{template "theme-default/menu" .}}
<div class="nb-container">
<div class="ui container">
<div class="service-status">
<table class="ui celled table">
@@ -22,8 +22,7 @@
</div>
</div>
{{template "common/footer" .}}
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
{{template "theme-default/footer" .}}
<script>
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
@@ -36,6 +35,8 @@
el: '#app',
delimiters: ['@#', '#@'],
data: {
page: 'network',
templates: {{.Themes}},
servers: initData,
option: {
tooltip: {
@@ -93,6 +94,7 @@
},
chartOnOff: true,
},
mixins: [mixinsVue],
mounted() {
this.renderChart();
this.parseMonitorInfo(monitorInfo);

View File

@@ -1,9 +1,9 @@
{{define "theme-default/service"}}
{{template "common/header" .}}
{{template "theme-default/header" .}}
{{if ts .CustomCode}}
{{.CustomCode|safe}}
{{end}}
{{template "common/menu" .}}
{{template "theme-default/menu" .}}
<div class="nb-container">
<div class="ui container">
<div class="service-status">
@@ -76,10 +76,20 @@
{{end}}
</tbody>
</table>
{{end}}
</div>
</div>
</div>
{{template "common/footer" .}}
{{template "theme-default/footer" .}}
<script>
new Vue({
el: '#app',
delimiters: ['@#', '#@'],
data: {
page: 'service',
templates: {{.Themes}}
},
mixins: [mixinsVue]
})
</script>
{{end}}