⚡️ 增强抗击打能力
This commit is contained in:
		
							parent
							
								
									379da07551
								
							
						
					
					
						commit
						ba39c6e848
					
				@ -4,7 +4,7 @@
 | 
				
			|||||||
  <br>
 | 
					  <br>
 | 
				
			||||||
  <small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
 | 
					  <small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
 | 
				
			||||||
  <br><br>
 | 
					  <br><br>
 | 
				
			||||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.12.15&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.8.1-brightgreen?style=for-the-badge&logo=linux">
 | 
					<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.12.16&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.8.1-brightgreen?style=for-the-badge&logo=linux">
 | 
				
			||||||
  <br>
 | 
					  <br>
 | 
				
			||||||
  <br>
 | 
					  <br>
 | 
				
			||||||
  <p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。</p>
 | 
					  <p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,计划任务和在线终端。</p>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
	"github.com/gorilla/websocket"
 | 
						"github.com/gorilla/websocket"
 | 
				
			||||||
	"github.com/hashicorp/go-uuid"
 | 
						"github.com/hashicorp/go-uuid"
 | 
				
			||||||
	"golang.org/x/crypto/bcrypt"
 | 
						"golang.org/x/crypto/bcrypt"
 | 
				
			||||||
 | 
						"golang.org/x/sync/singleflight"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/naiba/nezha/model"
 | 
						"github.com/naiba/nezha/model"
 | 
				
			||||||
	"github.com/naiba/nezha/pkg/mygin"
 | 
						"github.com/naiba/nezha/pkg/mygin"
 | 
				
			||||||
@ -32,6 +33,7 @@ type commonPage struct {
 | 
				
			|||||||
	r             *gin.Engine
 | 
						r             *gin.Engine
 | 
				
			||||||
	terminals     map[string]*terminalContext
 | 
						terminals     map[string]*terminalContext
 | 
				
			||||||
	terminalsLock *sync.Mutex
 | 
						terminalsLock *sync.Mutex
 | 
				
			||||||
 | 
						requestGroup  singleflight.Group
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (cp *commonPage) serve() {
 | 
					func (cp *commonPage) serve() {
 | 
				
			||||||
@ -108,11 +110,32 @@ func (p *commonPage) service(c *gin.Context) {
 | 
				
			|||||||
	}))
 | 
						}))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (cp *commonPage) getServerStat() ([]byte, error) {
 | 
				
			||||||
 | 
						v, err, _ := cp.requestGroup.Do("serverStats", func() (any, error) {
 | 
				
			||||||
 | 
							singleton.SortedServerLock.RLock()
 | 
				
			||||||
 | 
							defer singleton.SortedServerLock.RUnlock()
 | 
				
			||||||
 | 
							return utils.Json.Marshal(Data{
 | 
				
			||||||
 | 
								Now:     time.Now().Unix() * 1000,
 | 
				
			||||||
 | 
								Servers: singleton.SortedServerList,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return v.([]byte), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (cp *commonPage) home(c *gin.Context) {
 | 
					func (cp *commonPage) home(c *gin.Context) {
 | 
				
			||||||
	singleton.SortedServerLock.RLock()
 | 
						stat, err := cp.getServerStat()
 | 
				
			||||||
	defer singleton.SortedServerLock.RUnlock()
 | 
						if err != nil {
 | 
				
			||||||
 | 
							mygin.ShowErrorPage(c, mygin.ErrInfo{
 | 
				
			||||||
 | 
								Code:  http.StatusInternalServerError,
 | 
				
			||||||
 | 
								Title: "系统错误",
 | 
				
			||||||
 | 
								Msg:   "服务器状态获取失败",
 | 
				
			||||||
 | 
								Link:  "/",
 | 
				
			||||||
 | 
								Btn:   "返回首页",
 | 
				
			||||||
 | 
							}, true)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
 | 
						c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
 | 
				
			||||||
		"Servers":    singleton.SortedServerList,
 | 
							"Servers":    string(stat),
 | 
				
			||||||
		"CustomCode": singleton.Conf.Site.CustomCode,
 | 
							"CustomCode": singleton.Conf.Site.CustomCode,
 | 
				
			||||||
	}))
 | 
						}))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -140,23 +163,17 @@ func (cp *commonPage) ws(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer conn.Close()
 | 
						defer conn.Close()
 | 
				
			||||||
	var bytesToWrite []byte
 | 
					 | 
				
			||||||
	count := 0
 | 
						count := 0
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		singleton.SortedServerLock.RLock()
 | 
							stat, err := cp.getServerStat()
 | 
				
			||||||
		bytesToWrite, err = utils.Json.Marshal(Data{
 | 
					 | 
				
			||||||
			Now:     time.Now().Unix() * 1000,
 | 
					 | 
				
			||||||
			Servers: singleton.SortedServerList,
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		singleton.SortedServerLock.RUnlock()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			break
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		writer, err := conn.NextWriter(websocket.TextMessage)
 | 
							writer, err := conn.NextWriter(websocket.TextMessage)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		_, err = writer.Write(bytesToWrite)
 | 
							_, err = writer.Write(stat)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -28,6 +28,7 @@ require (
 | 
				
			|||||||
	github.com/stretchr/testify v1.7.1
 | 
						github.com/stretchr/testify v1.7.1
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
 | 
						golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
 | 
				
			||||||
	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
 | 
						golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
 | 
				
			||||||
 | 
						golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 | 
				
			||||||
	google.golang.org/grpc v1.45.0
 | 
						google.golang.org/grpc v1.45.0
 | 
				
			||||||
	google.golang.org/protobuf v1.27.1
 | 
						google.golang.org/protobuf v1.27.1
 | 
				
			||||||
	gopkg.in/yaml.v2 v2.4.0
 | 
						gopkg.in/yaml.v2 v2.4.0
 | 
				
			||||||
@ -75,7 +76,6 @@ require (
 | 
				
			|||||||
	github.com/ulikunitz/xz v0.5.10 // indirect
 | 
						github.com/ulikunitz/xz v0.5.10 // indirect
 | 
				
			||||||
	github.com/yusufpapurcu/wmi v1.2.2 // indirect
 | 
						github.com/yusufpapurcu/wmi v1.2.2 // indirect
 | 
				
			||||||
	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
 | 
						golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
 | 
				
			||||||
	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 | 
					 | 
				
			||||||
	golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect
 | 
						golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect
 | 
				
			||||||
	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 | 
						golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 | 
				
			||||||
	golang.org/x/text v0.3.7 // indirect
 | 
						golang.org/x/text v0.3.7 // indirect
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								resource/template/theme-daynight/home.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								resource/template/theme-daynight/home.html
									
									
									
									
										vendored
									
									
								
							@ -169,7 +169,7 @@
 | 
				
			|||||||
    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
 | 
					    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        const initData = {{.Servers }};
 | 
					        const initData = JSON.parse('{{.Servers}}').servers;
 | 
				
			||||||
        var statusCards = new Vue({
 | 
					        var statusCards = new Vue({
 | 
				
			||||||
            el: '#app',
 | 
					            el: '#app',
 | 
				
			||||||
            delimiters: ['@#', '#@'],
 | 
					            delimiters: ['@#', '#@'],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										339
									
								
								resource/template/theme-default/home.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										339
									
								
								resource/template/theme-default/home.html
									
									
									
									
										vendored
									
									
								
							@ -10,48 +10,27 @@
 | 
				
			|||||||
        <div class="active content">
 | 
					        <div class="active content">
 | 
				
			||||||
          <div class="ui four stackable status cards">
 | 
					          <div class="ui four stackable status cards">
 | 
				
			||||||
            <div v-for="server in group.data" :id="server.ID" class="ui card">
 | 
					            <div v-for="server in group.data" :id="server.ID" class="ui card">
 | 
				
			||||||
              <div
 | 
					              <div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
 | 
				
			||||||
                class="content"
 | 
					 | 
				
			||||||
                v-if="server.Host"
 | 
					 | 
				
			||||||
                style="margin-top: 10px; padding-bottom: 5px"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <div class="header">
 | 
					                <div class="header">
 | 
				
			||||||
                  <i :class="server.Host.CountryCode + ' flag'"></i
 | 
					                  <i :class="server.Host.CountryCode + ' flag'"></i><i v-if='server.Host.Platform == "darwin"'
 | 
				
			||||||
                  ><i
 | 
					                    class="apple icon"></i><i v-if='server.Host.Platform == "linux"' class="linux icon"></i><i
 | 
				
			||||||
                    v-if='server.Host.Platform == "darwin"'
 | 
					                    v-if='server.Host.Platform == "windows"' class="windows icon"></i><i
 | 
				
			||||||
                    class="apple icon"
 | 
					                    v-if='server.Host.Platform == "freebsd"' class="freebsd icon"></i>@#server.Name + (server.live?'':'
 | 
				
			||||||
                  ></i
 | 
					                  [已离线]')#@
 | 
				
			||||||
                  ><i
 | 
					 | 
				
			||||||
                    v-if='server.Host.Platform == "linux"'
 | 
					 | 
				
			||||||
                    class="linux icon"
 | 
					 | 
				
			||||||
                  ></i
 | 
					 | 
				
			||||||
                  ><i
 | 
					 | 
				
			||||||
                    v-if='server.Host.Platform == "windows"'
 | 
					 | 
				
			||||||
                    class="windows icon"
 | 
					 | 
				
			||||||
                  ></i
 | 
					 | 
				
			||||||
                  ><i
 | 
					 | 
				
			||||||
                    v-if='server.Host.Platform == "freebsd"'
 | 
					 | 
				
			||||||
                    class="freebsd icon"
 | 
					 | 
				
			||||||
                  ></i
 | 
					 | 
				
			||||||
                  >@#server.Name + (server.live?'':' [已离线]')#@
 | 
					 | 
				
			||||||
                  <i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
 | 
					                  <i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
 | 
				
			||||||
                  <div class="ui content popup" style="margin-bottom: 0">
 | 
					                  <div class="ui content popup" style="margin-bottom: 0">
 | 
				
			||||||
                    系统:@#server.Host.Platform#@-@#server.Host.PlatformVersion#@
 | 
					                    系统:@#server.Host.Platform#@-@#server.Host.PlatformVersion#@
 | 
				
			||||||
                    [<span v-if="server.Host.Virtualization"
 | 
					                    [<span
 | 
				
			||||||
                      >@#server.Host.Virtualization#@:</span
 | 
					                      v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
 | 
				
			||||||
                    >@#server.Host.Arch#@]<br />
 | 
					 | 
				
			||||||
                    CPU:@#server.Host.CPU#@<br />
 | 
					                    CPU:@#server.Host.CPU#@<br />
 | 
				
			||||||
                    硬盘:@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
 | 
					                    硬盘:@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
 | 
				
			||||||
                    内存:@#formatByteSize(server.State.MemUsed)#@/@#formatByteSize(server.Host.MemTotal)#@<br />
 | 
					                    内存:@#formatByteSize(server.State.MemUsed)#@/@#formatByteSize(server.Host.MemTotal)#@<br />
 | 
				
			||||||
                    交换:@#formatByteSize(server.State.SwapUsed)#@/@#formatByteSize(server.Host.SwapTotal)#@<br />
 | 
					                    交换:@#formatByteSize(server.State.SwapUsed)#@/@#formatByteSize(server.Host.SwapTotal)#@<br />
 | 
				
			||||||
                    流量:<i
 | 
					                    流量:<i
 | 
				
			||||||
                      class="arrow alternate circle down outline icon"
 | 
					                      class="arrow alternate circle down outline icon"></i>@#formatByteSize(server.State.NetInTransfer)#@<i
 | 
				
			||||||
                    ></i
 | 
					                      class="arrow alternate circle up outline icon"></i>@#formatByteSize(server.State.NetOutTransfer)#@<br />
 | 
				
			||||||
                    >@#formatByteSize(server.State.NetInTransfer)#@<i
 | 
					                    负载:@# toFixed2(server.State.Load1) #@/@# toFixed2(server.State.Load5) #@/@#
 | 
				
			||||||
                      class="arrow alternate circle up outline icon"
 | 
					                    toFixed2(server.State.Load15) #@<br />
 | 
				
			||||||
                    ></i
 | 
					 | 
				
			||||||
                    >@#formatByteSize(server.State.NetOutTransfer)#@<br />
 | 
					 | 
				
			||||||
                    负载:@# toFixed2(server.State.Load1) #@/@# toFixed2(server.State.Load5) #@/@# toFixed2(server.State.Load15) #@<br />
 | 
					 | 
				
			||||||
                    进程数:@# server.State.ProcessCount #@<br />
 | 
					                    进程数:@# server.State.ProcessCount #@<br />
 | 
				
			||||||
                    连接数:TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
 | 
					                    连接数:TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
 | 
				
			||||||
                    启动:@# formatTimestamp(server.Host.BootTime) #@<br />
 | 
					                    启动:@# formatTimestamp(server.Host.BootTime) #@<br />
 | 
				
			||||||
@ -64,46 +43,27 @@
 | 
				
			|||||||
                  <div class="ui grid">
 | 
					                  <div class="ui grid">
 | 
				
			||||||
                    <div class="three wide column">CPU</div>
 | 
					                    <div class="three wide column">CPU</div>
 | 
				
			||||||
                    <div class="thirteen wide column">
 | 
					                    <div class="thirteen wide column">
 | 
				
			||||||
                      <div
 | 
					                      <div :class="formatPercent(server.live,server.State.CPU, 100).class">
 | 
				
			||||||
                        :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
 | 
					 | 
				
			||||||
                          class="bar"
 | 
					 | 
				
			||||||
                          :style="formatPercent(server.live,server.State.CPU, 100).style"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <small
 | 
					 | 
				
			||||||
                            >@#formatPercent(server.live,server.State.CPU,100).percent#@%</small
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="three wide column">内存</div>
 | 
					                    <div class="three wide column">内存</div>
 | 
				
			||||||
                    <div class="thirteen wide column">
 | 
					                    <div class="thirteen wide column">
 | 
				
			||||||
                      <div
 | 
					                      <div :class="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).class">
 | 
				
			||||||
                        :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">
 | 
				
			||||||
                        <div
 | 
					                          <small>@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</small>
 | 
				
			||||||
                          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>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="three wide column">交换</div>
 | 
					                    <div class="three wide column">交换</div>
 | 
				
			||||||
                    <div class="thirteen wide column">
 | 
					                    <div class="thirteen wide column">
 | 
				
			||||||
                      <div
 | 
					                      <div :class="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).class">
 | 
				
			||||||
                        :class="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).class"
 | 
					                        <div class="bar"
 | 
				
			||||||
                      >
 | 
					                          :style="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).style">
 | 
				
			||||||
                        <div
 | 
					                          <small>@#parseInt(server.State?server.State.SwapUsed/server.Host.SwapTotal*100:0)#@%</small>
 | 
				
			||||||
                          class="bar"
 | 
					 | 
				
			||||||
                          :style="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).style"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <small
 | 
					 | 
				
			||||||
                            >@#parseInt(server.State?server.State.SwapUsed/server.Host.SwapTotal*100:0)#@%</small
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
@ -116,23 +76,16 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="three wide column">硬盘</div>
 | 
					                    <div class="three wide column">硬盘</div>
 | 
				
			||||||
                    <div class="thirteen wide column">
 | 
					                    <div class="thirteen wide column">
 | 
				
			||||||
                      <div
 | 
					                      <div :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class">
 | 
				
			||||||
                        :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">
 | 
				
			||||||
                        <div
 | 
					                          <small>@#parseInt(server.State?server.State.DiskUsed/server.Host.DiskTotal*100:0)#@%</small>
 | 
				
			||||||
                          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>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="three wide column">在线</div>
 | 
					                    <div class="three wide column">在线</div>
 | 
				
			||||||
                    <div class="thirteen wide column">
 | 
					                    <div class="thirteen wide column">
 | 
				
			||||||
                      <i class="clock icon"></i
 | 
					                      <i class="clock icon"></i>@#secondToDate(server.State.Uptime)#@
 | 
				
			||||||
                      >@#secondToDate(server.State.Uptime)#@
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@ -150,143 +103,143 @@
 | 
				
			|||||||
</div>
 | 
					</div>
 | 
				
			||||||
{{template "common/footer" .}}
 | 
					{{template "common/footer" .}}
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
  const initData = {{.Servers}};
 | 
					  const initData = JSON.parse('{{.Servers}}').servers;
 | 
				
			||||||
  var statusCards = new Vue({
 | 
					  var statusCards = new Vue({
 | 
				
			||||||
      el: '#app',
 | 
					    el: '#app',
 | 
				
			||||||
      delimiters: ['@#', '#@'],
 | 
					    delimiters: ['@#', '#@'],
 | 
				
			||||||
      data: {
 | 
					    data: {
 | 
				
			||||||
          data: initData,
 | 
					      data: initData,
 | 
				
			||||||
          groups: [],
 | 
					      groups: [],
 | 
				
			||||||
          cache: [],
 | 
					      cache: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    created() {
 | 
				
			||||||
 | 
					      this.group()
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mounted() {
 | 
				
			||||||
 | 
					      $('.nezha-secondary-font.info.icon').popup({
 | 
				
			||||||
 | 
					        popup: '.ui.content.popup',
 | 
				
			||||||
 | 
					        exclusive: true,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					      toFixed2(f) {
 | 
				
			||||||
 | 
					        return f.toFixed(2)
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      created() {
 | 
					      group() {
 | 
				
			||||||
          this.group()
 | 
					        this.groups = groupingData(this.data, "Tag")
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      mounted() {
 | 
					      formatPercent(live, used, total) {
 | 
				
			||||||
          $('.nezha-secondary-font.info.icon').popup({
 | 
					        const percent = live ? (parseInt(used / total * 100) || 0) : -1
 | 
				
			||||||
              popup: '.ui.content.popup',
 | 
					        if (!this.cache[percent]) {
 | 
				
			||||||
              exclusive: true,
 | 
					          this.cache[percent] = {
 | 
				
			||||||
          });
 | 
					            class: {
 | 
				
			||||||
      },
 | 
					              ui: true,
 | 
				
			||||||
      methods: {
 | 
					              progress: true,
 | 
				
			||||||
          toFixed2(f){
 | 
					            },
 | 
				
			||||||
            return f.toFixed(2)
 | 
					            style: {
 | 
				
			||||||
          },
 | 
					              'transition-duration': '300ms',
 | 
				
			||||||
          group() {
 | 
					              'min-width': 'unset',
 | 
				
			||||||
              this.groups = groupingData(this.data, "Tag")
 | 
					              width: percent + '% !important',
 | 
				
			||||||
          },
 | 
					            },
 | 
				
			||||||
          formatPercent(live, used, total) {
 | 
					            percent,
 | 
				
			||||||
              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]
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          secondToDate(s) {
 | 
					 | 
				
			||||||
              var d = Math.floor(s / 3600 / 24);
 | 
					 | 
				
			||||||
              if (d > 0) {
 | 
					 | 
				
			||||||
                  return d + "天"
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              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()
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          formatByteSize(bs) {
 | 
					 | 
				
			||||||
              const x = readableBytes(bs)
 | 
					 | 
				
			||||||
              return x != "NaN undefined" ? x : '0B'
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					          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]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      secondToDate(s) {
 | 
				
			||||||
 | 
					        var d = Math.floor(s / 3600 / 24);
 | 
				
			||||||
 | 
					        if (d > 0) {
 | 
				
			||||||
 | 
					          return d + "天"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        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()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      formatByteSize(bs) {
 | 
				
			||||||
 | 
					        const x = readableBytes(bs)
 | 
				
			||||||
 | 
					        return x != "NaN undefined" ? x : '0B'
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function groupingData(data, filed) {
 | 
					  function groupingData(data, field) {
 | 
				
			||||||
      let map = {};
 | 
					    let map = {};
 | 
				
			||||||
      let dest = [];
 | 
					    let dest = [];
 | 
				
			||||||
      data.forEach(item => {
 | 
					    data.forEach(item => {
 | 
				
			||||||
          if (!map[item[filed]]) {
 | 
					      if (!map[item[field]]) {
 | 
				
			||||||
              dest.push({
 | 
					        dest.push({
 | 
				
			||||||
                  [filed]: item[filed],
 | 
					          [field]: item[field],
 | 
				
			||||||
                  data: [item]
 | 
					          data: [item]
 | 
				
			||||||
              });
 | 
					        });
 | 
				
			||||||
              map[item[filed]] = item;
 | 
					        map[item[field]] = item;
 | 
				
			||||||
          } else {
 | 
					      } else {
 | 
				
			||||||
              dest.forEach(dItem => {
 | 
					        dest.forEach(dItem => {
 | 
				
			||||||
                  if (dItem[filed] == item[filed]) {
 | 
					          if (dItem[field] == item[field]) {
 | 
				
			||||||
                      dItem.data.push(item);
 | 
					            dItem.data.push(item);
 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
      })
 | 
					        });
 | 
				
			||||||
      return dest;
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    return dest;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const wsProtocol = window.location.protocol == "https:" ? "wss" : "ws"
 | 
					  const wsProtocol = window.location.protocol == "https:" ? "wss" : "ws"
 | 
				
			||||||
  const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
 | 
					  const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
 | 
				
			||||||
  ws.onopen = function (evt) {
 | 
					  ws.onopen = function (evt) {
 | 
				
			||||||
      $.suiAlert({
 | 
					    $.suiAlert({
 | 
				
			||||||
          title: '实时通道建立',
 | 
					      title: '实时通道建立',
 | 
				
			||||||
          description: '可以实时获取最新监控数据啦',
 | 
					      description: '可以实时获取最新监控数据啦',
 | 
				
			||||||
          type: 'success',
 | 
					      type: 'success',
 | 
				
			||||||
          time: '2',
 | 
					      time: '2',
 | 
				
			||||||
          position: 'top-center',
 | 
					      position: 'top-center',
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ws.onmessage = function (evt) {
 | 
					  ws.onmessage = function (evt) {
 | 
				
			||||||
      const oldServers = statusCards.servers
 | 
					    const oldServers = statusCards.servers
 | 
				
			||||||
      const data = JSON.parse(evt.data)
 | 
					    const data = JSON.parse(evt.data)
 | 
				
			||||||
      statusCards.servers = data.servers
 | 
					    statusCards.servers = data.servers
 | 
				
			||||||
      for (let i = 0; i < statusCards.servers.length; i++) {
 | 
					    for (let i = 0; i < statusCards.servers.length; i++) {
 | 
				
			||||||
          const ns = statusCards.servers[i];
 | 
					      const ns = statusCards.servers[i];
 | 
				
			||||||
          if (!ns.Host) ns.live = false
 | 
					      if (!ns.Host) ns.live = false
 | 
				
			||||||
          else {
 | 
					      else {
 | 
				
			||||||
              const lastActive = new Date(ns.LastActive).getTime()
 | 
					        const lastActive = new Date(ns.LastActive).getTime()
 | 
				
			||||||
              if (data.now - lastActive > 10 * 1000) {
 | 
					        if (data.now - lastActive > 10 * 1000) {
 | 
				
			||||||
                  ns.live = false
 | 
					          ns.live = false
 | 
				
			||||||
              } else {
 | 
					        } else {
 | 
				
			||||||
                  ns.live = true
 | 
					          ns.live = true
 | 
				
			||||||
              }
 | 
					        }
 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      statusCards.groups = groupingData(statusCards.servers, "Tag")
 | 
					    }
 | 
				
			||||||
 | 
					    statusCards.groups = groupingData(statusCards.servers, "Tag")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ws.onclose = function () {
 | 
					  ws.onclose = function () {
 | 
				
			||||||
      $.suiAlert({
 | 
					    $.suiAlert({
 | 
				
			||||||
          title: '实时通道断开',
 | 
					      title: '实时通道断开',
 | 
				
			||||||
          description: '无法实时获取最新监控数据咯',
 | 
					      description: '无法实时获取最新监控数据咯',
 | 
				
			||||||
          type: 'warning',
 | 
					      type: 'warning',
 | 
				
			||||||
          time: '2',
 | 
					      time: '2',
 | 
				
			||||||
          position: 'top-center',
 | 
					      position: 'top-center',
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  $('.ui.accordion')
 | 
					  $('.ui.accordion')
 | 
				
			||||||
      .accordion({"exclusive": false})
 | 
					    .accordion({ "exclusive": false })
 | 
				
			||||||
  ;
 | 
					    ;
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
							
								
								
									
										2
									
								
								resource/template/theme-hotaru/home.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								resource/template/theme-hotaru/home.html
									
									
									
									
										vendored
									
									
								
							@ -151,7 +151,7 @@
 | 
				
			|||||||
    <script src="/static/semantic-ui-alerts.min.js"></script>
 | 
					    <script src="/static/semantic-ui-alerts.min.js"></script>
 | 
				
			||||||
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script>
 | 
					    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"></script>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
        const initData = {{.Servers }};
 | 
					        const initData = JSON.parse('{{.Servers}}').servers;
 | 
				
			||||||
        var statusCards = new Vue({
 | 
					        var statusCards = new Vue({
 | 
				
			||||||
            el: '#app',
 | 
					            el: '#app',
 | 
				
			||||||
            delimiters: ['@#', '#@'],
 | 
					            delimiters: ['@#', '#@'],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								resource/template/theme-mdui/home.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								resource/template/theme-mdui/home.html
									
									
									
									
										vendored
									
									
								
							@ -159,7 +159,7 @@
 | 
				
			|||||||
    var container=document.querySelector("#container");
 | 
					    var container=document.querySelector("#container");
 | 
				
			||||||
    container.style.minHeight=window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
 | 
					    container.style.minHeight=window.innerHeight-document.body.clientHeight+container.clientHeight+'px';
 | 
				
			||||||
    mdui.mutation();
 | 
					    mdui.mutation();
 | 
				
			||||||
    const initData = {{.Servers }};
 | 
					    const initData = JSON.parse('{{.Servers}}').servers;
 | 
				
			||||||
    var statusCards = new Vue({
 | 
					    var statusCards = new Vue({
 | 
				
			||||||
        el: '#app',
 | 
					        el: '#app',
 | 
				
			||||||
        delimiters: ['@#', '#@'],
 | 
					        delimiters: ['@#', '#@'],
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ import (
 | 
				
			|||||||
	pb "github.com/naiba/nezha/proto"
 | 
						pb "github.com/naiba/nezha/proto"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var Version = "v0.12.15" // !!记得修改 README 中的 badge 版本!!
 | 
					var Version = "v0.12.16" // !!记得修改 README 中的 badge 版本!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	Conf  *model.Config
 | 
						Conf  *model.Config
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user