{{define "theme-hotaru/home"}} <!doctype html> <html lang="{{.Conf.Language}}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>{{.Title}}</title> <link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" /> <!-- Fix chrome language detection --> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="Content-Language" content="zh" /> <!-- Styles --> <link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css"> <link rel="stylesheet" type="text/css" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css"> <link rel="stylesheet" href="/static/theme-hotaru/css/core.css?v202012121912" type="text/css"> <link rel="stylesheet" href="/static/theme-hotaru/css/main.css?v202101171153" type="text/css"> <link rel="stylesheet" href="/static/theme-hotaru/css/darkmode.css?v202103021121" type="text/css"> {{if ts .CustomCode}} {{.CustomCode|safe}} {{end}} </head> <body> <section class="hotaru-cover"> <div class="container" style="text-align: center;"> <h1 style="line-height: 8rem;"> <strong>{{.Title}}</strong> </h1> </div> </section> <div id="app"> <div class="container"> <table class="table table-striped"> <thead> <tr> <th>{{tr "Status"}}</th> <th>{{tr "Name"}}</th> <th>{{tr "Platform"}}</th> <th>{{tr "Location"}}</th> <th>{{tr "Uptime"}}</th> <th>{{tr "NetSpeed"}}(B/s) ↓|↑</th> <th>{{tr "NetTransfer"}}(B) ↓|↑</th> <th>CPU</th> <th>RAM</th> <th>{{tr "DiskUsed"}}</th> </tr> </thead> <tbody id="servers" style="text-align:center;"> <tr v-for='server in servers' :id="server.ID"> <td> <div class="progress"> <div :class="'state-'+ (server.live?'online':'offline')"> <small>@#server.live?'{{tr "Running"}}':'{{tr "Offline"}}'#@</small> </div> </div> </td> <td>@#server.Name#@</td> <td>@#server.Host?server.Host.Platform:'-'#@</td> <td>@#server.Host?server.Host.CountryCode:'-'#@</td> <td>@#server.State?secondToDate(server.State.Uptime):'-'#@</td> <td>@#server.State?readableBytes(server.State.NetInSpeed)+'/s|'+readableBytes(server.State.NetOutSpeed)+'/s':'-'#@ </td> <td>@#server.State?readableBytes(server.State.NetInTransfer)+'|'+readableBytes(server.State.NetOutTransfer):'-'#@ </td> <td> <div v-if="server.State" :class="formatPercent(server.live,server.State.CPU, 100).class"> <div class="bar" :style="formatPercent(server.live,server.State.CPU, 100).style"> <small>@#formatPercent(server.live,server.State.CPU,100).percent#@%</small> </div> </div> </td> <td> <div v-if="server.State" :class="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).class"> <div class="bar" :style="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).style"> <small>@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</small> </div> </div> </td> <td> <div v-if="server.State" :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class"> <div class="bar" :style="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).style"> <small>@#parseInt(server.State?server.State.DiskUsed/server.Host.DiskTotal*100:0)#@%</small> </div> </div> </td> </tr> </tbody> </table> </div> <div class="page-section"> <div class="container"> <div class="row"> <div class="col-lg-4 col-md-4 col-sm-4" v-for='server in servers' :id="server.ID"> <div class="panel panel-block panel-block-sm panel-location"> <div class="location-header"> <h3 class="h4"><img :src="'/static/theme-hotaru/img/clients/'+(server.Host&&server.Host.CountryCode?server.Host.CountryCode.toUpperCase():'CN')+'.png'"> @#server.Name#@</h3> <i class="zmdi zmdi-check-circle text-success"></i> </div> <div class="location-progress"> <div :class="formatPercent(server.live,mergeUsage(server.State, server.Host),100).class"> <div class="bar" :style="formatPercent(server.live,mergeUsage(server.State, server.Host),100).style"> </div> </div> </div> <ul class="location-info list-styled"> <li><span class="list-label">{{tr "NetSpeed"}}:</span> @#server.State?formatByteSize(server.State.NetInSpeed)+'/s|'+formatByteSize(server.State.NetOutSpeed)+'/s':'-'#@ </li> </ul> </div> </div> </div> </div> </div> </div> <div class="sidebar-container"> <ul> <li id='sun'><i class="fas fa-sun" title='{{tr "LightMode"}}'></i><span>{{tr "LightMode"}}</span></li> <li id='moon'><i class="fas fa-moon" title='{{tr "DarkMode"}}'></i><span>{{tr "DarkMode"}}</span></li> </ul> </div> <footer> <p style="text-align:center;padding: 15px;">Powered by <a href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> build · {{.Version}} <a href="/service">{{tr "Services"}}</a> <a href="/server">{{tr "AdminPanel"}}</a> </p> </footer> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.js"></script> <script src="/static/semantic-ui-alerts.min.js"></script> <script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script> <script> const initData = JSON.parse('{{.Servers}}').servers; var statusCards = new Vue({ el: '#app', delimiters: ['@#', '#@'], data: { servers: initData, cache: [], }, mounted() { this.troggleDarkMode(); }, methods: { mergeUsage(state, host) { if (!state) { return 0 } if (!host) { return state.CPU } return (state.CPU + (state.MemUsed / host.MemTotal * 100) + (state.DiskUsed / host.DiskTotal * 100)) / 3 }, formatPercent(live, used, total) { const percent = live ? (parseInt(used / total * 100) || 0) : -1 if (!this.cache[percent]) { this.cache[percent] = { class: { ui: true, progress: true, }, style: { 'transition-duration': '300ms', 'min-width': 'unset', width: percent + '% !important', }, percent, } if (percent < 0) { this.cache[percent].style['background-color'] = 'slategray' this.cache[percent].class.offline = true } else if (percent < 51) { this.cache[percent].style['background-color'] = '#0a94f2' this.cache[percent].class.fine = true } else if (percent < 81) { this.cache[percent].style['background-color'] = 'orange' this.cache[percent].class.warning = true } else { this.cache[percent].style['background-color'] = 'crimson' this.cache[percent].class.error = true } } return this.cache[percent] }, readableBytes(bytes) { if (!bytes) { return '0B' } var i = Math.floor(Math.log(bytes) / Math.log(1024)), sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i]; }, formatByteSize(bs) { const x = this.readableBytes(bs) return x != "NaN undefined" ? x : '0B' }, troggleDarkMode() { const hour = new Date(Date.now()).getHours() if (hour > 17 || hour < 4) { document.body.classList.add('dark'); } }, secondToDate(s) { var d = Math.floor(s / 3600 / 24); if (d > 0) { return d + " {{tr "Day"}}" } var h = Math.floor(s / 3600 % 24); var m = Math.floor(s / 60 % 60); var s = Math.floor(s % 60); return h + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2); }, formatTimestamp(t) { return new Date(t * 1000).toLocaleString() } } }) $(function(){ $('#sun').click(function(){ $('body').removeClass('dark'); }); $('#moon').click(function(){ $('body').addClass('dark'); }) }) const wsProtocol = window.location.protocol == "https:" ? "wss" : "ws" let canShowError = true; function connect() { const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws'); ws.onopen = function (evt) { canShowError = true; $.suiAlert({ title: '{{tr "RealtimeChannelEstablished"}}', description: '{{tr "GetTheLatestMonitoringDataInRealTime"}}', type: 'success', time: '2', position: 'top-center', }); } ws.onmessage = function (evt) { const data = JSON.parse(evt.data) statusCards.servers = data.servers for (let i = 0; i < statusCards.servers.length; i++) { const ns = statusCards.servers[i]; if (!ns.Host) ns.live = false else { const lastActive = new Date(ns.LastActive).getTime() if (data.now - lastActive > 10 * 1000) { ns.live = false } else { ns.live = true } } } } ws.onclose = function () { if (canShowError) { canShowError = false; $.suiAlert({ title: '{{tr "RealtimeChannelDisconnect"}}', description: '{{tr "CanNotGetTheLatestMonitoringDataInRealTime"}}', type: 'warning', time: '2', position: 'top-center', }); } setTimeout(function () { connect() }, 3000); } ws.onerror = function () { ws.close() } } connect() </script> </body> </html> {{end}}