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]
 | 
			
		||||
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]
 | 
			
		||||
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]
 | 
			
		||||
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]
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
    background-color: rgba(23, 26, 30, 1);
 | 
			
		||||
    border-color: rgba(49, 54, 59, 1);
 | 
			
		||||
@ -88,8 +93,8 @@ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
 | 
			
		||||
/* expandRow展开部分样式结束 */
 | 
			
		||||
 | 
			
		||||
body[theme="dark"] .progress {
 | 
			
		||||
    background-image: linear-gradient(#2c2c2c 0,rgba(28, 29, 38, 1) 100%);
 | 
			
		||||
    background-color: rgba(28, 29, 38, 1);
 | 
			
		||||
    background-image: none;
 | 
			
		||||
    background-color: rgba(255, 255, 255, 0.075); 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body[theme="dark"] .progress-bar {
 | 
			
		||||
@ -174,5 +179,5 @@ body[theme="dark"] .toolbox i{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body[theme="light"] .navbar .navbar-nav > li > a{
 | 
			
		||||
body[theme="light"] .navbar .navbar-nav > li > a {
 | 
			
		||||
    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:hover,
 | 
			
		||||
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{
 | 
			
		||||
    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) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 {
 | 
			
		||||
    min-width: 100px;
 | 
			
		||||
    max-width: 100px;
 | 
			
		||||
    min-width: 110px;
 | 
			
		||||
    max-width: 110px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.node-cell.traffic {
 | 
			
		||||
@ -250,6 +250,24 @@ tr.accordion-toggle{
 | 
			
		||||
    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 {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
@ -331,6 +349,8 @@ td.ping-network-quality {
 | 
			
		||||
    padding: 5px 0px 15px 5px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border-bottom-width: 1px;
 | 
			
		||||
    border-bottom-style: solid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.network-box .network-box-header .dropdown-menu {
 | 
			
		||||
@ -529,6 +549,10 @@ footer p{
 | 
			
		||||
        display: none;
 | 
			
		||||
        visibility: hidden;
 | 
			
		||||
    }
 | 
			
		||||
    .node-cell.remaining {
 | 
			
		||||
        min-width: 75px;
 | 
			
		||||
        max-width: 75px;    
 | 
			
		||||
    }
 | 
			
		||||
    .accordian-body{
 | 
			
		||||
        margin: 5px 0px 5px 10px;        
 | 
			
		||||
    }
 | 
			
		||||
@ -572,6 +596,7 @@ footer p{
 | 
			
		||||
    .network-box .network-box-header {
 | 
			
		||||
        margin: 8px 0px 0px 8px;
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
        border-bottom: none;
 | 
			
		||||
    }
 | 
			
		||||
    .network-box .chartTitle {
 | 
			
		||||
        font-size: 16px;
 | 
			
		||||
 | 
			
		||||
@ -155,6 +155,11 @@ const mixinsVue = {
 | 
			
		||||
        checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
 | 
			
		||||
            return window.innerWidth <= 768;
 | 
			
		||||
        },
 | 
			
		||||
        isMenuActive(page){
 | 
			
		||||
            if(page == this.$root.page) {
 | 
			
		||||
                return this.isMobile ? 'm-active' : 'pc-active'; 
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        setBenchmarkHeight() {
 | 
			
		||||
            let vh = window.innerHeight * 0.01;
 | 
			
		||||
            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 "HideForGuest"}}</th>
 | 
			
		||||
                    <th>{{tr "EnableDDNS"}}</th>
 | 
			
		||||
                    <th>{{tr "EnableIPv4"}}</th>
 | 
			
		||||
                    <th>{{tr "EnableIpv6"}}</th>
 | 
			
		||||
                    <th>{{tr "DDNSDomain"}}</th>
 | 
			
		||||
                    <th>{{tr "Secret"}}</th>
 | 
			
		||||
                    <th>{{tr "OneKeyInstall"}}</th>
 | 
			
		||||
                    <th>{{tr "Note"}}</th>
 | 
			
		||||
@ -51,9 +48,6 @@
 | 
			
		||||
                    <td>{{$server.Host.Version}}</td>
 | 
			
		||||
                    <td>{{$server.HideForGuest}}</td>
 | 
			
		||||
                    <td>{{$server.EnableDDNS}}</td>
 | 
			
		||||
                    <td>{{$server.EnableIPv4}}</td>
 | 
			
		||||
                    <td>{{$server.EnableIpv6}}</td>
 | 
			
		||||
                    <td>{{$server.DDNSDomain}}</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
 | 
			
		||||
                            <i class="copy icon"></i>
 | 
			
		||||
@ -76,8 +70,16 @@
 | 
			
		||||
                            <i class="apple icon"></i>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td style="word-break: break-word;white-space: pre-wrap;">{{$server.Note}}</td>
 | 
			
		||||
                    <td style="word-break: break-word;white-space: pre-wrap;">{{$server.PublicNote}}</td>
 | 
			
		||||
                    <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>
 | 
			
		||||
                        <div class="ui mini icon buttons">
 | 
			
		||||
                            <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/vue@2.6.14/dist/vue.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/dark.css?v202408011">
 | 
			
		||||
    <link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
 | 
			
		||||
    <script src="/static/theme-server-status/js/mixin.js?v20240907"></script>
 | 
			
		||||
    <link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240929">
 | 
			
		||||
    <link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202409029">
 | 
			
		||||
    <link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240916">
 | 
			
		||||
    <script src="/static/theme-server-status/js/mixin.js?v20240915"></script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<div id="app">
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@
 | 
			
		||||
        <th class="node-cell name center">{{tr "Name"}}</th>
 | 
			
		||||
        <th class="node-cell os center">{{tr "Platform"}}</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 load center">{{tr "Load"}}</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 memory center">{{tr "MemUsed"}}</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>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody id="servers">
 | 
			
		||||
@ -36,6 +38,12 @@
 | 
			
		||||
                <i :class="'fi fi-' + node.location"></i>
 | 
			
		||||
                <span class="node-cell-location-text text-uppercase">@#node.location#@</span>
 | 
			
		||||
            </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 load">@#node.load#@</td>
 | 
			
		||||
            <td style="text-align: center;" class="node-cell network">@#node.network#@</td>
 | 
			
		||||
@ -59,6 +67,21 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </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 class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
 | 
			
		||||
            <td colspan="16">
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@
 | 
			
		||||
        <th class="node-cell name center">{{tr "Name"}}</th>
 | 
			
		||||
        <th class="node-cell os center">{{tr "Platform"}}</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 load center">{{tr "Load"}}</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 memory center">{{tr "MemUsed"}}</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>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody id="servers">
 | 
			
		||||
@ -39,6 +41,12 @@
 | 
			
		||||
                <i :class="'fi fi-' + node.location"></i>
 | 
			
		||||
                <span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
 | 
			
		||||
            </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 load">@#node.load#@</td>
 | 
			
		||||
            <td style="text-align: center;" class="node-cell network">@#node.network#@</td>
 | 
			
		||||
@ -62,6 +70,21 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </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 class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
 | 
			
		||||
            <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: [],
 | 
			
		||||
            nodesTag: [],
 | 
			
		||||
            nodesNoTag: [],
 | 
			
		||||
            additional: {},
 | 
			
		||||
            chartDataList: [],
 | 
			
		||||
            ws: null,
 | 
			
		||||
            language: {{.Conf.Language}},
 | 
			
		||||
@ -81,7 +82,8 @@
 | 
			
		||||
                this.countryNameMap = this.initCountryNameMap();
 | 
			
		||||
                this.countryServer = this.initCountryServer();
 | 
			
		||||
                this.countryMapChartData = this.initCountryMapChartData();
 | 
			
		||||
            })
 | 
			
		||||
            });
 | 
			
		||||
            this.additional = this.initAdditional(this.servers);
 | 
			
		||||
        },
 | 
			
		||||
        mounted() {
 | 
			
		||||
            // 初始化时建立WebSocket连接
 | 
			
		||||
@ -98,6 +100,25 @@
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        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() {
 | 
			
		||||
                return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
 | 
			
		||||
                    .then(response => response.json())
 | 
			
		||||
@ -722,6 +743,331 @@
 | 
			
		||||
                        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>
 | 
			
		||||
        <nav id="navbar" class="navbar-collapse collapse">
 | 
			
		||||
            <ul class="nav navbar-nav">
 | 
			
		||||
                <li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
 | 
			
		||||
                <template v-if="isMobile"> 
 | 
			
		||||
                    <li class="dropdown">
 | 
			
		||||
                        <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>
 | 
			
		||||
                <li :class="isMenuActive('index')"><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
 | 
			
		||||
                <li :class="isMenuActive('service')"><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
 | 
			
		||||
                <li :class="isMenuActive('network')"><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
 | 
			
		||||
                {{ if not .Conf.DisableSwitchTemplateInFrontend }}
 | 
			
		||||
                    <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>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user