mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
✨ feat: add server-status theme (#295)
* ✨ feat: add server-status theme
* add `ServerStatus` theme to README
---------
Co-authored-by: naiba <hi@nai.ba>
This commit is contained in:
parent
150612a1d9
commit
470fa69ad9
@ -4,7 +4,7 @@
|
||||
<br>
|
||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/naiba/nezha/dashboard.yml?branch=master&label=Dash%20v0.15.5&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?branch=v0.15.5&label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.15.0-brightgreen?style=for-the-badge&logo=linux">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/naiba/nezha/dashboard.yml?branch=master&label=Dash%20v0.15.6&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?branch=v0.15.6&label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.15.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>
|
||||
@ -25,8 +25,8 @@
|
||||
| Default Theme | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru |
|
||||
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| ![Default Theme](resource/template/theme-default/screenshot.png) | <img src="resource/template/theme-daynight/screenshot.png" width="3000px"/> | <img src="resource/template/theme-hotaru/screenshot.png" width="1500px" /> |
|
||||
| <div align="center"><b>Default modified <a href="https://www.google.com/search?q=%E5%93%AA%E5%90%92%E7%9B%91%E6%8E%A7%E7%BE%8E%E5%8C%96">[Guide]</a></b></div> | <div align="center"><b>Neko Mdui <a href="https://github.com/MikoyChinese">@MikoyChinese</a></b></div> | <div align="center"><b>AngelKanade <a href="https://github.com/adminsama">@adminsama</a></b></div> |
|
||||
| ![默认主题魔改](https://fastly.jsdelivr.net/gh/idarku/img@main/me/1631120192341.webp) | ![Neko Mdui](resource/template/theme-mdui/screenshot.png) | ![AngelKanade](resource/template/theme-angel-kanade/screenshot.png) |
|
||||
| <div align="center"><b>Neko Mdui <a href="https://github.com/MikoyChinese">@MikoyChinese</a></b></div> | <div align="center"><b>AngelKanade <a href="https://github.com/adminsama">@adminsama</a></b></div> |<div align="center"><b>ServerStatus <a href="https://github.com/unclezs">@unclezs</a></b></div> |
|
||||
| ![Neko Mdui](resource/template/theme-mdui/screenshot.png) | ![AngelKanade](resource/template/theme-angel-kanade/screenshot.png) | ![默认主题魔改](resource/template/theme-server-status/screenshot.png) |
|
||||
|
||||
## Supported Languages
|
||||
|
||||
|
@ -17,12 +17,13 @@ var Languages = map[string]string{
|
||||
}
|
||||
|
||||
var Themes = map[string]string{
|
||||
"default": "Default",
|
||||
"daynight": "JackieSung DayNight",
|
||||
"mdui": "Neko Mdui",
|
||||
"hotaru": "Hotaru",
|
||||
"angel-kanade": "AngelKanade",
|
||||
"custom": "Custom(local)",
|
||||
"default": "Default",
|
||||
"daynight": "JackieSung DayNight",
|
||||
"mdui": "Neko Mdui",
|
||||
"hotaru": "Hotaru",
|
||||
"angel-kanade": "AngelKanade",
|
||||
"server-status": "SeverStatus",
|
||||
"custom": "Custom(local)",
|
||||
}
|
||||
|
||||
var DashboardThemes = map[string]string{
|
||||
|
24
resource/l10n/en-US.toml
vendored
24
resource/l10n/en-US.toml
vendored
@ -388,6 +388,12 @@ other = "Disk"
|
||||
[MemUsed]
|
||||
other = "RAM"
|
||||
|
||||
[CpuUsed]
|
||||
other = "CPU"
|
||||
|
||||
[Virtualization]
|
||||
other = "Virtualization"
|
||||
|
||||
[SwapUsed]
|
||||
other = "Swap"
|
||||
|
||||
@ -475,6 +481,9 @@ other = "Light Mode"
|
||||
[DarkMode]
|
||||
other = "Dark Mode"
|
||||
|
||||
[SystemMode]
|
||||
other = "System Mode"
|
||||
|
||||
[GridLayout]
|
||||
other = "Grid Layout"
|
||||
|
||||
@ -597,3 +606,18 @@ other = "Info"
|
||||
|
||||
[HideForGuest]
|
||||
other = "No display to visitors"
|
||||
|
||||
[Feature]
|
||||
other = "Menu"
|
||||
|
||||
[SitePV]
|
||||
other = "Total page views"
|
||||
|
||||
[SitePVUnit]
|
||||
other = "Page views"
|
||||
|
||||
[SiteUV]
|
||||
other = "Total visitors"
|
||||
|
||||
[SiteUVUnit]
|
||||
other = "Visitors"
|
26
resource/l10n/es-ES.toml
vendored
26
resource/l10n/es-ES.toml
vendored
@ -388,6 +388,12 @@ other = "Disco"
|
||||
[MemUsed]
|
||||
other = "Memoria"
|
||||
|
||||
[CpuUsed]
|
||||
other = "CPU"
|
||||
|
||||
[Virtualization]
|
||||
other = "Virtualización"
|
||||
|
||||
[SwapUsed]
|
||||
other = "Swap"
|
||||
|
||||
@ -475,6 +481,9 @@ other = "Modo luminoso"
|
||||
[DarkMode]
|
||||
other = "Modo oscuro"
|
||||
|
||||
[SystemMode]
|
||||
other = "Modo del sistema"
|
||||
|
||||
[GridLayout]
|
||||
other = "Grid Layout"
|
||||
|
||||
@ -596,4 +605,19 @@ other = "Tema del panel de administración"
|
||||
other = "Información"
|
||||
|
||||
[HideForGuest]
|
||||
other = "No se muestra a los visitantes"
|
||||
other = "No se muestra a los visitantes"
|
||||
|
||||
[Feature]
|
||||
other = "Características"
|
||||
|
||||
[SitePV]
|
||||
other = "Número total de visitas al sitio"
|
||||
|
||||
[SitePVUnit]
|
||||
other = "visitas"
|
||||
|
||||
[SiteUV]
|
||||
other = "Número total de visitantes únicos al sitio"
|
||||
|
||||
[SiteUVUnit]
|
||||
other = "visitantes únicos"
|
26
resource/l10n/zh-CN.toml
vendored
26
resource/l10n/zh-CN.toml
vendored
@ -388,6 +388,12 @@ other = "硬盘"
|
||||
[MemUsed]
|
||||
other = "内存"
|
||||
|
||||
[CpuUsed]
|
||||
other = "核心"
|
||||
|
||||
[Virtualization]
|
||||
other = "虚拟化"
|
||||
|
||||
[SwapUsed]
|
||||
other = "交换"
|
||||
|
||||
@ -475,6 +481,9 @@ other = "白昼模式"
|
||||
[DarkMode]
|
||||
other = "暗黑模式"
|
||||
|
||||
[SystemMode]
|
||||
other = "跟随系统"
|
||||
|
||||
[GridLayout]
|
||||
other = "网格视图"
|
||||
|
||||
@ -596,4 +605,19 @@ other = "管理后台主题"
|
||||
other = "信息"
|
||||
|
||||
[HideForGuest]
|
||||
other = "对游客隐藏"
|
||||
other = "对游客隐藏"
|
||||
|
||||
[Feature]
|
||||
other = "功能"
|
||||
|
||||
[SitePV]
|
||||
other = "本站总访问量"
|
||||
|
||||
[SitePVUnit]
|
||||
other = "次"
|
||||
|
||||
[SiteUV]
|
||||
other = "本站总访客数"
|
||||
|
||||
[SiteUVUnit]
|
||||
other = "人次"
|
26
resource/l10n/zh-TW.toml
vendored
26
resource/l10n/zh-TW.toml
vendored
@ -388,6 +388,12 @@ other = "硬盤"
|
||||
[MemUsed]
|
||||
other = "內存"
|
||||
|
||||
[CpuUsed]
|
||||
other = "CPU"
|
||||
|
||||
[Virtualization]
|
||||
other = "虛擬化"
|
||||
|
||||
[SwapUsed]
|
||||
other = "交換"
|
||||
|
||||
@ -475,6 +481,9 @@ other = "白晝模式"
|
||||
[DarkMode]
|
||||
other = "暗黑模式"
|
||||
|
||||
[SystemMode]
|
||||
other = "系統模式"
|
||||
|
||||
[GridLayout]
|
||||
other = "網格視圖"
|
||||
|
||||
@ -596,4 +605,19 @@ other = "管理後臺主題"
|
||||
other = "訊息"
|
||||
|
||||
[HideForGuest]
|
||||
other = "對遊客隱藏"
|
||||
other = "對遊客隱藏"
|
||||
|
||||
[Feature]
|
||||
other = "功能"
|
||||
|
||||
[SitePV]
|
||||
other = "本站總訪問量"
|
||||
|
||||
[SitePVUnit]
|
||||
other = "次"
|
||||
|
||||
[SiteUV]
|
||||
other = "本站總訪問人數"
|
||||
|
||||
[SiteUVUnit]
|
||||
other = "人次"
|
||||
|
6
resource/static/theme-server-status/css/bootstrap-theme.min.css
vendored
Normal file
6
resource/static/theme-server-status/css/bootstrap-theme.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resource/static/theme-server-status/css/bootstrap-theme.min.css.map
vendored
Normal file
1
resource/static/theme-server-status/css/bootstrap-theme.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
resource/static/theme-server-status/css/bootstrap.min.css
vendored
Normal file
6
resource/static/theme-server-status/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
resource/static/theme-server-status/css/bootstrap.min.css.map
vendored
Normal file
1
resource/static/theme-server-status/css/bootstrap.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
132
resource/static/theme-server-status/css/dark.css
vendored
Normal file
132
resource/static/theme-server-status/css/dark.css
vendored
Normal file
File diff suppressed because one or more lines are too long
78
resource/static/theme-server-status/css/light.css
vendored
Normal file
78
resource/static/theme-server-status/css/light.css
vendored
Normal file
File diff suppressed because one or more lines are too long
252
resource/static/theme-server-status/css/main.css
vendored
Normal file
252
resource/static/theme-server-status/css/main.css
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
body {
|
||||
padding-top: 70px !important;
|
||||
padding-bottom: 30px !important;
|
||||
}
|
||||
|
||||
/* 导航部分 开始*/
|
||||
.navbar {
|
||||
min-height: 40px !important;
|
||||
}
|
||||
|
||||
.navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle {
|
||||
margin-top: 18px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.node-cell-expand {
|
||||
max-width: 420px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.node-cell-expand-label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.dropdown .dropdown-toggle {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.navbar-inverse, .nav.navbar-nav {
|
||||
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-brand {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 导航部分 结束 */
|
||||
|
||||
/* 正文部分 开始 */
|
||||
.content {
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
border-collapse: collapse;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
text-align: center !important;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.expandRow > td {
|
||||
padding: 0 !important;
|
||||
border-top: 0 !important;
|
||||
}
|
||||
|
||||
.node-cell.center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.node-cell.status .status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px
|
||||
}
|
||||
|
||||
.node-cell.status .status-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.node-cell.os .icon.windows {
|
||||
margin-right: 0 !important;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.node-cell.location .flag {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.node-cell.network {
|
||||
min-width: 110px;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
.node-cell.cpu, .node-cell.ram, .node-cell.hdd {
|
||||
min-width: 45px;
|
||||
max-width: 90px;
|
||||
}
|
||||
|
||||
/*正文结束*/
|
||||
|
||||
/* 服务页 正文*/
|
||||
.service-status .delay-today {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.service-status .delay-today > i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
background-color: grey;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
.service-day-status-icon {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 4px;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.service-status {
|
||||
|
||||
}
|
||||
|
||||
/* 服务页 正文结束 */
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.node-cell.os,
|
||||
.node-cell.location,
|
||||
.node-cell.uptime {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 720px) {
|
||||
body {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.node-cell.os,
|
||||
.node-cell.location,
|
||||
.node-cell.uptime {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.service-day-status-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
body {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.node-cell.type,
|
||||
.node-cell.location,
|
||||
.node-cell.uptime,
|
||||
.node-cell.traffic {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.service-day-status-icon {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 533px) {
|
||||
body {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.node-cell.os,
|
||||
.node-cell.location,
|
||||
.node-cell.uptime,
|
||||
.node-cell.traffic {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.service-day-status-icon {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 450px) {
|
||||
body {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.node-cell.type,
|
||||
.node-cell.location,
|
||||
.node-cell.uptime,
|
||||
.node-cell.traffic {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.node-cell.cpu,
|
||||
.node-cell.ram,
|
||||
.node-cell.hdd {
|
||||
min-width: 25px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.service-day-status-icon {
|
||||
}
|
||||
}
|
7
resource/static/theme-server-status/js/bootstrap.min.js
vendored
Normal file
7
resource/static/theme-server-status/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
326
resource/static/theme-server-status/js/html5shiv.js
vendored
Normal file
326
resource/static/theme-server-status/js/html5shiv.js
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
/**
|
||||
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||
*/
|
||||
;(function(window, document) {
|
||||
/*jshint evil:true */
|
||||
/** version */
|
||||
var version = '3.7.3';
|
||||
|
||||
/** Preset options */
|
||||
var options = window.html5 || {};
|
||||
|
||||
/** Used to skip problem elements */
|
||||
var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
|
||||
|
||||
/** Not all elements can be cloned in IE **/
|
||||
var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
|
||||
|
||||
/** Detect whether the browser supports default html5 styles */
|
||||
var supportsHtml5Styles;
|
||||
|
||||
/** Name of the expando, to work with multiple documents or to re-shiv one document */
|
||||
var expando = '_html5shiv';
|
||||
|
||||
/** The id for the the documents expando */
|
||||
var expanID = 0;
|
||||
|
||||
/** Cached data for each document */
|
||||
var expandoData = {};
|
||||
|
||||
/** Detect whether the browser supports unknown elements */
|
||||
var supportsUnknownElements;
|
||||
|
||||
(function() {
|
||||
try {
|
||||
var a = document.createElement('a');
|
||||
a.innerHTML = '<xyz></xyz>';
|
||||
//if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
|
||||
supportsHtml5Styles = ('hidden' in a);
|
||||
|
||||
supportsUnknownElements = a.childNodes.length == 1 || (function() {
|
||||
// assign a false positive if unable to shiv
|
||||
(document.createElement)('a');
|
||||
var frag = document.createDocumentFragment();
|
||||
return (
|
||||
typeof frag.cloneNode == 'undefined' ||
|
||||
typeof frag.createDocumentFragment == 'undefined' ||
|
||||
typeof frag.createElement == 'undefined'
|
||||
);
|
||||
}());
|
||||
} catch(e) {
|
||||
// assign a false positive if detection fails => unable to shiv
|
||||
supportsHtml5Styles = true;
|
||||
supportsUnknownElements = true;
|
||||
}
|
||||
|
||||
}());
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Creates a style sheet with the given CSS text and adds it to the document.
|
||||
* @private
|
||||
* @param {Document} ownerDocument The document.
|
||||
* @param {String} cssText The CSS text.
|
||||
* @returns {StyleSheet} The style element.
|
||||
*/
|
||||
function addStyleSheet(ownerDocument, cssText) {
|
||||
var p = ownerDocument.createElement('p'),
|
||||
parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
|
||||
|
||||
p.innerHTML = 'x<style>' + cssText + '</style>';
|
||||
return parent.insertBefore(p.lastChild, parent.firstChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of `html5.elements` as an array.
|
||||
* @private
|
||||
* @returns {Array} An array of shived element node names.
|
||||
*/
|
||||
function getElements() {
|
||||
var elements = html5.elements;
|
||||
return typeof elements == 'string' ? elements.split(' ') : elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the built-in list of html5 elements
|
||||
* @memberOf html5
|
||||
* @param {String|Array} newElements whitespace separated list or array of new element names to shiv
|
||||
* @param {Document} ownerDocument The context document.
|
||||
*/
|
||||
function addElements(newElements, ownerDocument) {
|
||||
var elements = html5.elements;
|
||||
if(typeof elements != 'string'){
|
||||
elements = elements.join(' ');
|
||||
}
|
||||
if(typeof newElements != 'string'){
|
||||
newElements = newElements.join(' ');
|
||||
}
|
||||
html5.elements = elements +' '+ newElements;
|
||||
shivDocument(ownerDocument);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data associated to the given document
|
||||
* @private
|
||||
* @param {Document} ownerDocument The document.
|
||||
* @returns {Object} An object of data.
|
||||
*/
|
||||
function getExpandoData(ownerDocument) {
|
||||
var data = expandoData[ownerDocument[expando]];
|
||||
if (!data) {
|
||||
data = {};
|
||||
expanID++;
|
||||
ownerDocument[expando] = expanID;
|
||||
expandoData[expanID] = data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a shived element for the given nodeName and document
|
||||
* @memberOf html5
|
||||
* @param {String} nodeName name of the element
|
||||
* @param {Document|DocumentFragment} ownerDocument The context document.
|
||||
* @returns {Object} The shived element.
|
||||
*/
|
||||
function createElement(nodeName, ownerDocument, data){
|
||||
if (!ownerDocument) {
|
||||
ownerDocument = document;
|
||||
}
|
||||
if(supportsUnknownElements){
|
||||
return ownerDocument.createElement(nodeName);
|
||||
}
|
||||
if (!data) {
|
||||
data = getExpandoData(ownerDocument);
|
||||
}
|
||||
var node;
|
||||
|
||||
if (data.cache[nodeName]) {
|
||||
node = data.cache[nodeName].cloneNode();
|
||||
} else if (saveClones.test(nodeName)) {
|
||||
node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
|
||||
} else {
|
||||
node = data.createElem(nodeName);
|
||||
}
|
||||
|
||||
// Avoid adding some elements to fragments in IE < 9 because
|
||||
// * Attributes like `name` or `type` cannot be set/changed once an element
|
||||
// is inserted into a document/fragment
|
||||
// * Link elements with `src` attributes that are inaccessible, as with
|
||||
// a 403 response, will cause the tab/window to crash
|
||||
// * Script elements appended to fragments will execute when their `src`
|
||||
// or `text` property is set
|
||||
return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a shived DocumentFragment for the given document
|
||||
* @memberOf html5
|
||||
* @param {Document} ownerDocument The context document.
|
||||
* @returns {Object} The shived DocumentFragment.
|
||||
*/
|
||||
function createDocumentFragment(ownerDocument, data){
|
||||
if (!ownerDocument) {
|
||||
ownerDocument = document;
|
||||
}
|
||||
if(supportsUnknownElements){
|
||||
return ownerDocument.createDocumentFragment();
|
||||
}
|
||||
data = data || getExpandoData(ownerDocument);
|
||||
var clone = data.frag.cloneNode(),
|
||||
i = 0,
|
||||
elems = getElements(),
|
||||
l = elems.length;
|
||||
for(;i<l;i++){
|
||||
clone.createElement(elems[i]);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
|
||||
* @private
|
||||
* @param {Document|DocumentFragment} ownerDocument The document.
|
||||
* @param {Object} data of the document.
|
||||
*/
|
||||
function shivMethods(ownerDocument, data) {
|
||||
if (!data.cache) {
|
||||
data.cache = {};
|
||||
data.createElem = ownerDocument.createElement;
|
||||
data.createFrag = ownerDocument.createDocumentFragment;
|
||||
data.frag = data.createFrag();
|
||||
}
|
||||
|
||||
|
||||
ownerDocument.createElement = function(nodeName) {
|
||||
//abort shiv
|
||||
if (!html5.shivMethods) {
|
||||
return data.createElem(nodeName);
|
||||
}
|
||||
return createElement(nodeName, ownerDocument, data);
|
||||
};
|
||||
|
||||
ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
|
||||
'var n=f.cloneNode(),c=n.createElement;' +
|
||||
'h.shivMethods&&(' +
|
||||
// unroll the `createElement` calls
|
||||
getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
|
||||
data.createElem(nodeName);
|
||||
data.frag.createElement(nodeName);
|
||||
return 'c("' + nodeName + '")';
|
||||
}) +
|
||||
');return n}'
|
||||
)(html5, data.frag);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Shivs the given document.
|
||||
* @memberOf html5
|
||||
* @param {Document} ownerDocument The document to shiv.
|
||||
* @returns {Document} The shived document.
|
||||
*/
|
||||
function shivDocument(ownerDocument) {
|
||||
if (!ownerDocument) {
|
||||
ownerDocument = document;
|
||||
}
|
||||
var data = getExpandoData(ownerDocument);
|
||||
|
||||
if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
|
||||
data.hasCSS = !!addStyleSheet(ownerDocument,
|
||||
// corrects block display not defined in IE6/7/8/9
|
||||
'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
|
||||
// adds styling not present in IE6/7/8/9
|
||||
'mark{background:#FF0;color:#000}' +
|
||||
// hides non-rendered elements
|
||||
'template{display:none}'
|
||||
);
|
||||
}
|
||||
if (!supportsUnknownElements) {
|
||||
shivMethods(ownerDocument, data);
|
||||
}
|
||||
return ownerDocument;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* The `html5` object is exposed so that more elements can be shived and
|
||||
* existing shiving can be detected on iframes.
|
||||
* @type Object
|
||||
* @example
|
||||
*
|
||||
* // options can be changed before the script is included
|
||||
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
|
||||
*/
|
||||
var html5 = {
|
||||
|
||||
/**
|
||||
* An array or space separated string of node names of the elements to shiv.
|
||||
* @memberOf html5
|
||||
* @type Array|String
|
||||
*/
|
||||
'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
|
||||
|
||||
/**
|
||||
* current version of html5shiv
|
||||
*/
|
||||
'version': version,
|
||||
|
||||
/**
|
||||
* A flag to indicate that the HTML5 style sheet should be inserted.
|
||||
* @memberOf html5
|
||||
* @type Boolean
|
||||
*/
|
||||
'shivCSS': (options.shivCSS !== false),
|
||||
|
||||
/**
|
||||
* Is equal to true if a browser supports creating unknown/HTML5 elements
|
||||
* @memberOf html5
|
||||
* @type boolean
|
||||
*/
|
||||
'supportsUnknownElements': supportsUnknownElements,
|
||||
|
||||
/**
|
||||
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
|
||||
* methods should be overwritten.
|
||||
* @memberOf html5
|
||||
* @type Boolean
|
||||
*/
|
||||
'shivMethods': (options.shivMethods !== false),
|
||||
|
||||
/**
|
||||
* A string to describe the type of `html5` object ("default" or "default print").
|
||||
* @memberOf html5
|
||||
* @type String
|
||||
*/
|
||||
'type': 'default',
|
||||
|
||||
// shivs the document according to the specified `html5` object options
|
||||
'shivDocument': shivDocument,
|
||||
|
||||
//creates a shived element
|
||||
createElement: createElement,
|
||||
|
||||
//creates a shived documentFragment
|
||||
createDocumentFragment: createDocumentFragment,
|
||||
|
||||
//extends list of elements
|
||||
addElements: addElements
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
// expose html5
|
||||
window.html5 = html5;
|
||||
|
||||
// shiv the document
|
||||
shivDocument(document);
|
||||
|
||||
if(typeof module == 'object' && module.exports){
|
||||
module.exports = html5;
|
||||
}
|
||||
|
||||
}(typeof window !== "undefined" ? window : this, document));
|
2
resource/static/theme-server-status/js/jquery.min.js
vendored
Normal file
2
resource/static/theme-server-status/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
49
resource/static/theme-server-status/js/mixin.js
vendored
Normal file
49
resource/static/theme-server-status/js/mixin.js
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
const mixinsVue = {
|
||||
data: {
|
||||
cache: [],
|
||||
theme: "light",
|
||||
isSystemTheme: false
|
||||
},
|
||||
created() {
|
||||
this.initTheme()
|
||||
},
|
||||
methods: {
|
||||
setTheme(title, store = false) {
|
||||
this.theme = title
|
||||
document.body.setAttribute("theme", title)
|
||||
if (store) {
|
||||
localStorage.setItem("theme", title)
|
||||
this.isSystemTheme = false
|
||||
}
|
||||
},
|
||||
setSystemTheme() {
|
||||
localStorage.removeItem("theme")
|
||||
this.initTheme()
|
||||
this.isSystemTheme = true
|
||||
},
|
||||
initTheme() {
|
||||
const storeTheme = localStorage.getItem("theme")
|
||||
if (storeTheme === 'dark' || storeTheme === 'light') {
|
||||
this.setTheme(storeTheme, true);
|
||||
} else {
|
||||
this.isSystemTheme = true
|
||||
const handleChange = (mediaQueryListEvent) => {
|
||||
if (localStorage.getItem("theme")) {
|
||||
return
|
||||
}
|
||||
if (mediaQueryListEvent.matches) {
|
||||
this.setTheme('dark');
|
||||
} else {
|
||||
this.setTheme('light');
|
||||
}
|
||||
}
|
||||
const mediaQueryListDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.setTheme(mediaQueryListDark.matches ? 'dark' : 'light');
|
||||
mediaQueryListDark.addEventListener("change", handleChange);
|
||||
}
|
||||
},
|
||||
toFixed2(f) {
|
||||
return f.toFixed(2)
|
||||
},
|
||||
}
|
||||
}
|
5
resource/static/theme-server-status/js/respond.min.js
vendored
Normal file
5
resource/static/theme-server-status/js/respond.min.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
|
||||
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
|
||||
* */
|
||||
|
||||
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
|
13
resource/template/theme-server-status/content-footer.html
vendored
Normal file
13
resource/template/theme-server-status/content-footer.html
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{{define "theme-server-status/content-footer"}}
|
||||
<div class="container">
|
||||
<p style="text-align: center; font-size: 10px;">
|
||||
{{ .Title }} | Theme <a href="https://github.com/cppla/ServerStatus">ServerStatus</a> | Powered by <a
|
||||
href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a>
|
||||
</p>
|
||||
<p style="text-align: center; font-size: 10px;">
|
||||
<span id="busuanzi_container_site_pv" style="display: none">
|
||||
{{tr "SitePV" }} <span id="busuanzi_value_site_pv"></span> {{tr "SitePVUnit" }} | {{tr "SiteUV" }} <span id="busuanzi_value_site_uv"></span> {{tr "SiteUVUnit" }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
42
resource/template/theme-server-status/content-nav.html
vendored
Normal file
42
resource/template/theme-server-status/content-nav.html
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
{{define "theme-server-status/content-nav"}}
|
||||
<div role="navigation" class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="#" class="navbar-brand">{{ .Title }}</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">{{tr "Feature" }}<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/">{{tr "Home" }}</a></li>
|
||||
<li><a href="/service">{{tr "Services" }}</a></li>
|
||||
{{if .Admin}}
|
||||
<li><a href="/server">{{tr "AdminPanel" }} ({{.Admin.Name}})</a></li>
|
||||
{{else}}
|
||||
<li><a href="/login">{{tr "Login" }}</a></li>
|
||||
{{end}}
|
||||
<li><a href="#" @click="setSystemTheme">{{tr "SystemMode" }}
|
||||
<span style="color: #fff" v-if="isSystemTheme"> ✔️</span></a>
|
||||
</li>
|
||||
<li><a href="#" @click="setTheme('dark', true)">{{tr "DarkMode" }}
|
||||
<span v-if="theme === 'dark' && !isSystemTheme"> ✔️</span></a>
|
||||
</li>
|
||||
<li><a href="#" @click="setTheme('light', true)">{{tr "LightMode" }}
|
||||
<span v-if="theme === 'light' && !isSystemTheme"> ✔️</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
4
resource/template/theme-server-status/footer.html
vendored
Normal file
4
resource/template/theme-server-status/footer.html
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{{define "theme-server-status/footer"}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
35
resource/template/theme-server-status/header.html
vendored
Normal file
35
resource/template/theme-server-status/header.html
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
{{define "theme-server-status/header"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{.Conf.Language}}">
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css">
|
||||
<link href="https://cdn.staticfile.org/font-logos/0.17/font-logos.min.css" type="text/css"
|
||||
rel="stylesheet"/>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.staticfile.org/semantic-ui/2.4.1/semantic.min.css">
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="/static/theme-server-status/js/html5shiv.js"></script>
|
||||
<script src="/static/theme-server-status/js/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
|
||||
<script src="/static/theme-server-status/js/jquery.min.js"></script>
|
||||
<script src="/static/theme-server-status/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
|
||||
<script src="/static/theme-server-status/js/mixin.js"></script>
|
||||
<!-- custom code 引入这段代码即可开启卜算子统计 -->
|
||||
<!--<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>-->
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
329
resource/template/theme-server-status/home.html
vendored
Normal file
329
resource/template/theme-server-status/home.html
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
{{define "theme-server-status/home"}}
|
||||
{{template "theme-server-status/header" .}}
|
||||
<div id="app">
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<div class="container content" style="max-width: 95vw">
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-cell status center">🍀 {{tr "Status"}}</th>
|
||||
<th class="node-cell name">🚀 {{tr "Name"}}</th>
|
||||
<th class="node-cell os">🗂 {{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">🌍 {{tr "Location"}}</th>
|
||||
<th class="node-cell uptime center">⏱️ {{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">📋{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">🚦 {{tr "NetSpeed"}}↓|↑</th>
|
||||
<th class="node-cell traffic center">📊 {{tr "NetTransfer"}}↓|↑</th>
|
||||
<th class="node-cell cpu center">🎯 {{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell ram center">⚡ {{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">💾 {{tr "DiskUsed"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="(node,index) in nodes">
|
||||
<tr :id="'r'+index" data-toggle="collapse" :data-target="'#rt'+index" class="accordion-toggle"
|
||||
:class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td class="node-cell status center">
|
||||
<div class="status-container">
|
||||
<div v-if="node.online" class="status-icon online"></div>
|
||||
<div v-else class="status-icon offline"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell name">@#node.name#@</td>
|
||||
<td class="node-cell os">
|
||||
<i v-if='node.os == "darwin"' class="apple icon"></i>
|
||||
<i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
|
||||
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
|
||||
@#node.os#@
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell location">
|
||||
<i :class="node.location + ' flag'"></i>
|
||||
<span>@#node.location#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
<td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td>
|
||||
<td class="node-cell cpu">
|
||||
<div class="progress">
|
||||
<div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell memory">
|
||||
<div class="progress">
|
||||
<div :style="node.memory.style" :class="node.memory.class">
|
||||
<small>@#node.memory.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell hdd">
|
||||
<div class="progress">
|
||||
<div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
<div class="accordian-body collapse" :id="'rt'+index">
|
||||
<div style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
|
||||
<div style="display: flex;align-items: flex-start;justify-content: center;flex-direction: column; width: 450px;max-width: 90vw">
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">系统:</span>
|
||||
@#node.host.Platform#@
|
||||
</span>
|
||||
<span class="node-cell-expand" v-if="node.host.CPU">
|
||||
<span class="node-cell-expand-label">CPU:</span>
|
||||
@#node.host.CPU.join(",")#@
|
||||
</span>
|
||||
<span class="node-cell-expand load">
|
||||
<span class="node-cell-expand-label">{{tr "Load"}}:</span>
|
||||
@#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ /@#toFixed2(node.state.Load15)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
|
||||
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "MemUsed"}}:</span>
|
||||
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%)
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
|
||||
<i class="arrow alternate circle down outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetInTransfer)#@
|
||||
<i class="arrow alternate circle up outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span>
|
||||
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@
|
||||
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
|
||||
@#formatTimestamp(node.host.BootTime)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
|
||||
@#new Date(node.lastActive).toLocaleString()#@
|
||||
</span>
|
||||
<span class="node-cell-expand" v-if="node.host.Virtualization">
|
||||
<span class="node-cell-expand-label">{{tr "Virtualization"}}:</span>
|
||||
@#node.host.Virtualization#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span>
|
||||
@#node.state.ProcessCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ConnCount"}}:</span>
|
||||
TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
</div>
|
||||
{{template "theme-server-status/content-footer" .}}
|
||||
</div>
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
nodes: [],
|
||||
},
|
||||
mixins: [mixinsVue],
|
||||
created() {
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
this.nodes = this.handleNodes(initData);
|
||||
this.initTheme()
|
||||
},
|
||||
mounted() {
|
||||
this.connect();
|
||||
},
|
||||
methods: {
|
||||
isWindowsPlatform(str) {
|
||||
return str.includes('Windows')
|
||||
},
|
||||
getFontLogoClass(str) {
|
||||
if (["almalinux",
|
||||
"alpine",
|
||||
"aosc",
|
||||
"apple",
|
||||
"archlinux",
|
||||
"archlabs",
|
||||
"artix",
|
||||
"budgie",
|
||||
"centos",
|
||||
"coreos",
|
||||
"debian",
|
||||
"deepin",
|
||||
"devuan",
|
||||
"docker",
|
||||
"elementary",
|
||||
"fedora",
|
||||
"ferris",
|
||||
"flathub",
|
||||
"freebsd",
|
||||
"gentoo",
|
||||
"gnu-guix",
|
||||
"illumos",
|
||||
"kali-linux",
|
||||
"linuxmint",
|
||||
"mageia",
|
||||
"mandriva",
|
||||
"manjaro",
|
||||
"nixos",
|
||||
"openbsd",
|
||||
"opensuse",
|
||||
"pop-os",
|
||||
"raspberry-pi",
|
||||
"redhat",
|
||||
"rocky-linux",
|
||||
"sabayon",
|
||||
"slackware",
|
||||
"snappy",
|
||||
"solus",
|
||||
"tux",
|
||||
"ubuntu",
|
||||
"void",
|
||||
"zorin"].indexOf(str)
|
||||
> -1) {
|
||||
return str;
|
||||
}
|
||||
if (['openwrt', 'linux', "immortalwrt"].indexOf(str) > -1) {
|
||||
return 'tux';
|
||||
}
|
||||
if (str == 'amazon') {
|
||||
return 'redhat';
|
||||
}
|
||||
if (str == 'arch') {
|
||||
return 'archlinux';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
secondToDate(s) {
|
||||
const d = Math.floor(s / 3600 / 24);
|
||||
if (d > 0) {
|
||||
return d + "天"
|
||||
}
|
||||
const h = Math.floor(s / 3600 % 24);
|
||||
const m = Math.floor(s / 60 % 60);
|
||||
const second = Math.floor(s % 60);
|
||||
return h + ":" + ("0" + m).slice(-2) + ":" + ("0" + second).slice(-2);
|
||||
},
|
||||
formatTimestamp(t) {
|
||||
return new Date(t * 1000).toLocaleString()
|
||||
},
|
||||
formatByteSize(bs) {
|
||||
const x = this.readableBytes(bs)
|
||||
return x !== "NaN undefined" ? x : '0B'
|
||||
},
|
||||
formatPercent(live, used, total) {
|
||||
const percent = live ? (this.toFixed2(used / total * 100) || 0) : 1
|
||||
return this.formatPercents(percent)
|
||||
},
|
||||
formatPercents(percent) {
|
||||
if (percent <= 0) {
|
||||
percent = 1;
|
||||
}
|
||||
if (!this.cache[percent]) {
|
||||
this.cache[percent] = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
style: `width: ${parseInt(percent)}%`,
|
||||
percent,
|
||||
}
|
||||
if (percent < 80) {
|
||||
this.cache[percent].class = 'progress-bar progress-bar-success'
|
||||
} else if (percent < 90) {
|
||||
this.cache[percent].class = 'progress-bar progress-bar-warning'
|
||||
} else {
|
||||
this.cache[percent].class = 'progress-bar progress-bar-danger'
|
||||
}
|
||||
}
|
||||
return this.cache[percent]
|
||||
},
|
||||
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];
|
||||
},
|
||||
connect() {
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws"
|
||||
const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
|
||||
ws.onopen = function () {
|
||||
console.log("Connection open ...")
|
||||
}
|
||||
ws.onmessage = (evt) => {
|
||||
let jsonData = evt.data
|
||||
const data = JSON.parse(jsonData)
|
||||
for (let i = 0; i < data.servers.length; i++) {
|
||||
const ns = data.servers[i];
|
||||
if (!ns.Host) {
|
||||
data.servers[i].live = false
|
||||
} else {
|
||||
const lastActive = new Date(ns.LastActive).getTime()
|
||||
data.servers[i].live = data.now - lastActive <= 10 * 1000;
|
||||
}
|
||||
}
|
||||
this.nodes = this.handleNodes(data.servers)
|
||||
}
|
||||
ws.onclose = () => {
|
||||
setTimeout(function () {
|
||||
this.connect()
|
||||
}, 5000);
|
||||
}
|
||||
ws.onerror = function () {
|
||||
ws.close()
|
||||
}
|
||||
},
|
||||
handleNodes(servers) {
|
||||
let nodes = []
|
||||
servers.forEach(server => {
|
||||
let platform = server.Host.Platform
|
||||
if (this.isWindowsPlatform(server.Host.Platform)) {
|
||||
platform = "windows"
|
||||
}else if (platform === "immortalwrt") {
|
||||
platform = "openwrt"
|
||||
}
|
||||
let node = {
|
||||
name: server.Name,
|
||||
os: platform,
|
||||
location: server.Host.CountryCode,
|
||||
uptime: this.secondToDate(server.State.Uptime),
|
||||
load: this.toFixed2(server.State.Load1),
|
||||
network: this.getNetworkSpeed(server.State.NetInSpeed, server.State.NetOutSpeed),
|
||||
traffic: this.formatByteSize(server.State.NetInTransfer) + ' | ' + this.formatByteSize(server.State.NetOutTransfer),
|
||||
cpu: this.formatPercents(this.toFixed2(server.State.CPU)),
|
||||
memory: this.formatPercent(server.live, server.State.MemUsed, server.Host.MemTotal),
|
||||
hdd: this.formatPercent(server.live, server.State.DiskUsed, server.Host.DiskTotal),
|
||||
online: server.live,
|
||||
state: server.State,
|
||||
host: server.Host,
|
||||
lastActive: server.LastActive,
|
||||
}
|
||||
nodes.push(node)
|
||||
})
|
||||
return nodes;
|
||||
},
|
||||
getNetworkSpeed(netInSpeed, netOutSpeed) {
|
||||
return this.formatByteSize(netInSpeed) + ' | ' + this.formatByteSize(netOutSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
{{template "theme-server-status/footer" .}}
|
||||
{{end}}
|
BIN
resource/template/theme-server-status/screenshot.png
vendored
Normal file
BIN
resource/template/theme-server-status/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
237
resource/template/theme-server-status/service.html
vendored
Normal file
237
resource/template/theme-server-status/service.html
vendored
Normal file
@ -0,0 +1,237 @@
|
||||
{{define "theme-server-status/service"}}
|
||||
{{template "theme-server-status/header" .}}
|
||||
<div id="app">
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<div class="container content" style="max-width: 95vw">
|
||||
<table class="table table-striped table-condensed service-status">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-cell" style="min-width: 60px">🍀 {{tr "Status"}}</th>
|
||||
<th class="node-cell" style="min-width: 60px">🚀 {{tr "Name"}}</th>
|
||||
<th class="node-cell center">🗂 {{tr "Details"}}</th>
|
||||
<th class="node-cell center" style="min-width: 80px">⚡️{{tr "AverageLatency"}}</th>
|
||||
<th class="node-cell center" style="min-width: 80px">⏱️ {{tr "30DaysOnline"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="service in services">
|
||||
<tr>
|
||||
<td style="text-align: left" class="node-cell">
|
||||
<div class="delay-today">
|
||||
<i class="delay-today" :class="service.health.className"></i>
|
||||
@#service.health.text#@
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell">@#service.name#@</td>
|
||||
<td class="node-cell center">
|
||||
<template v-for="(item,index) in service.dayDetail">
|
||||
<div class="service-day-status-icon" :class="item.className"
|
||||
:data-tooltip="item.text">
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.avgDelay#@</td>
|
||||
<td class="node-cell center">
|
||||
<div class="progress">
|
||||
<div :style="service.totalUpTime.style" :class="service.totalUpTime.className">
|
||||
<small>@#service.totalUpTime.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="ui container">
|
||||
<div class="service-status">
|
||||
{{if .CycleTransferStats}}
|
||||
|
||||
<h2 style="text-align: center;">{{tr "CycleTransferStats"}}</h2>
|
||||
|
||||
<table class="ui celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ui center aligned">ID</th>
|
||||
<th class="ui center aligned">{{tr "Rules"}}</th>
|
||||
<th class="ui center aligned">{{tr "Server"}}</th>
|
||||
<th class="ui center aligned">{{tr "From"}}</th>
|
||||
<th class="ui center aligned">{{tr "To"}}</th>
|
||||
<th class="ui center aligned">MAX</th>
|
||||
<th class="ui center aligned">MIN</th>
|
||||
<th class="ui center aligned">{{tr "NextCheck"}}</th>
|
||||
<th class="ui center aligned">{{tr "CurrentUsage"}}</th>
|
||||
<th class='ui center aligned' style='padding: 0px 31px 0px 31px;'>{{tr "Transleft"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $id, $stats := .CycleTransferStats}}
|
||||
{{range $innerId, $transfer := $stats.Transfer}}
|
||||
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
||||
<tr>
|
||||
<td class="ui center aligned">{{$id}}</td>
|
||||
<td class="ui center aligned">{{$stats.Name}}</td>
|
||||
<td class="ui center aligned">{{index $stats.ServerName $innerId}}</td>
|
||||
<td class="ui center aligned">{{$stats.From|tf}}</td>
|
||||
<td class="ui center aligned">{{$stats.To|tf}}</td>
|
||||
<td class="ui center aligned">{{$stats.Max|bf}}</td>
|
||||
<td class="ui center aligned">{{$stats.Min|bf}}</td>
|
||||
<td class="ui center aligned">{{(index $stats.NextUpdate $innerId)|sft}}</td>
|
||||
<td class="ui center aligned">{{$transfer|bf}}</td>
|
||||
<td class="ui center aligned" style="padding: 14px 0px 0px 0px; position: relative;">
|
||||
<div class="thirteen wide column">
|
||||
<div class="ui progress {{TransClassName $TransLeftPercent}}"
|
||||
style=" background: rgba(0,0,0,.1); background-color: rgba(0,0,0,.1)!important; height: 25px;">
|
||||
<div class="bar"
|
||||
style="transition-duration: 300ms; min-width: unset; background-color: rgb(10, 148, 242); width: {{$TransLeftPercent}}% !important;"></div>
|
||||
<small style="position: relative; top: -2em;">{{TransLeft $stats.Max $transfer}} /
|
||||
{{$TransLeftPercent}} %</small></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "theme-server-status/content-footer" .}}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
services: []
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
mixins: [mixinsVue],
|
||||
methods: {
|
||||
initData() {
|
||||
// @formatter:off
|
||||
const services = []
|
||||
{{range $service := .Services}}
|
||||
services.push({
|
||||
name: '{{$service.Monitor.Name}}',
|
||||
currentUp: parseInt('{{$service.CurrentUp}}'),
|
||||
currentDown: parseInt('{{$service.CurrentDown}}'),
|
||||
totalUp: parseInt('{{$service.TotalUp}}'),
|
||||
totalDown: parseInt('{{$service.TotalDown}}'),
|
||||
delay: '{{$service.Delay}}'.replaceAll("[","").replaceAll("]","").split(" "),
|
||||
up: '{{$service.Up}}'.replaceAll("[","").replaceAll("]","").split(" "),
|
||||
down: '{{$service.Down}}'.replaceAll("[","").replaceAll("]","").split(" "),
|
||||
})
|
||||
{{end}}
|
||||
// @formatter:on
|
||||
for (let i = 0; i < services.length; i++) {
|
||||
const service = services[i];
|
||||
service.avgDelay = parseInt(service.delay[service.delay.length - 1]) + "ms"
|
||||
service.health = this.getStateInfo(this.getPercent(service.currentUp, service.currentDown))
|
||||
service.dayDetail = this.getDayTails(service)
|
||||
service.totalUpTime = this.getProgressInfo(this.getPercent(service.totalUp, service.totalDown))
|
||||
}
|
||||
this.services = services
|
||||
},
|
||||
getPercent(up, down) {
|
||||
if (!up) {
|
||||
up = 0;
|
||||
}
|
||||
if (!down) {
|
||||
down = 0
|
||||
}
|
||||
const currentUp = parseInt(up)
|
||||
const currentDown = parseInt(down)
|
||||
const total = currentUp + currentDown
|
||||
if (total === 0) {
|
||||
if (currentUp > 0) {
|
||||
return 100
|
||||
}
|
||||
return 0
|
||||
} else if (currentUp === 0) {
|
||||
return 0.00001 / total * 100
|
||||
}
|
||||
return this.toFixed2(currentUp / total * 100)
|
||||
},
|
||||
getDayTails(service) {
|
||||
const result = []
|
||||
for (let i = 0; i < service.up.length; i++) {
|
||||
const up = service.up[i]
|
||||
const down = service.down[i]
|
||||
const delay = service.delay[i]
|
||||
let percent = this.getPercent(up, down)
|
||||
if (percent <= 0) {
|
||||
percent = 0;
|
||||
}
|
||||
let className = this.getStateInfo(percent).className
|
||||
let available = '{{tr "Availability"}}'
|
||||
let averageLatency = '{{tr "AverageLatency"}}'
|
||||
const text = `${this.beforeDay(service.up.length - i - 1)},${available}:${percent}%,${averageLatency}:${delay}ms`
|
||||
result.push({
|
||||
text, className
|
||||
})
|
||||
}
|
||||
return result
|
||||
},
|
||||
beforeDay(days) {
|
||||
const today = new Date();
|
||||
today.setDate(today.getDate() - days);
|
||||
// 获取月份和日期并格式化
|
||||
const month = (today.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = today.getDate().toString().padStart(2, '0');
|
||||
return `${month}-${day}`;
|
||||
},
|
||||
getStateInfo(percent) {
|
||||
if (percent < 0) {
|
||||
percent = 0;
|
||||
}
|
||||
const result = {
|
||||
className: "good",
|
||||
text: "",
|
||||
percent
|
||||
}
|
||||
if (percent === 0) {
|
||||
result.className = ""
|
||||
result.text = '{{tr "StatusNoData"}}'
|
||||
} else if (percent > 95) {
|
||||
result.className = "good"
|
||||
result.text = '{{tr "StatusGood"}}'
|
||||
} else if (percent > 80) {
|
||||
result.className = "warning"
|
||||
result.text = '{{tr "StatusLowAvailability"}}'
|
||||
} else {
|
||||
result.className = "danger"
|
||||
result.text = '{{tr "StatusDown"}}'
|
||||
}
|
||||
return result;
|
||||
},
|
||||
getProgressInfo(percent) {
|
||||
const result = this.getStateInfo(percent)
|
||||
result.style = `width: ${parseInt(percent)}%`;
|
||||
const className = result.className;
|
||||
if (className === "good") {
|
||||
result.className = 'progress-bar progress-bar-success'
|
||||
} else if (className === "waining") {
|
||||
result.className = 'progress-bar progress-bar-warning'
|
||||
} else if (className === "danger") {
|
||||
result.className = 'progress-bar progress-bar-danger'
|
||||
} else {
|
||||
result.className = ""
|
||||
result.style = "width: 100%"
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{{template "theme-server-status/footer" .}}
|
||||
{{end}}
|
25
resource/template/theme-server-status/viewpassword.html
vendored
Normal file
25
resource/template/theme-server-status/viewpassword.html
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{{define "theme-server-status/viewpassword"}}
|
||||
{{template "common/header" .}}
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
<div class="login nb-container">
|
||||
<div class="ui center aligned grid">
|
||||
<div class="column">
|
||||
<h2 class="ui image header">
|
||||
<img src="static/logo.svg?v20210804" class="image">
|
||||
<div class="content">
|
||||
{{tr "VerifyPassword"}}
|
||||
</div>
|
||||
</h2>
|
||||
<form action="/view-password" method="POST" class="ui form">
|
||||
<div class="field">
|
||||
<input type="password" name="Password">
|
||||
</div>
|
||||
<button class="ui nezha-primary-btn button" type="submit">{{tr "Confirm"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "common/footer" .}}
|
||||
{{end}}
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
)
|
||||
|
||||
var Version = "v0.15.5" // !!记得修改 README 中的 badge 版本!!
|
||||
var Version = "v0.15.6" // !!记得修改 README 中的 badge 版本!!
|
||||
|
||||
var (
|
||||
Conf *model.Config
|
||||
|
Loading…
Reference in New Issue
Block a user