mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 20:58:14 -05:00
feat: status-server主题增加agent账单信息展示 (#424)
1. 首页通过在后台配置PublicNote字段,实现agent账单信息展示 2. 优化顶部导航,增加当前位置标识 3. 优化network页移动端样式 4. 优化后台server页展示,隐藏掉一些不重要的信息 5. 一些其他小优化 使用说明:https://github.com/naiba/nezha/pull/424#issuecomment-2386837658
This commit is contained in:
parent
7940e010e0
commit
60ada33ee9
15
resource/l10n/en-US.toml
vendored
15
resource/l10n/en-US.toml
vendored
@ -690,3 +690,18 @@ other = "Cancel"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "Remaining"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "Lifetime"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "Price"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "Expired"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "d"
|
||||||
|
15
resource/l10n/es-ES.toml
vendored
15
resource/l10n/es-ES.toml
vendored
@ -690,3 +690,18 @@ other = "Cancelar"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "Remaining"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "Lifetime"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "Price"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "Expired"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "d"
|
||||||
|
15
resource/l10n/zh-CN.toml
vendored
15
resource/l10n/zh-CN.toml
vendored
@ -690,3 +690,18 @@ other = "取消"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "剩余"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "永续"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "价格"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "已到期"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "天"
|
||||||
|
15
resource/l10n/zh-TW.toml
vendored
15
resource/l10n/zh-TW.toml
vendored
@ -690,3 +690,18 @@ other = "取消"
|
|||||||
|
|
||||||
[FMError]
|
[FMError]
|
||||||
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
||||||
|
|
||||||
|
[Remaining]
|
||||||
|
other = "剩餘"
|
||||||
|
|
||||||
|
[Lifetime]
|
||||||
|
other = "永續"
|
||||||
|
|
||||||
|
[Price]
|
||||||
|
other = "價格"
|
||||||
|
|
||||||
|
[Expired]
|
||||||
|
other = "已到期"
|
||||||
|
|
||||||
|
[Days]
|
||||||
|
other = "天"
|
||||||
|
11
resource/static/theme-server-status/css/dark.css
vendored
11
resource/static/theme-server-status/css/dark.css
vendored
@ -8,6 +8,11 @@ body[theme="dark"] .navbar .navbar-brand {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-nav li.pc-active a,
|
||||||
|
body[theme="dark"] .navbar .navbar-nav li.m-active a{
|
||||||
|
color: rgba(73, 146, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .dropdown-menu {
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
background-color: rgba(23, 26, 30, 1);
|
background-color: rgba(23, 26, 30, 1);
|
||||||
border-color: rgba(49, 54, 59, 1);
|
border-color: rgba(49, 54, 59, 1);
|
||||||
@ -88,8 +93,8 @@ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
|
|||||||
/* expandRow展开部分样式结束 */
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
body[theme="dark"] .progress {
|
body[theme="dark"] .progress {
|
||||||
background-image: linear-gradient(#2c2c2c 0,rgba(28, 29, 38, 1) 100%);
|
background-image: none;
|
||||||
background-color: rgba(28, 29, 38, 1);
|
background-color: rgba(255, 255, 255, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-bar {
|
body[theme="dark"] .progress-bar {
|
||||||
@ -174,5 +179,5 @@ body[theme="dark"] .toolbox i{
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .network-box .network-box-header{
|
body[theme="dark"] .network-box .network-box-header{
|
||||||
border-bottom: 1px solid rgba(110, 112, 121, 0.25);
|
border-bottom-color: rgba(110, 112, 121, 0.25);
|
||||||
}
|
}
|
@ -16,10 +16,15 @@ body[theme="light"] .navbar .navbar-brand {
|
|||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="light"] .navbar .navbar-nav > li > a{
|
body[theme="light"] .navbar .navbar-nav > li > a {
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-nav li.pc-active a,
|
||||||
|
body[theme="light"] .navbar .navbar-nav li.m-active a {
|
||||||
|
color: rgba(73, 146, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
|
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
|
||||||
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
|
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
|
||||||
body[theme="light"] .navbar .navbar-nav > .active > a,
|
body[theme="light"] .navbar .navbar-nav > .active > a,
|
||||||
@ -198,7 +203,7 @@ body[theme="light"] .modal-header i.xclose{
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="light"] .network-box .network-box-header{
|
body[theme="light"] .network-box .network-box-header{
|
||||||
border-bottom: 1px solid rgba(224, 230, 241, 0.6);
|
border-bottom-color: rgba(224, 230, 241, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
|
29
resource/static/theme-server-status/css/main.css
vendored
29
resource/static/theme-server-status/css/main.css
vendored
@ -237,8 +237,8 @@ tr.accordion-toggle{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.node-cell.network {
|
.node-cell.network {
|
||||||
min-width: 100px;
|
min-width: 110px;
|
||||||
max-width: 100px;
|
max-width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-cell.traffic {
|
.node-cell.traffic {
|
||||||
@ -250,6 +250,24 @@ tr.accordion-toggle{
|
|||||||
max-width: 50px;
|
max-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining {
|
||||||
|
min-width: 65px;
|
||||||
|
max-width: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining .additional {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-cell.remaining .additional small{
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
.temp-detail {
|
.temp-detail {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -331,6 +349,8 @@ td.ping-network-quality {
|
|||||||
padding: 5px 0px 15px 5px;
|
padding: 5px 0px 15px 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.network-box .network-box-header .dropdown-menu {
|
.network-box .network-box-header .dropdown-menu {
|
||||||
@ -529,6 +549,10 @@ footer p{
|
|||||||
display: none;
|
display: none;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
.node-cell.remaining {
|
||||||
|
min-width: 75px;
|
||||||
|
max-width: 75px;
|
||||||
|
}
|
||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
@ -572,6 +596,7 @@ footer p{
|
|||||||
.network-box .network-box-header {
|
.network-box .network-box-header {
|
||||||
margin: 8px 0px 0px 8px;
|
margin: 8px 0px 0px 8px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.network-box .chartTitle {
|
.network-box .chartTitle {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -155,6 +155,11 @@ const mixinsVue = {
|
|||||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||||
return window.innerWidth <= 768;
|
return window.innerWidth <= 768;
|
||||||
},
|
},
|
||||||
|
isMenuActive(page){
|
||||||
|
if(page == this.$root.page) {
|
||||||
|
return this.isMobile ? 'm-active' : 'pc-active';
|
||||||
|
}
|
||||||
|
},
|
||||||
setBenchmarkHeight() {
|
setBenchmarkHeight() {
|
||||||
let vh = window.innerHeight * 0.01;
|
let vh = window.innerHeight * 0.01;
|
||||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||||
|
18
resource/template/dashboard-default/server.html
vendored
18
resource/template/dashboard-default/server.html
vendored
@ -30,9 +30,6 @@
|
|||||||
<th>{{tr "VersionNumber"}}</th>
|
<th>{{tr "VersionNumber"}}</th>
|
||||||
<th>{{tr "HideForGuest"}}</th>
|
<th>{{tr "HideForGuest"}}</th>
|
||||||
<th>{{tr "EnableDDNS"}}</th>
|
<th>{{tr "EnableDDNS"}}</th>
|
||||||
<th>{{tr "EnableIPv4"}}</th>
|
|
||||||
<th>{{tr "EnableIpv6"}}</th>
|
|
||||||
<th>{{tr "DDNSDomain"}}</th>
|
|
||||||
<th>{{tr "Secret"}}</th>
|
<th>{{tr "Secret"}}</th>
|
||||||
<th>{{tr "OneKeyInstall"}}</th>
|
<th>{{tr "OneKeyInstall"}}</th>
|
||||||
<th>{{tr "Note"}}</th>
|
<th>{{tr "Note"}}</th>
|
||||||
@ -51,9 +48,6 @@
|
|||||||
<td>{{$server.Host.Version}}</td>
|
<td>{{$server.Host.Version}}</td>
|
||||||
<td>{{$server.HideForGuest}}</td>
|
<td>{{$server.HideForGuest}}</td>
|
||||||
<td>{{$server.EnableDDNS}}</td>
|
<td>{{$server.EnableDDNS}}</td>
|
||||||
<td>{{$server.EnableIPv4}}</td>
|
|
||||||
<td>{{$server.EnableIpv6}}</td>
|
|
||||||
<td>{{$server.DDNSDomain}}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
||||||
<i class="copy icon"></i>
|
<i class="copy icon"></i>
|
||||||
@ -76,8 +70,16 @@
|
|||||||
<i class="apple icon"></i>
|
<i class="apple icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.Note}}</td>
|
<td>
|
||||||
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.PublicNote}}</td>
|
{{if $server.Note}}
|
||||||
|
<button class="ui icon green mini button" title="{{ $server.Note }}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky note icon"></i></button>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{if $server.PublicNote}}
|
||||||
|
<button class="ui icon green mini button" title="{{$server.PublicNote}}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky clipboard icon"></i></button>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="ui mini icon buttons">
|
<div class="ui mini icon buttons">
|
||||||
<button class="ui button" onclick="connectToServer({{$server.ID}})">
|
<button class="ui button" onclick="connectToServer({{$server.ID}})">
|
||||||
|
@ -26,10 +26,10 @@
|
|||||||
<script src="https://unpkg.com/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
|
<script src="https://unpkg.com/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
|
||||||
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/echarts@5.5.0/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240909">
|
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240929">
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202409029">
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240916">
|
||||||
<script src="/static/theme-server-status/js/mixin.js?v20240907"></script>
|
<script src="/static/theme-server-status/js/mixin.js?v20240915"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||||
|
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th>
|
||||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||||
@ -13,6 +14,7 @@
|
|||||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||||
|
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="servers">
|
<tbody id="servers">
|
||||||
@ -36,6 +38,12 @@
|
|||||||
<i :class="'fi fi-' + node.location"></i>
|
<i :class="'fi fi-' + node.location"></i>
|
||||||
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">
|
||||||
|
<template v-if="additional[node.ID] && additional[node.ID].price">
|
||||||
|
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span>
|
||||||
|
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</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 load">@#node.load#@</td>
|
||||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||||
@ -59,6 +67,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining">
|
||||||
|
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||||
|
<template v-if="additional[node.ID] && additional[node.ID].remaining.format">
|
||||||
|
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div>
|
||||||
|
<div class="additional">
|
||||||
|
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||||
|
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small>
|
||||||
|
<small v-else>
|
||||||
|
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span>
|
||||||
|
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||||
<td colspan="16">
|
<td colspan="16">
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||||
|
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th>
|
||||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||||
@ -16,6 +17,7 @@
|
|||||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||||
|
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="servers">
|
<tbody id="servers">
|
||||||
@ -39,6 +41,12 @@
|
|||||||
<i :class="'fi fi-' + node.location"></i>
|
<i :class="'fi fi-' + node.location"></i>
|
||||||
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">
|
||||||
|
<template v-if="additional[node.ID] && additional[node.ID].price">
|
||||||
|
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span>
|
||||||
|
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</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 load">@#node.load#@</td>
|
||||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||||
@ -62,6 +70,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining">
|
||||||
|
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||||
|
<template v-if="additional[node.ID] && additional[node.ID].remaining.format">
|
||||||
|
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div>
|
||||||
|
<div class="additional">
|
||||||
|
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||||
|
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small>
|
||||||
|
<small v-else>
|
||||||
|
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span>
|
||||||
|
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||||
<td colspan="16">
|
<td colspan="16">
|
||||||
|
348
resource/template/theme-server-status/home.html
vendored
348
resource/template/theme-server-status/home.html
vendored
@ -42,6 +42,7 @@
|
|||||||
servers: [],
|
servers: [],
|
||||||
nodesTag: [],
|
nodesTag: [],
|
||||||
nodesNoTag: [],
|
nodesNoTag: [],
|
||||||
|
additional: {},
|
||||||
chartDataList: [],
|
chartDataList: [],
|
||||||
ws: null,
|
ws: null,
|
||||||
language: {{.Conf.Language}},
|
language: {{.Conf.Language}},
|
||||||
@ -81,7 +82,8 @@
|
|||||||
this.countryNameMap = this.initCountryNameMap();
|
this.countryNameMap = this.initCountryNameMap();
|
||||||
this.countryServer = this.initCountryServer();
|
this.countryServer = this.initCountryServer();
|
||||||
this.countryMapChartData = this.initCountryMapChartData();
|
this.countryMapChartData = this.initCountryMapChartData();
|
||||||
})
|
});
|
||||||
|
this.additional = this.initAdditional(this.servers);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 初始化时建立WebSocket连接
|
// 初始化时建立WebSocket连接
|
||||||
@ -98,6 +100,25 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
initAdditional(servers) {
|
||||||
|
let nodes = {};
|
||||||
|
servers?.forEach(server => {
|
||||||
|
if(server.PublicNote) {
|
||||||
|
nodes[server.ID] = {
|
||||||
|
"remaining": {
|
||||||
|
format: this.getRemainingFormat(server.live, server.PublicNote),
|
||||||
|
days: this.getRemainingDays(this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"),server.PublicNote),
|
||||||
|
percent: this.toFixed2(100 - this.getRemainingPercent(this.getNoteElementValue(server.PublicNote, "billingDataMod", "startDate"), this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"), server.PublicNote))
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
amount: this.getNoteElementValue(server.PublicNote, "billingDataMod", "amount"),
|
||||||
|
cycle: this.getNoteElementValue(server.PublicNote, "billingDataMod", "cycle")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return nodes;
|
||||||
|
},
|
||||||
initCountryMap() {
|
initCountryMap() {
|
||||||
return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
|
return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@ -722,6 +743,331 @@
|
|||||||
this.renderCharts(id,true);
|
this.renderCharts(id,true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
getNoteElementValue(string, elementName, childElementName) {
|
||||||
|
let obj;
|
||||||
|
|
||||||
|
try {
|
||||||
|
obj = JSON.parse(string);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elementName) return null;
|
||||||
|
|
||||||
|
if (childElementName) {
|
||||||
|
return obj[elementName] && childElementName in obj[elementName] ? obj[elementName][childElementName] : null;
|
||||||
|
} else {
|
||||||
|
return elementName in obj ? obj[elementName] : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getBillingCycle(billingCycle) {
|
||||||
|
// 统一转换为小写进行比较
|
||||||
|
const cycle = billingCycle.toLowerCase();
|
||||||
|
|
||||||
|
switch (cycle) {
|
||||||
|
case '月':
|
||||||
|
case 'month':
|
||||||
|
case 'monthly':
|
||||||
|
case 'm':
|
||||||
|
return "M";
|
||||||
|
case '季':
|
||||||
|
case 'quarterly':
|
||||||
|
case 'q':
|
||||||
|
return "Q";
|
||||||
|
case '半':
|
||||||
|
case '半年':
|
||||||
|
case 'half':
|
||||||
|
case 'semi-annually':
|
||||||
|
case 'h':
|
||||||
|
return "H";
|
||||||
|
case '年':
|
||||||
|
case 'year':
|
||||||
|
case 'annually':
|
||||||
|
case 'y':
|
||||||
|
return "Y";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAdjustTimezone(reference, target) {
|
||||||
|
// 获取时区
|
||||||
|
const referenceTimezoneOffset = reference.getTimezoneOffset();
|
||||||
|
const targetTimezoneOffset = target.getTimezoneOffset();
|
||||||
|
|
||||||
|
// 计算时区差异
|
||||||
|
const timezoneDifference = (referenceTimezoneOffset - targetTimezoneOffset) * 60 * 1000;
|
||||||
|
|
||||||
|
// 将 target 日期调整到 reference 时区
|
||||||
|
return new Date(target.getTime() + timezoneDifference);
|
||||||
|
},
|
||||||
|
getAutoRenewalEndDate(endDate, billingCycle) {
|
||||||
|
const expiration = new Date(endDate);
|
||||||
|
const current = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 如果 expiration 无效,返回 null 并记录日志
|
||||||
|
if (isNaN(expiration.getTime())) {
|
||||||
|
console.error("getAutoRenewalEndDate: Invalid expiration format");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
flag: 1, // 1表示需要更新
|
||||||
|
check: 0, // 判断逻辑标记
|
||||||
|
count: 0 // 周期累计计数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果当前时间还没到到期时间,直接返回到期时间
|
||||||
|
if (current < expiration) {
|
||||||
|
result.flag = 0;
|
||||||
|
result.date = expiration;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextExpiration = new Date(expiration); // 初始化为原到期时间
|
||||||
|
let newExpirationMonth = expiration.getMonth(); // 获取expiration初始月份
|
||||||
|
|
||||||
|
switch (billingCycle.toUpperCase()) {
|
||||||
|
case 'M': // 月度
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
const monthCheck = current < new Date(current.getFullYear(), current.getMonth(), expiration.getDate());
|
||||||
|
|
||||||
|
// 检查当前月是否在有效期内
|
||||||
|
if (monthCheck) {
|
||||||
|
nextExpiration.setMonth(current.getMonth());
|
||||||
|
result.check = 1;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 1);
|
||||||
|
result.check = 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Q': // 季度
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
// 每次增加 3 个月,直到新的月份大于当前月份
|
||||||
|
while (newExpirationMonth < current.getMonth()) {
|
||||||
|
newExpirationMonth += 3;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextExpiration设置获取到的新月份
|
||||||
|
nextExpiration.setMonth(newExpirationMonth);
|
||||||
|
|
||||||
|
const quarterlyCheck = current < nextExpiration;
|
||||||
|
|
||||||
|
// 检查新月份是否在有效期内
|
||||||
|
if (quarterlyCheck) {
|
||||||
|
result.check = 3;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 3);
|
||||||
|
result.check = 4;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H': // 半年
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
|
||||||
|
// 每次增加 6 个月,直到新的月份大于当前月份
|
||||||
|
while (newExpirationMonth < current.getMonth()) {
|
||||||
|
newExpirationMonth += 6;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextExpiration设置获取到的新月份
|
||||||
|
nextExpiration.setMonth(newExpirationMonth);
|
||||||
|
|
||||||
|
const halfCheck = current < nextExpiration;
|
||||||
|
|
||||||
|
// 检查新月份是否在有效期内
|
||||||
|
if (halfCheck) {
|
||||||
|
result.check = 5;
|
||||||
|
} else {
|
||||||
|
nextExpiration.setMonth(current.getMonth() + 6);
|
||||||
|
result.check = 6;
|
||||||
|
result.count += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Y': // 年度
|
||||||
|
const yearCheck = current < new Date(current.getFullYear(), expiration.getMonth(), expiration.getDate());
|
||||||
|
|
||||||
|
// 如果当前时间比这一年有效期早,则到期为本年
|
||||||
|
if (yearCheck) {
|
||||||
|
nextExpiration.setFullYear(current.getFullYear());
|
||||||
|
result.check = 7;
|
||||||
|
} else {
|
||||||
|
// 否则推到下一年
|
||||||
|
nextExpiration.setFullYear(current.getFullYear() + 1);
|
||||||
|
result.check = 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid billing cycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持原到期时间的时分秒
|
||||||
|
nextExpiration.setHours(expiration.getHours());
|
||||||
|
nextExpiration.setMinutes(expiration.getMinutes());
|
||||||
|
nextExpiration.setSeconds(expiration.getSeconds());
|
||||||
|
|
||||||
|
result.date = nextExpiration;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
getAutoRenewalStartDate(flag, startDate, check, count) {
|
||||||
|
//1.判断什么时候改变 2.如何改变
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const current = this.getAdjustTimezone(start, new Date());
|
||||||
|
|
||||||
|
// 检查 startDate 格式是否有效,若无效返回 null
|
||||||
|
if (isNaN(start.getTime())) {
|
||||||
|
console.error("getAutoRenewalStartDate: Invalid startDate format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 flag 为 0,直接返回开始日期
|
||||||
|
if (flag === 0) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化新的开始日期
|
||||||
|
const newStart = new Date(start);
|
||||||
|
|
||||||
|
switch (check) {
|
||||||
|
case 1: // 处理月份:设置为上个月
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(current.getMonth() - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // 处理月份:设置为当前月
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(current.getMonth());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
case 4: // 处理季度
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(start.getMonth() + 3 * count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
case 6: // 处理半年
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
newStart.setMonth(start.getMonth() + 6 * count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7: // 处理年份:设置为上一年
|
||||||
|
newStart.setFullYear(current.getFullYear() - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8: // 处理年份:保持当前年份
|
||||||
|
newStart.setFullYear(current.getFullYear());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // 默认处理:直接返回
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newStart;
|
||||||
|
},
|
||||||
|
getRemainingFormat(online, note) {
|
||||||
|
if (!note) return null;
|
||||||
|
const startDate = this.getNoteElementValue(note, "billingDataMod", "startDate");
|
||||||
|
const endDate = this.getNoteElementValue(note, "billingDataMod", "endDate");
|
||||||
|
|
||||||
|
// 检查 startDate 和 endDate 是否有效
|
||||||
|
if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||||
|
console.error("getRemainingFormat: Invalid startDate or endDate in note");
|
||||||
|
return null; // 如果无效,返回 null 或其他错误处理逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
//处理特殊时间格式
|
||||||
|
if (startDate.includes('0000-00-00') || endDate.includes("0000-00-00")) {
|
||||||
|
return this.formatPercents(online, this.toFixed2(100));
|
||||||
|
} else {
|
||||||
|
const percent = this.getRemainingPercent(startDate, endDate, note);
|
||||||
|
return this.formatPercents(online, this.toFixed2(percent));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getRemainingDays(endDate, note) {
|
||||||
|
// 检查 endDate 是否有效
|
||||||
|
if (!endDate || typeof endDate !== 'string') {
|
||||||
|
console.error("getRemainingDays: Invalid endDate format");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊时间格式
|
||||||
|
if (endDate.includes("0000-00-00")) return "lifetime";
|
||||||
|
|
||||||
|
// 获取当前时间,并调整时区
|
||||||
|
const currentTime = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 获取计费周期和自动续订日期
|
||||||
|
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||||
|
const autoEndDate = this.getAutoRenewalEndDate(
|
||||||
|
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||||
|
this.getBillingCycle(billingCycle)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查 autoRenewal 状态
|
||||||
|
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||||
|
|
||||||
|
// 确定到期时间
|
||||||
|
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||||
|
|
||||||
|
// 计算剩余天数
|
||||||
|
const timeDiff = end - currentTime;
|
||||||
|
const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
return daysDiff;
|
||||||
|
},
|
||||||
|
getRemainingPercent(startDate, endDate, note) {
|
||||||
|
// 检查 startDate 和 endDate 是否为有效字符串并处理特殊格式
|
||||||
|
if (typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||||
|
console.error("getRemainingPercent: Invalid startDate or endDate format");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate.includes("0000-00-00") || endDate.includes("0000-00-00")) return 100;
|
||||||
|
|
||||||
|
// 获取当前时间并调整时区
|
||||||
|
const now = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||||
|
|
||||||
|
// 获取计费周期
|
||||||
|
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||||
|
|
||||||
|
// 自动获取结束日期
|
||||||
|
const autoEndDate = this.getAutoRenewalEndDate(
|
||||||
|
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||||
|
this.getBillingCycle(billingCycle)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 自动获取开始日期
|
||||||
|
const autoStartDate = autoEndDate.flag == 1
|
||||||
|
? this.getAutoRenewalStartDate(autoEndDate.flag, this.getNoteElementValue(note, "billingDataMod", "startDate"), autoEndDate.check, autoEndDate.count)
|
||||||
|
: new Date(startDate);
|
||||||
|
|
||||||
|
// 计算开始和结束时间
|
||||||
|
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||||
|
const start = autoRenewal ? autoStartDate : new Date(startDate);
|
||||||
|
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||||
|
|
||||||
|
// 计算剩余百分比
|
||||||
|
if (now < start) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (now >= end) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalDuration = end - start;
|
||||||
|
const elapsedDuration = now - start;
|
||||||
|
const percent = (elapsedDuration / totalDuration) * 100;
|
||||||
|
|
||||||
|
// 确保百分比在 0-100 之间
|
||||||
|
return Math.min(Math.max(percent, 0), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
17
resource/template/theme-server-status/menu.html
vendored
17
resource/template/theme-server-status/menu.html
vendored
@ -15,20 +15,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<nav id="navbar" class="navbar-collapse collapse">
|
<nav id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
<li :class="isMenuActive('index')"><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
||||||
<template v-if="isMobile">
|
<li :class="isMenuActive('service')"><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||||
<li class="dropdown">
|
<li :class="isMenuActive('network')"><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||||
<a data-toggle="dropdown"><i class="bi bi-gear-wide-connected" style="position:relative;top:1px;margin-right:3px;font-size:1.1rem;"></i>{{tr "Feature" }}<b class="caret"></b></a>
|
|
||||||
<ul class="dropdown-menu" style="min-width:100px;">
|
|
||||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
|
||||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
|
||||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
|
||||||
</template>
|
|
||||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
||||||
|
Loading…
Reference in New Issue
Block a user