Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64da3c7438 | ||
|
|
fd85f599ba | ||
|
|
cae443d5c8 | ||
|
|
0ec1bb2c54 | ||
|
|
47f8447a22 | ||
|
|
9c986d06cb | ||
|
|
e9428d5757 | ||
|
|
daab64d232 | ||
|
|
da8fb57268 | ||
|
|
fda0dad5f1 | ||
|
|
c6fb3196ea | ||
|
|
711d162a46 | ||
|
|
d667ce8afe | ||
|
|
6abc0fce51 | ||
|
|
b1d77a1d27 | ||
|
|
c18e0e420e | ||
|
|
d9097540c3 | ||
|
|
6b05a21641 | ||
|
|
f289b76f47 | ||
|
|
89fe4ec621 | ||
|
|
fca2f60380 |
+16
-12
@@ -97,20 +97,24 @@ def sync_to_gitee(tag: str, body: str, files: slice):
|
|||||||
asset_api_uri = f"{release_api_uri}/{release_id}/attach_files"
|
asset_api_uri = f"{release_api_uri}/{release_id}/attach_files"
|
||||||
|
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
files = {
|
success = False
|
||||||
'file': open(file_path, 'rb')
|
|
||||||
}
|
|
||||||
|
|
||||||
asset_api_response = requests.post(
|
while not success:
|
||||||
asset_api_uri, params={'access_token': access_token}, files=files)
|
files = {
|
||||||
|
'file': open(file_path, 'rb')
|
||||||
|
}
|
||||||
|
|
||||||
if asset_api_response.status_code == 201:
|
asset_api_response = requests.post(
|
||||||
asset_info = asset_api_response.json()
|
asset_api_uri, params={'access_token': access_token}, files=files)
|
||||||
asset_name = asset_info.get('name')
|
|
||||||
print(f"Successfully uploaded {asset_name}!")
|
if asset_api_response.status_code == 201:
|
||||||
else:
|
asset_info = asset_api_response.json()
|
||||||
print(
|
asset_name = asset_info.get('name')
|
||||||
f"Request failed with status code {asset_api_response.status_code}")
|
print(f"Successfully uploaded {asset_name}!")
|
||||||
|
success = True
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Request failed with status code {asset_api_response.status_code}")
|
||||||
|
|
||||||
# 仅保留最新 Release 以防超出 Gitee 仓库配额
|
# 仅保留最新 Release 以防超出 Gitee 仓库配额
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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 alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.18.2-brightgreen?style=for-the-badge&logo=linux">
|
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&style=for-the-badge&logo=github&label=Dashboard"> <img src="https://img.shields.io/github/v/release/nezhahq/agent?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/actions/workflow/status/nezhahq/agent/agent.yml?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.18.3-brightgreen?style=for-the-badge&logo=linux">
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
||||||
@@ -51,27 +51,28 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
|
<a href="https://github.com/JackieSung4ev" title="JackieSung4ev"><img src="https://avatars.githubusercontent.com/u/24974735?v=4" width="50;" alt="JackieSung4ev"/></a>
|
||||||
<a href="https://github.com/cantoblanco" title="Kris"><img src="https://avatars.githubusercontent.com/u/116849421?v=4" width="50;" alt="Kris"/></a>
|
<a href="https://github.com/cantoblanco" title="Kris"><img src="https://avatars.githubusercontent.com/u/116849421?v=4" width="50;" alt="Kris"/></a>
|
||||||
<a href="https://github.com/lemoeo" title="Lemoe"><img src="https://avatars.githubusercontent.com/u/18618627?v=4" width="50;" alt="Lemoe"/></a>
|
<a href="https://github.com/lemoeo" title="Lemoe"><img src="https://avatars.githubusercontent.com/u/18618627?v=4" width="50;" alt="Lemoe"/></a>
|
||||||
<a href="https://github.com/CosmosZ-code" title="CosmosZ-code"><img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/></a>
|
|
||||||
<a href="https://github.com/liuyanxi975" title="刘颜溪"><img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/></a>
|
|
||||||
<a href="https://github.com/spiritLHLS" title="spiritlhl"><img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/></a>
|
<a href="https://github.com/spiritLHLS" title="spiritlhl"><img src="https://avatars.githubusercontent.com/u/103393591?v=4" width="50;" alt="spiritlhl"/></a>
|
||||||
<a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a>
|
<a href="https://github.com/liuyanxi975" title="刘颜溪"><img src="https://avatars.githubusercontent.com/u/24417037?v=4" width="50;" alt="刘颜溪"/></a>
|
||||||
|
<a href="https://github.com/CosmosZ-code" title="CosmosZ-code"><img src="https://avatars.githubusercontent.com/u/81398224?v=4" width="50;" alt="CosmosZ-code"/></a>
|
||||||
<a href="https://github.com/lvgj-stack" title="Ko no dio"><img src="https://avatars.githubusercontent.com/u/38449861?v=4" width="50;" alt="Ko no dio"/></a>
|
<a href="https://github.com/lvgj-stack" title="Ko no dio"><img src="https://avatars.githubusercontent.com/u/38449861?v=4" width="50;" alt="Ko no dio"/></a>
|
||||||
<a href="https://github.com/hhhkkk520" title="Kris"><img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/></a>
|
<a href="https://github.com/hhhkkk520" title="Kris"><img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/></a>
|
||||||
<a href="https://github.com/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></a>
|
<a href="https://github.com/1ridic" title="1ridic"><img src="https://avatars.githubusercontent.com/u/88495501?v=4" width="50;" alt="1ridic"/></a>
|
||||||
<a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a>
|
<a href="https://github.com/Mmx233" title="Mmx"><img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/></a>
|
||||||
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
|
<a href="https://github.com/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></a>
|
||||||
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
|
|
||||||
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
|
|
||||||
<a href="https://github.com/HsukqiLee" title="HsukqiLee"><img src="https://avatars.githubusercontent.com/u/79034142?v=4" width="50;" alt="HsukqiLee"/></a>
|
|
||||||
<a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a>
|
|
||||||
<a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a>
|
|
||||||
<a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a>
|
<a href="https://github.com/iilemon" title="Sean"><img src="https://avatars.githubusercontent.com/u/33201711?v=4" width="50;" alt="Sean"/></a>
|
||||||
|
<a href="https://github.com/fscarmen" title="fscarmen"><img src="https://avatars.githubusercontent.com/u/62703343?v=4" width="50;" alt="fscarmen"/></a>
|
||||||
|
<a href="https://github.com/ch8o" title="no-name-now"><img src="https://avatars.githubusercontent.com/u/9103372?v=4" width="50;" alt="no-name-now"/></a>
|
||||||
|
<a href="https://github.com/HsukqiLee" title="HsukqiLee"><img src="https://avatars.githubusercontent.com/u/79034142?v=4" width="50;" alt="HsukqiLee"/></a>
|
||||||
|
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
|
||||||
|
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
|
||||||
|
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
|
||||||
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
|
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
|
||||||
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
|
<a href="https://github.com/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></a>
|
||||||
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
|
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
|
||||||
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
|
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
|
||||||
<a href="https://github.com/eya46" title="eya46"><img src="https://avatars.githubusercontent.com/u/61458340?v=4" width="50;" alt="eya46"/></a>
|
<a href="https://github.com/eya46" title="eya46"><img src="https://avatars.githubusercontent.com/u/61458340?v=4" width="50;" alt="eya46"/></a>
|
||||||
<a href="https://github.com/guoyongchang" title="guoyongchang"><img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/></a>
|
<a href="https://github.com/guoyongchang" title="guoyongchang"><img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/></a>
|
||||||
|
<a href="https://github.com/hiDandelion" title="hiDandelion"><img src="https://avatars.githubusercontent.com/u/77157418?v=4" width="50;" alt="hiDandelion"/></a>
|
||||||
<a href="https://github.com/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></a>
|
<a href="https://github.com/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></a>
|
||||||
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></a>
|
<a href="https://github.com/lvyaoting" title="lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50;" alt="lvyaoting"/></a>
|
||||||
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
|
<a href="https://github.com/unclezs" title="unclezs"><img src="https://avatars.githubusercontent.com/u/42318775?v=4" width="50;" alt="unclezs"/></a>
|
||||||
@@ -82,6 +83,7 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
|
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
|
||||||
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
|
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
|
||||||
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
|
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
|
||||||
|
<a href="https://github.com/silver-ymz" title="Mingzhuo Yin"><img src="https://avatars.githubusercontent.com/u/78400701?v=4" width="50;" alt="Mingzhuo Yin"/></a>
|
||||||
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
|
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
|
||||||
<a href="https://github.com/xrgzs" title="MadDogOwner"><img src="https://avatars.githubusercontent.com/u/26499123?v=4" width="50;" alt="MadDogOwner"/></a>
|
<a href="https://github.com/xrgzs" title="MadDogOwner"><img src="https://avatars.githubusercontent.com/u/26499123?v=4" width="50;" alt="MadDogOwner"/></a>
|
||||||
<a href="https://github.com/funnyzak" title="Leon"><img src="https://avatars.githubusercontent.com/u/2562087?v=4" width="50;" alt="Leon"/></a>
|
<a href="https://github.com/funnyzak" title="Leon"><img src="https://avatars.githubusercontent.com/u/2562087?v=4" width="50;" alt="Leon"/></a>
|
||||||
@@ -89,7 +91,7 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
|||||||
<a href="https://github.com/techotaku" title="Ian Li"><img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/></a>
|
<a href="https://github.com/techotaku" title="Ian Li"><img src="https://avatars.githubusercontent.com/u/1948179?v=4" width="50;" alt="Ian Li"/></a>
|
||||||
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839"><img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/></a>
|
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839"><img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/></a>
|
||||||
<a href="https://github.com/Es-dese" title="Esdese"><img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/></a>
|
<a href="https://github.com/Es-dese" title="Esdese"><img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/></a>
|
||||||
<a href="https://github.com/wwng2333" title="Crazy"><img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt="Crazy"/></a>
|
<a href="https://github.com/wwng2333" title=":D"><img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt=":D"/></a>
|
||||||
<a href="https://github.com/wellcoming" title="Coming"><img src="https://avatars.githubusercontent.com/u/74850890?v=4" width="50;" alt="Coming"/></a><!--GAMFC_DELIMITER_END-->
|
<a href="https://github.com/wellcoming" title="Coming"><img src="https://avatars.githubusercontent.com/u/74850890?v=4" width="50;" alt="Coming"/></a><!--GAMFC_DELIMITER_END-->
|
||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ func (cp *commonPage) serve() {
|
|||||||
cr.GET("/network", cp.network)
|
cr.GET("/network", cp.network)
|
||||||
cr.GET("/ws", cp.ws)
|
cr.GET("/ws", cp.ws)
|
||||||
cr.POST("/terminal", cp.createTerminal)
|
cr.POST("/terminal", cp.createTerminal)
|
||||||
|
cr.GET("/file", cp.createFM)
|
||||||
|
cr.GET("/file/:id", cp.fm)
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewPasswordForm struct {
|
type viewPasswordForm struct {
|
||||||
@@ -99,7 +101,6 @@ func (p *commonPage) service(c *gin.Context) {
|
|||||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
|
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
|
||||||
"Services": res.([]interface{})[0],
|
"Services": res.([]interface{})[0],
|
||||||
"CycleTransferStats": res.([]interface{})[1],
|
"CycleTransferStats": res.([]interface{})[1],
|
||||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +205,6 @@ func (cp *commonPage) network(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/network"), mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/network"), mygin.CommonEnvironment(c, gin.H{
|
||||||
"Servers": string(serversBytes),
|
"Servers": string(serversBytes),
|
||||||
"MonitorInfos": string(monitorInfos),
|
"MonitorInfos": string(monitorInfos),
|
||||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
|
||||||
"MaxTCPPingValue": singleton.Conf.MaxTCPPingValue,
|
"MaxTCPPingValue": singleton.Conf.MaxTCPPingValue,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -254,14 +254,13 @@ func (cp *commonPage) home(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/home"), mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/home"), mygin.CommonEnvironment(c, gin.H{
|
||||||
"Servers": string(stat),
|
"Servers": string(stat),
|
||||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 10240,
|
ReadBufferSize: 32768,
|
||||||
WriteBufferSize: 10240,
|
WriteBufferSize: 32768,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
@@ -430,5 +429,130 @@ func (cp *commonPage) createTerminal(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/terminal", mygin.CommonEnvironment(c, gin.H{
|
||||||
"SessionID": streamId,
|
"SessionID": streamId,
|
||||||
"ServerName": server.Name,
|
"ServerName": server.Name,
|
||||||
|
"ServerID": server.ID,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *commonPage) fm(c *gin.Context) {
|
||||||
|
streamId := c.Param("id")
|
||||||
|
if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Title: "无权访问",
|
||||||
|
Msg: "FM会话不存在",
|
||||||
|
Link: "/",
|
||||||
|
Btn: "返回首页",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
|
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: "NetworkError",
|
||||||
|
}),
|
||||||
|
Msg: "Websocket协议切换失败",
|
||||||
|
Link: "/",
|
||||||
|
Btn: "返回首页",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer wsConn.Close()
|
||||||
|
conn := websocketx.NewConn(wsConn)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// PING 保活
|
||||||
|
for {
|
||||||
|
if err = conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = rpc.NezhaHandlerSingleton.UserConnected(streamId, conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.NezhaHandlerSingleton.StartStream(streamId, time.Second*10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *commonPage) createFM(c *gin.Context) {
|
||||||
|
IdString := c.Query("id")
|
||||||
|
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); !authorized {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Title: "无权访问",
|
||||||
|
Msg: "用户未登录",
|
||||||
|
Link: "/login",
|
||||||
|
Btn: "去登录",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
streamId, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Title: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: "SystemError",
|
||||||
|
}),
|
||||||
|
Msg: "生成会话ID失败",
|
||||||
|
Link: "/server",
|
||||||
|
Btn: "返回重试",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
||||||
|
|
||||||
|
serverId, err := strconv.Atoi(IdString)
|
||||||
|
if err != nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Title: "请求失败",
|
||||||
|
Msg: "请求参数有误:" + err.Error(),
|
||||||
|
Link: "/server",
|
||||||
|
Btn: "返回重试",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
singleton.ServerLock.RLock()
|
||||||
|
server := singleton.ServerList[uint64(serverId)]
|
||||||
|
singleton.ServerLock.RUnlock()
|
||||||
|
if server == nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Title: "请求失败",
|
||||||
|
Msg: "服务器不存在或处于离线状态",
|
||||||
|
Link: "/server",
|
||||||
|
Btn: "返回重试",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmData, _ := utils.Json.Marshal(&model.TaskFM{
|
||||||
|
StreamID: streamId,
|
||||||
|
})
|
||||||
|
if err := server.TaskStream.Send(&proto.Task{
|
||||||
|
Type: model.TaskTypeFM,
|
||||||
|
Data: string(fmData),
|
||||||
|
}); err != nil {
|
||||||
|
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Title: "请求失败",
|
||||||
|
Msg: "Agent信令下发失败",
|
||||||
|
Link: "/server",
|
||||||
|
Btn: "返回重试",
|
||||||
|
}, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/file", mygin.CommonEnvironment(c, gin.H{
|
||||||
|
"SessionID": streamId,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,20 +35,27 @@ func ServeWeb(port uint) *http.Server {
|
|||||||
pprof.Register(r)
|
pprof.Register(r)
|
||||||
}
|
}
|
||||||
r.Use(natGateway)
|
r.Use(natGateway)
|
||||||
tmpl := template.New("").Funcs(funcMap)
|
if os.Getenv("NZ_LOCAL_TEMPLATE") == "true" {
|
||||||
var err error
|
r.SetFuncMap(funcMap)
|
||||||
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
|
r.Use(mygin.RecordPath)
|
||||||
if err != nil {
|
r.Static("/static", "resource/static")
|
||||||
panic(err)
|
r.LoadHTMLGlob("resource/template/**/*.html")
|
||||||
|
} else {
|
||||||
|
tmpl := template.New("").Funcs(funcMap)
|
||||||
|
var err error
|
||||||
|
tmpl, err = tmpl.ParseFS(resource.TemplateFS, "template/**/*.html")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
tmpl = loadThirdPartyTemplates(tmpl)
|
||||||
|
r.SetHTMLTemplate(tmpl)
|
||||||
|
r.Use(mygin.RecordPath)
|
||||||
|
staticFs, err := fs.Sub(resource.StaticFS, "static")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r.StaticFS("/static", http.FS(staticFs))
|
||||||
}
|
}
|
||||||
tmpl = loadThirdPartyTemplates(tmpl)
|
|
||||||
r.SetHTMLTemplate(tmpl)
|
|
||||||
r.Use(mygin.RecordPath)
|
|
||||||
staticFs, err := fs.Sub(resource.StaticFS, "static")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
r.StaticFS("/static", http.FS(staticFs))
|
|
||||||
r.Static("/static-custom", "resource/static/custom")
|
r.Static("/static-custom", "resource/static/custom")
|
||||||
routers(r)
|
routers(r)
|
||||||
page404 := func(c *gin.Context) {
|
page404 := func(c *gin.Context) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -384,6 +385,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
s.Host = &model.Host{}
|
s.Host = &model.Host{}
|
||||||
s.State = &model.HostState{}
|
s.State = &model.HostState{}
|
||||||
|
s.TaskCloseLock = new(sync.Mutex)
|
||||||
singleton.ServerLock.Lock()
|
singleton.ServerLock.Lock()
|
||||||
singleton.SecretToID[s.Secret] = s.ID
|
singleton.SecretToID[s.Secret] = s.ID
|
||||||
singleton.ServerList[s.ID] = &s
|
singleton.ServerList[s.ID] = &s
|
||||||
@@ -906,6 +908,7 @@ type settingForm struct {
|
|||||||
Theme string
|
Theme string
|
||||||
DashboardTheme string
|
DashboardTheme string
|
||||||
CustomCode string
|
CustomCode string
|
||||||
|
CustomCodeDashboard string
|
||||||
ViewPassword string
|
ViewPassword string
|
||||||
IgnoredIPNotification string
|
IgnoredIPNotification string
|
||||||
IPChangeNotificationTag string // IP变更提醒的通知组
|
IPChangeNotificationTag string // IP变更提醒的通知组
|
||||||
@@ -971,6 +974,7 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
|
|||||||
singleton.Conf.Site.Theme = sf.Theme
|
singleton.Conf.Site.Theme = sf.Theme
|
||||||
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
|
singleton.Conf.Site.DashboardTheme = sf.DashboardTheme
|
||||||
singleton.Conf.Site.CustomCode = sf.CustomCode
|
singleton.Conf.Site.CustomCode = sf.CustomCode
|
||||||
|
singleton.Conf.Site.CustomCodeDashboard = sf.CustomCodeDashboard
|
||||||
singleton.Conf.Site.ViewPassword = sf.ViewPassword
|
singleton.Conf.Site.ViewPassword = sf.ViewPassword
|
||||||
singleton.Conf.Oauth2.Admin = sf.Admin
|
singleton.Conf.Oauth2.Admin = sf.Admin
|
||||||
// 保证NotificationTag不为空
|
// 保证NotificationTag不为空
|
||||||
|
|||||||
+7
-6
@@ -80,12 +80,13 @@ type Config struct {
|
|||||||
Debug bool // debug模式开关
|
Debug bool // debug模式开关
|
||||||
Language string // 系统语言,默认 zh-CN
|
Language string // 系统语言,默认 zh-CN
|
||||||
Site struct {
|
Site struct {
|
||||||
Brand string // 站点名称
|
Brand string // 站点名称
|
||||||
CookieName string // 浏览器 Cookie 名称
|
CookieName string // 浏览器 Cookie 名称
|
||||||
Theme string
|
Theme string
|
||||||
DashboardTheme string
|
DashboardTheme string
|
||||||
CustomCode string
|
CustomCode string
|
||||||
ViewPassword string // 前台查看密码
|
CustomCodeDashboard string
|
||||||
|
ViewPassword string // 前台查看密码
|
||||||
}
|
}
|
||||||
Oauth2 struct {
|
Oauth2 struct {
|
||||||
Type string
|
Type string
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
TaskTypeTerminalGRPC
|
TaskTypeTerminalGRPC
|
||||||
TaskTypeNAT
|
TaskTypeNAT
|
||||||
TaskTypeReportHostInfo
|
TaskTypeReportHostInfo
|
||||||
|
TaskTypeFM
|
||||||
)
|
)
|
||||||
|
|
||||||
type TerminalTask struct {
|
type TerminalTask struct {
|
||||||
@@ -34,6 +35,10 @@ type TaskNAT struct {
|
|||||||
Host string
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskFM struct {
|
||||||
|
StreamID string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MonitorCoverAll = iota
|
MonitorCoverAll = iota
|
||||||
MonitorCoverIgnoreAll
|
MonitorCoverIgnoreAll
|
||||||
|
|||||||
@@ -170,14 +170,23 @@ func (ns *NotificationServerBundle) replaceParamsInString(str string, message st
|
|||||||
|
|
||||||
if ns.Server != nil {
|
if ns.Server != nil {
|
||||||
str = strings.ReplaceAll(str, "#SERVER.NAME#", mod(ns.Server.Name))
|
str = strings.ReplaceAll(str, "#SERVER.NAME#", mod(ns.Server.Name))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.ID#", mod(fmt.Sprintf("%d", ns.Server.ID)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.CPU#", mod(fmt.Sprintf("%f", ns.Server.State.CPU)))
|
str = strings.ReplaceAll(str, "#SERVER.CPU#", mod(fmt.Sprintf("%f", ns.Server.State.CPU)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.MEM#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)))
|
str = strings.ReplaceAll(str, "#SERVER.MEM#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.SWAP#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)))
|
str = strings.ReplaceAll(str, "#SERVER.SWAP#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.DISK#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)))
|
str = strings.ReplaceAll(str, "#SERVER.DISK#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.MEMUSED#", mod(fmt.Sprintf("%d", ns.Server.State.MemUsed)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.SWAPUSED#", mod(fmt.Sprintf("%d", ns.Server.State.SwapUsed)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.DISKUSED#", mod(fmt.Sprintf("%d", ns.Server.State.DiskUsed)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.MEMTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.MemTotal)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.SWAPTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.SwapTotal)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.DISKTOTAL#", mod(fmt.Sprintf("%d", ns.Server.Host.DiskTotal)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.NETINSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetInSpeed)))
|
str = strings.ReplaceAll(str, "#SERVER.NETINSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetInSpeed)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.NETOUTSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutSpeed)))
|
str = strings.ReplaceAll(str, "#SERVER.NETOUTSPEED#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutSpeed)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.TRANSFERIN#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)))
|
str = strings.ReplaceAll(str, "#SERVER.TRANSFERIN#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.TRANSFEROUT#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)))
|
str = strings.ReplaceAll(str, "#SERVER.TRANSFEROUT#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.NETINTRANSFER#", mod(fmt.Sprintf("%d", ns.Server.State.NetInTransfer)))
|
||||||
|
str = strings.ReplaceAll(str, "#SERVER.NETOUTTRANSFER#", mod(fmt.Sprintf("%d", ns.Server.State.NetOutTransfer)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.LOAD1#", mod(fmt.Sprintf("%f", ns.Server.State.Load1)))
|
str = strings.ReplaceAll(str, "#SERVER.LOAD1#", mod(fmt.Sprintf("%f", ns.Server.State.Load1)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.LOAD5#", mod(fmt.Sprintf("%f", ns.Server.State.Load5)))
|
str = strings.ReplaceAll(str, "#SERVER.LOAD5#", mod(fmt.Sprintf("%f", ns.Server.State.Load5)))
|
||||||
str = strings.ReplaceAll(str, "#SERVER.LOAD15#", mod(fmt.Sprintf("%f", ns.Server.State.Load15)))
|
str = strings.ReplaceAll(str, "#SERVER.LOAD15#", mod(fmt.Sprintf("%f", ns.Server.State.Load15)))
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ func execCase(t *testing.T, item testSt) {
|
|||||||
UdpConnCount: 0,
|
UdpConnCount: 0,
|
||||||
ProcessCount: 0,
|
ProcessCount: 0,
|
||||||
},
|
},
|
||||||
LastActive: time.Time{},
|
LastActive: time.Time{},
|
||||||
TaskClose: nil,
|
TaskClose: nil,
|
||||||
TaskStream: nil,
|
TaskStream: nil,
|
||||||
PrevHourlyTransferIn: 0,
|
PrevTransferInSnapshot: 0,
|
||||||
PrevHourlyTransferOut: 0,
|
PrevTransferOutSnapshot: 0,
|
||||||
}
|
}
|
||||||
ns := NotificationServerBundle{
|
ns := NotificationServerBundle{
|
||||||
Notification: &n,
|
Notification: &n,
|
||||||
|
|||||||
+5
-3
@@ -5,6 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -103,21 +105,21 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
|
|||||||
src = float64(server.LastActive.Unix())
|
src = float64(server.LastActive.Unix())
|
||||||
}
|
}
|
||||||
case "transfer_in_cycle":
|
case "transfer_in_cycle":
|
||||||
src = float64(server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn))
|
src = float64(utils.Uint64SubInt64(server.State.NetInTransfer, server.PrevTransferInSnapshot))
|
||||||
if u.CycleInterval != 0 {
|
if u.CycleInterval != 0 {
|
||||||
var res NResult
|
var res NResult
|
||||||
db.Model(&Transfer{}).Select("SUM(`in`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
db.Model(&Transfer{}).Select("SUM(`in`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
||||||
src += float64(res.N)
|
src += float64(res.N)
|
||||||
}
|
}
|
||||||
case "transfer_out_cycle":
|
case "transfer_out_cycle":
|
||||||
src = float64(server.State.NetOutTransfer - uint64(server.PrevHourlyTransferOut))
|
src = float64(utils.Uint64SubInt64(server.State.NetOutTransfer, server.PrevTransferOutSnapshot))
|
||||||
if u.CycleInterval != 0 {
|
if u.CycleInterval != 0 {
|
||||||
var res NResult
|
var res NResult
|
||||||
db.Model(&Transfer{}).Select("SUM(`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
db.Model(&Transfer{}).Select("SUM(`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
||||||
src += float64(res.N)
|
src += float64(res.N)
|
||||||
}
|
}
|
||||||
case "transfer_all_cycle":
|
case "transfer_all_cycle":
|
||||||
src = float64(server.State.NetOutTransfer - uint64(server.PrevHourlyTransferOut) + server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn))
|
src = float64(utils.Uint64SubInt64(server.State.NetOutTransfer, server.PrevTransferOutSnapshot) + utils.Uint64SubInt64(server.State.NetInTransfer, server.PrevTransferInSnapshot))
|
||||||
if u.CycleInterval != 0 {
|
if u.CycleInterval != 0 {
|
||||||
var res NResult
|
var res NResult
|
||||||
db.Model(&Transfer{}).Select("SUM(`in`+`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
db.Model(&Transfer{}).Select("SUM(`in`+`out`) AS n").Where("datetime(`created_at`) >= datetime(?) AND server_id = ?", u.GetTransferDurationStart().UTC(), server.ID).Scan(&res)
|
||||||
|
|||||||
+14
-11
@@ -3,6 +3,7 @@ package model
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
@@ -17,21 +18,22 @@ type Server struct {
|
|||||||
Note string `json:"-"` // 管理员可见备注
|
Note string `json:"-"` // 管理员可见备注
|
||||||
DisplayIndex int // 展示排序,越大越靠前
|
DisplayIndex int // 展示排序,越大越靠前
|
||||||
HideForGuest bool // 对游客隐藏
|
HideForGuest bool // 对游客隐藏
|
||||||
EnableDDNS bool // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
EnableDDNS bool `json:"-"` // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
||||||
EnableIPv4 bool // 是否启用DDNS IPv4
|
EnableIPv4 bool `json:"-"` // 是否启用DDNS IPv4
|
||||||
EnableIpv6 bool // 是否启用DDNS IPv6
|
EnableIpv6 bool `json:"-"` // 是否启用DDNS IPv6
|
||||||
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
DDNSDomain string `json:"-"` // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
||||||
DDNSProfile string // DDNS配置
|
DDNSProfile string `json:"-"` // DDNS配置
|
||||||
|
|
||||||
Host *Host `gorm:"-"`
|
Host *Host `gorm:"-"`
|
||||||
State *HostState `gorm:"-"`
|
State *HostState `gorm:"-"`
|
||||||
LastActive time.Time `gorm:"-"`
|
LastActive time.Time `gorm:"-"`
|
||||||
|
|
||||||
TaskClose chan error `gorm:"-" json:"-"`
|
TaskClose chan error `gorm:"-" json:"-"`
|
||||||
TaskStream pb.NezhaService_RequestTaskServer `gorm:"-" json:"-"`
|
TaskCloseLock *sync.Mutex `gorm:"-" json:"-"`
|
||||||
|
TaskStream pb.NezhaService_RequestTaskServer `gorm:"-" json:"-"`
|
||||||
|
|
||||||
PrevHourlyTransferIn int64 `gorm:"-" json:"-"` // 上次数据点时的入站使用量
|
PrevTransferInSnapshot int64 `gorm:"-" json:"-"` // 上次数据点时的入站使用量
|
||||||
PrevHourlyTransferOut int64 `gorm:"-" json:"-"` // 上次数据点时的出站使用量
|
PrevTransferOutSnapshot int64 `gorm:"-" json:"-"` // 上次数据点时的出站使用量
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CopyFromRunningServer(old *Server) {
|
func (s *Server) CopyFromRunningServer(old *Server) {
|
||||||
@@ -39,9 +41,10 @@ func (s *Server) CopyFromRunningServer(old *Server) {
|
|||||||
s.State = old.State
|
s.State = old.State
|
||||||
s.LastActive = old.LastActive
|
s.LastActive = old.LastActive
|
||||||
s.TaskClose = old.TaskClose
|
s.TaskClose = old.TaskClose
|
||||||
|
s.TaskCloseLock = old.TaskCloseLock
|
||||||
s.TaskStream = old.TaskStream
|
s.TaskStream = old.TaskStream
|
||||||
s.PrevHourlyTransferIn = old.PrevHourlyTransferIn
|
s.PrevTransferInSnapshot = old.PrevTransferInSnapshot
|
||||||
s.PrevHourlyTransferOut = old.PrevHourlyTransferOut
|
s.PrevTransferOutSnapshot = old.PrevTransferOutSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolToString(b bool) string {
|
func boolToString(b bool) string {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
|
|||||||
data["Version"] = singleton.Version
|
data["Version"] = singleton.Version
|
||||||
data["Conf"] = singleton.Conf
|
data["Conf"] = singleton.Conf
|
||||||
data["Themes"] = model.Themes
|
data["Themes"] = model.Themes
|
||||||
|
data["CustomCode"] = singleton.Conf.Site.CustomCode
|
||||||
|
data["CustomCodeDashboard"] = singleton.Conf.Site.CustomCodeDashboard
|
||||||
// 是否是管理页面
|
// 是否是管理页面
|
||||||
data["IsAdminPage"] = adminPage[data["MatchedPath"].(string)]
|
data["IsAdminPage"] = adminPage[data["MatchedPath"].(string)]
|
||||||
// 站点标题
|
// 站点标题
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ func ValidateViewPassword(opt ValidateViewPasswordOption) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
if opt.IsPage {
|
if opt.IsPage {
|
||||||
c.HTML(http.StatusOK, GetPreferredTheme(c, "/viewpassword"), CommonEnvironment(c, gin.H{
|
c.HTML(http.StatusOK, GetPreferredTheme(c, "/viewpassword"), CommonEnvironment(c, gin.H{
|
||||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}),
|
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}),
|
||||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -76,3 +76,13 @@ func GenerateRandomString(n int) (string, error) {
|
|||||||
}
|
}
|
||||||
return string(ret), nil
|
return string(ret), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Uint64SubInt64(a uint64, b int64) uint64 {
|
||||||
|
if b < 0 {
|
||||||
|
return a + uint64(-b)
|
||||||
|
}
|
||||||
|
if a < uint64(b) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return a - uint64(b)
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+7
-1
@@ -331,6 +331,9 @@ other = "Frontend Theme"
|
|||||||
[CustomCodes]
|
[CustomCodes]
|
||||||
other = "Custom Codes (Style and Script)"
|
other = "Custom Codes (Style and Script)"
|
||||||
|
|
||||||
|
[CustomCodesDashboard]
|
||||||
|
other = "Custom Codes for Dashboard"
|
||||||
|
|
||||||
[AccessPassword]
|
[AccessPassword]
|
||||||
other = "Frontend Access Password"
|
other = "Frontend Access Password"
|
||||||
|
|
||||||
@@ -650,4 +653,7 @@ other = "Disable Switch Template in Frontend"
|
|||||||
other = "Servers On World Map"
|
other = "Servers On World Map"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "Network Monitor"
|
||||||
Vendored
+7
-1
@@ -331,6 +331,9 @@ other = "Tema de Frontend"
|
|||||||
[CustomCodes]
|
[CustomCodes]
|
||||||
other = "Códigos Personalizados (Incluye style y script)"
|
other = "Códigos Personalizados (Incluye style y script)"
|
||||||
|
|
||||||
|
[CustomCodesDashboard]
|
||||||
|
other = "Custom Codes for Dashboard"
|
||||||
|
|
||||||
[AccessPassword]
|
[AccessPassword]
|
||||||
other = "Contraseña de Acceso al Frontend"
|
other = "Contraseña de Acceso al Frontend"
|
||||||
|
|
||||||
@@ -650,4 +653,7 @@ other = "Deshabilitar Cambio de Plantilla en Frontend"
|
|||||||
other = "Servidores en el mapa mundial"
|
other = "Servidores en el mapa mundial"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "Red Monitor"
|
||||||
Vendored
+7
-1
@@ -331,6 +331,9 @@ other = "前台界面主题"
|
|||||||
[CustomCodes]
|
[CustomCodes]
|
||||||
other = "自定义代码(包括 style 和 script)"
|
other = "自定义代码(包括 style 和 script)"
|
||||||
|
|
||||||
|
[CustomCodesDashboard]
|
||||||
|
other = "Custom Codes for Dashboard"
|
||||||
|
|
||||||
[AccessPassword]
|
[AccessPassword]
|
||||||
other = "前台访问密码"
|
other = "前台访问密码"
|
||||||
|
|
||||||
@@ -650,4 +653,7 @@ other = "禁止前台切换模板"
|
|||||||
other = "服务器世界分布图"
|
other = "服务器世界分布图"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "内网穿透"
|
other = "内网穿透"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "网络监控"
|
||||||
|
|||||||
Vendored
+7
-1
@@ -331,6 +331,9 @@ other = "前台界面主題"
|
|||||||
[CustomCodes]
|
[CustomCodes]
|
||||||
other = "自定義代碼(包括 style 和 script)"
|
other = "自定義代碼(包括 style 和 script)"
|
||||||
|
|
||||||
|
[CustomCodesDashboard]
|
||||||
|
other = "Custom Codes for Dashboard"
|
||||||
|
|
||||||
[AccessPassword]
|
[AccessPassword]
|
||||||
other = "前台訪問密碼"
|
other = "前台訪問密碼"
|
||||||
|
|
||||||
@@ -650,4 +653,7 @@ other = "禁止前台切換主題"
|
|||||||
other = "伺服器世界分布圖"
|
other = "伺服器世界分布圖"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT"
|
||||||
|
|
||||||
|
[NetworkSpiterList]
|
||||||
|
other = "網絡監控"
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
html[nz-theme='dark'] body {
|
||||||
|
background-color: #121212 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.menu {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.menu * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .accordion {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .accordion .title {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.card {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .header {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .description {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .icon {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.popup {
|
||||||
|
background-color: #575757 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.table {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui thead th {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.buttons .button {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.modal {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.modal * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] textarea,
|
||||||
|
html[nz-theme='dark'] input,
|
||||||
|
html[nz-theme='dark'] select,
|
||||||
|
html[nz-theme='dark'] .dropdown {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.message {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.dropdown .menu {
|
||||||
|
background-color: #575757 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.modal>.header {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.modal>.content {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.modal>.actions {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] #alert {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.form .field>label {
|
||||||
|
color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.segment {
|
||||||
|
background-color: #3f3f3f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.segment textarea,
|
||||||
|
html[nz-theme='dark'] input,
|
||||||
|
html[nz-theme='dark'] select,
|
||||||
|
html[nz-theme='dark'] .dropdown {
|
||||||
|
background-color: #575757 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] form label {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.inverted.segment {
|
||||||
|
background-color: #121212 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.inverted.segment * {
|
||||||
|
color: #8b8b8b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .menu .dropdown {
|
||||||
|
background-color: #282828 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .ui.menu .ui.dropdown .menu>.item {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[nz-theme='dark'] .login .ui.message{
|
||||||
|
color: #8b8b8b !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
let receivedLength = 0;
|
||||||
|
let expectedLength = 0;
|
||||||
|
let root;
|
||||||
|
let draftHandle;
|
||||||
|
let accessHandle;
|
||||||
|
|
||||||
|
const Operation = Object.freeze({
|
||||||
|
WriteHeader: 1,
|
||||||
|
WriteChunks: 2,
|
||||||
|
DeleteFiles: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
onmessage = async function (event) {
|
||||||
|
try {
|
||||||
|
const { operation, arrayBuffer, fileName } = event.data;
|
||||||
|
|
||||||
|
switch (operation) {
|
||||||
|
case Operation.WriteHeader: {
|
||||||
|
const dataView = new DataView(arrayBuffer);
|
||||||
|
expectedLength = Number(dataView.getBigUint64(4, false));
|
||||||
|
receivedLength = 0;
|
||||||
|
|
||||||
|
// Create a new temporary file
|
||||||
|
root = await navigator.storage.getDirectory();
|
||||||
|
draftHandle = await root.getFileHandle(fileName, { create: true });
|
||||||
|
accessHandle = await draftHandle.createSyncAccessHandle();
|
||||||
|
|
||||||
|
// Inform that file handle is created
|
||||||
|
const dataChunk = arrayBuffer.slice(12);
|
||||||
|
receivedLength += dataChunk.byteLength;
|
||||||
|
accessHandle.write(dataChunk, { at: 0 });
|
||||||
|
const progress = 'got handle';
|
||||||
|
postMessage({ type: 'progress', progress: progress });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Operation.WriteChunks: {
|
||||||
|
if (!accessHandle) {
|
||||||
|
throw new Error('accessHandle is undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataChunk = arrayBuffer;
|
||||||
|
accessHandle.write(dataChunk, { at: receivedLength });
|
||||||
|
receivedLength += dataChunk.byteLength;
|
||||||
|
|
||||||
|
if (receivedLength === expectedLength) {
|
||||||
|
accessHandle.flush();
|
||||||
|
accessHandle.close();
|
||||||
|
|
||||||
|
const fileBlob = await draftHandle.getFile();
|
||||||
|
const blob = new Blob([fileBlob], { type: 'application/octet-stream' });
|
||||||
|
|
||||||
|
postMessage({ type: 'result', blob: blob, fileName: fileName });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Operation.DeleteFiles: {
|
||||||
|
for await (const [name, handle] of root.entries()) {
|
||||||
|
if (handle.kind === 'file') {
|
||||||
|
await root.removeEntry(name);
|
||||||
|
} else if (handle.kind === 'directory') {
|
||||||
|
await root.removeEntry(name, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
postMessage({ error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -9,6 +9,11 @@ td {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.container{
|
||||||
|
width: 95vw !important;
|
||||||
|
max-width: 1680px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nb-container {
|
.nb-container {
|
||||||
padding-top: 75px;
|
padding-top: 75px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
+55
-60
@@ -1,46 +1,35 @@
|
|||||||
/* 屏幕适配 */
|
/* 屏幕适配 */
|
||||||
@media only screen and (min-width:1200px) {
|
|
||||||
.ui.container {
|
|
||||||
width:95% !important;
|
|
||||||
font-size: 90% !important;
|
|
||||||
max-width: 1300px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media only screen and (max-width:767px) {
|
@media only screen and (max-width:767px) {
|
||||||
.ui.card>.content>.header:not(.ui),.ui.cards>.card>.content>.header:not(.ui) {
|
.ui.card>.content>.header:not(.ui),
|
||||||
margin-top:0.4em !important;
|
.ui.cards>.card>.content>.header:not(.ui) {
|
||||||
|
margin-top: 0.4em !important;
|
||||||
}
|
}
|
||||||
.ui.menu .item>img:not(.ui){
|
|
||||||
|
.ui.menu .item>img:not(.ui) {
|
||||||
width: 2.2rem;
|
width: 2.2rem;
|
||||||
}
|
}
|
||||||
.ui.menu .item:before{
|
|
||||||
width:0.5px;
|
.ui.menu .item:before {
|
||||||
|
width: 0.5px;
|
||||||
}
|
}
|
||||||
.ui.menu .item{
|
|
||||||
|
.ui.menu .item {
|
||||||
padding: 0.9rem 0.55rem;
|
padding: 0.9rem 0.55rem;
|
||||||
}
|
}
|
||||||
.ui.large.menu{
|
|
||||||
|
.ui.large.menu {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i.icon {
|
i.icon {
|
||||||
color:#000;
|
color: #000;
|
||||||
width:1.2em !important;
|
width: 1.2em !important;
|
||||||
}
|
|
||||||
i.fi {
|
|
||||||
width:0.9em;
|
|
||||||
margin:0px 6px 0px 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
i.fi {
|
||||||
content:" " !important;
|
width: 0.9em;
|
||||||
background:fixed !important;
|
margin: 0px 6px 0px 2px;
|
||||||
z-index:-1 !important;
|
|
||||||
top:0 !important;
|
|
||||||
right:0 !important;
|
|
||||||
bottom:0 !important;
|
|
||||||
left:0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@@ -55,6 +44,11 @@ td {
|
|||||||
margin-bottom: -47px;
|
margin-bottom: -47px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.container {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 1680px !important;
|
||||||
|
}
|
||||||
|
|
||||||
#app .ui.fluid.accordion {
|
#app .ui.fluid.accordion {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -65,79 +59,80 @@ td {
|
|||||||
padding-top: unset;
|
padding-top: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login.nb-container > .grid {
|
.login.nb-container>.grid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login.nb-container > .grid .column {
|
.login.nb-container>.grid .column {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.menu .item-right:before{
|
.ui.menu .item-right:before {
|
||||||
width:0px;
|
width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .flag {
|
.status.cards .flag {
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .header > .info.icon {
|
.status.cards .header>.info.icon {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.grid {
|
.ui.grid {
|
||||||
margin-bottom:-0.5em
|
margin-bottom: -0.5em
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.card>.content>.header:not(.ui), .ui.cards>.card>.content>.header:not(.ui){
|
.ui.card>.content>.header:not(.ui),
|
||||||
|
.ui.cards>.card>.content>.header:not(.ui) {
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .wide.column {
|
.status.cards .wide.column {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
height:2.3rem !important;
|
height: 2.3rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .wide.column:nth-child(1) {
|
.status.cards .wide.column:nth-child(1) {
|
||||||
margin-top:1.2rem !important;
|
margin-top: 1.2rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .wide.column:nth-child(2) {
|
.status.cards .wide.column:nth-child(2) {
|
||||||
margin-top:1.2rem !important;
|
margin-top: 1.2rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .three.wide.column {
|
.status.cards .three.wide.column {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 22%!important;
|
width: 22% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .thirteen.wide.column{
|
.status.cards .thirteen.wide.column {
|
||||||
width: 78%!important;
|
width: 78% !important;
|
||||||
padding-left:0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .description {
|
.status.cards .description {
|
||||||
padding-bottom:0 !important;
|
padding-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .flag {
|
.status.cards .flag {
|
||||||
margin-right:0.5rem !important;
|
margin-right: 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .header > .info.icon {
|
.status.cards .header>.info.icon {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right:0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.popup:before {
|
.ui.popup:before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.closePopup{
|
.closePopup {
|
||||||
color:rgb(10, 148, 242) !important;
|
color: rgb(10, 148, 242) !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 7px;
|
top: 7px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
@@ -150,26 +145,26 @@ td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui.content {
|
.ui.content {
|
||||||
margin:0 !important;
|
margin: 0 !important;
|
||||||
padding:1em !important;
|
padding: 1em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .ui.content.popup {
|
.status.cards .ui.content.popup {
|
||||||
min-width:calc(100%)!important;
|
min-width: calc(100%) !important;
|
||||||
line-height:2rem !important;
|
line-height: 2rem !important;
|
||||||
border-radius:5px !important;
|
border-radius: 5px !important;
|
||||||
border:1px solid transparent !important;
|
border: 1px solid transparent !important;
|
||||||
font-family:Arial,Helvetica,sans-serif !important;
|
font-family: Arial, Helvetica, sans-serif !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.cards .outline.icon {
|
.status.cards .outline.icon {
|
||||||
margin-right:1px !important;
|
margin-right: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.progress .bar {
|
.ui.progress .bar {
|
||||||
min-width:1.8em !important;
|
min-width: 1.8em !important;
|
||||||
border-radius:5px !important;
|
border-radius: 5px !important;
|
||||||
line-height:1.65em !important;
|
line-height: 1.65em !important;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 0.4em;
|
padding-right: 0.4em;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
@@ -182,7 +177,7 @@ td {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-status .delay-today > i {
|
.service-status .delay-today>i {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+28
-24
@@ -1,6 +1,6 @@
|
|||||||
body[theme="dark"] {
|
body[theme="dark"] {
|
||||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
background: rgba(49, 54, 59, 1);
|
background-color: rgba(49, 54, 59, 1);
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,7 +9,6 @@ body[theme="dark"] .navbar .navbar-brand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .dropdown-menu {
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
list-style-image: initial;
|
|
||||||
background-color: rgba(23, 26, 30, 1);
|
background-color: rgba(23, 26, 30, 1);
|
||||||
border-color: rgba(49, 54, 59, 1);
|
border-color: rgba(49, 54, 59, 1);
|
||||||
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
||||||
@@ -29,9 +28,9 @@ body[theme="dark"] .navbar .navbar-nav > .open > a:focus,
|
|||||||
body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
|
body[theme="dark"] .navbar .navbar-nav > .open > a:hover,
|
||||||
body[theme="dark"] .navbar .navbar-nav > .active > a,
|
body[theme="dark"] .navbar .navbar-nav > .active > a,
|
||||||
body[theme="dark"] .navbar .navbar-nav > .open > a{
|
body[theme="dark"] .navbar .navbar-nav > .open > a{
|
||||||
background-image: none !important;
|
background-image: none;
|
||||||
box-shadow: none !important;
|
box-shadow: none;
|
||||||
background-color: transparent !important;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
||||||
@@ -39,7 +38,6 @@ body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .content {
|
body[theme="dark"] .content {
|
||||||
background-image: initial;
|
|
||||||
background-color: rgba(28, 29, 38, 1);
|
background-color: rgba(28, 29, 38, 1);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
@@ -48,7 +46,6 @@ body[theme="dark"] .content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .table {
|
body[theme="dark"] .table {
|
||||||
background-image: initial;
|
|
||||||
background-color: rgba(28, 29, 38, 1);
|
background-color: rgba(28, 29, 38, 1);
|
||||||
border-color: #545b5e;
|
border-color: #545b5e;
|
||||||
}
|
}
|
||||||
@@ -69,7 +66,6 @@ body[theme="dark"] .table-striped tbody > tr.even > th {
|
|||||||
body[theme="dark"] .table-striped tbody > tr,
|
body[theme="dark"] .table-striped tbody > tr,
|
||||||
body[theme="dark"] .table-striped tbody > tr.odd > td,
|
body[theme="dark"] .table-striped tbody > tr.odd > td,
|
||||||
body[theme="dark"] .table-striped tbody > tr.odd > th {
|
body[theme="dark"] .table-striped tbody > tr.odd > th {
|
||||||
/* background-color: rgba(28, 29, 38, 1); 282B2E*/
|
|
||||||
background-color: rgba(34, 35, 46, 1);
|
background-color: rgba(34, 35, 46, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +79,6 @@ body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
|
|||||||
|
|
||||||
/* expandRow展开部分样式 */
|
/* expandRow展开部分样式 */
|
||||||
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
|
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
/* background-color: rgba(28, 29, 38, 1); 282B2E*/
|
|
||||||
background-color: rgba(34, 35, 46, 1);
|
background-color: rgba(34, 35, 46, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,35 +97,35 @@ body[theme="dark"] .progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-bar-success {
|
body[theme="dark"] .progress-bar-success {
|
||||||
background-image: linear-gradient(#4d853a 0, #367e36 100%);
|
background-image: linear-gradient(to right, rgba(77, 133, 58, 1) 0, rgba(54, 126, 54, 1) 100%);
|
||||||
}
|
|
||||||
|
|
||||||
body[theme="dark"] .progress-offline .progress-bar-success {
|
|
||||||
background-image: linear-gradient(#808080 0, #808080 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-bar-warning {
|
body[theme="dark"] .progress-bar-warning {
|
||||||
background-image: linear-gradient(#995f0d 0, #b5710f 100%);
|
background-image: linear-gradient(to right, rgba(153, 95, 13, 1) 0, rgba(181, 113, 15, 1) 100%);
|
||||||
}
|
|
||||||
|
|
||||||
body[theme="dark"] .progress-offline .progress-bar-warning {
|
|
||||||
background-image: linear-gradient(#808080 0, #808080 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-bar-danger {
|
body[theme="dark"] .progress-bar-danger {
|
||||||
background-image: linear-gradient(#942320 0, #a12623 100%);
|
background-image: linear-gradient(to right, rgba(145, 35, 32, 1) 0, rgba(161, 38, 35, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-offline .progress-bar-success {
|
||||||
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-offline .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .progress-offline .progress-bar-danger {
|
body[theme="dark"] .progress-offline .progress-bar-danger {
|
||||||
background-image: linear-gradient(#808080 0, #808080 100%);
|
background-image: linear-gradient(rgba(128, 128, 128, 1) 0, rgba(128, 128, 128, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .node-cell.status .status-icon.online {
|
body[theme="dark"] .node-cell.status .status-icon.online {
|
||||||
background-image: linear-gradient(#4d853a 0, #367e36 100%);
|
background-image: linear-gradient(rgba(77, 133, 58, 1) 0, rgba(54, 126, 54, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .node-cell.status .status-icon.offline {
|
body[theme="dark"] .node-cell.status .status-icon.offline {
|
||||||
background-image: linear-gradient(#9b2522 0, #9b2522 100%);
|
background-image: linear-gradient(rgba(155, 37, 34, 1) 0, rgba(161, 38, 35, 1) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .service-status .danger {
|
body[theme="dark"] .service-status .danger {
|
||||||
@@ -160,15 +155,24 @@ body[theme="dark"] .modal-content{
|
|||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] #earthChartBox .modal-content{
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
body[theme="dark"] .modal-header{
|
body[theme="dark"] .modal-header{
|
||||||
border-bottom-color:#080808;
|
border-bottom-color:#080808;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .modal-header i{
|
body[theme="dark"] .modal-header i.xclose{
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
body[theme="dark"] .toolbox i{
|
body[theme="dark"] .toolbox i{
|
||||||
color: rgba(241, 241, 241, 1);
|
color: rgba(241, 241, 241, 1);
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .network-box .network-box-header{
|
||||||
|
border-bottom: 1px solid rgba(110, 112, 121, 0.25);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
body[theme="dark"]::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url(/static/theme-server-status/img/bg.jpg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar {
|
||||||
|
/** 顶部导航条 背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-brand {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu {
|
||||||
|
/** 二级导航下拉 背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.85);
|
||||||
|
border-top: none;
|
||||||
|
border-color: #31363b;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a {
|
||||||
|
color: #c8c3bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a:focus,
|
||||||
|
body[theme="dark"] .navbar .dropdown-menu > li > a:hover {
|
||||||
|
/** 二级导航鼠标悬停选中背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.95);
|
||||||
|
background-image: linear-gradient(#1c1d26 0, #1c1d26 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .navbar .navbar-nav .open .dropdown-menu > li > a {
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table,
|
||||||
|
body[theme="dark"] .table-condensed > tbody > tr,
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr,
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > td,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > th,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > td,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > th,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > td:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.even > th:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > td:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.odd > th:hover,
|
||||||
|
body[theme="dark"] .table-striped tbody > tr.expandRow:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .content {
|
||||||
|
/** 主box 背景 **/
|
||||||
|
background-color: rgba(28, 29, 38, 0.8);
|
||||||
|
border: none;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
-webkit-box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-tag > th,
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-tag > th:before {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > tbody > tr > td:before,
|
||||||
|
body[theme="dark"] .table > tfoot > tr > td:before,
|
||||||
|
body[theme="dark"] .table > thead > tr > td:before,
|
||||||
|
body[theme="dark"] .table > thead > tr.node-group-cell > th:before{
|
||||||
|
/** border-bottom 颜色 **/
|
||||||
|
background-color: rgba(155, 155, 155, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table-hover > tbody > tr:not(.expandRow):hover > td {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expandRow展开部分样式 */
|
||||||
|
body[theme="dark"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
|
body[theme="dark"] .progress {
|
||||||
|
background-image: none;
|
||||||
|
background-color: rgba(255, 255, 255, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-success {
|
||||||
|
background-image: linear-gradient(to right, rgba(77, 133, 58, 0.9) 0, rgba(54, 126, 54, 0.9) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(to right, rgba(153, 95, 13, 0.9) 0, rgba(181, 113, 15, 0.9) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="dark"] .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(to right, rgba(145, 35, 32, 0.8) 0, rgba(161, 38, 35, 0.8) 100%);
|
||||||
|
}
|
||||||
+108
-44
File diff suppressed because one or more lines are too long
@@ -0,0 +1,134 @@
|
|||||||
|
body[theme="light"]::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url(/static/theme-server-status/img/bg.jpg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] {
|
||||||
|
background-image: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"]::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar {
|
||||||
|
/** 顶部导航条 背景 **/
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-bottom: 1px #cecece solid;
|
||||||
|
box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .dropdown-menu {
|
||||||
|
/** 二级导航下拉 **/
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .dropdown-menu > li > a:focus,
|
||||||
|
body[theme="light"] .navbar .dropdown-menu > li > a:hover{
|
||||||
|
/** 二级导航鼠标悬停选中背景 **/
|
||||||
|
background-color: rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-collapse{
|
||||||
|
border-color:rgba(0, 0, 0, 0.085);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-toggle .icon-bar{
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .navbar .navbar-toggle:focus,
|
||||||
|
body[theme="light"] .navbar .navbar-toggle:hover {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table,
|
||||||
|
body[theme="light"] .table-condensed > tbody > tr,
|
||||||
|
body[theme="light"] .table-hover > tbody > tr,
|
||||||
|
body[theme="light"] .table-hover > tbody > tr:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > td,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > th,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > td,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > th,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > td:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.even > th:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > td:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.odd > th:hover,
|
||||||
|
body[theme="light"] .table-striped tbody > tr.expandRow:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .content {
|
||||||
|
/** 主box 背景 **/
|
||||||
|
background-color: rgba(245, 245, 245, 0.8);
|
||||||
|
border: none;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0.625em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-tag > th,
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-tag > th:before {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > tbody > tr > td:before,
|
||||||
|
body[theme="light"] .table > tfoot > tr > td:before,
|
||||||
|
body[theme="light"] .table > thead > tr > td:before,
|
||||||
|
body[theme="light"] .table > thead > tr.node-group-cell > th:before{
|
||||||
|
/** border-bottom 颜色 **/
|
||||||
|
background-color: rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table-hover > tbody > tr:not(.expandRow):hover > td {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expandRow展开部分样式 */
|
||||||
|
body[theme="light"] .table > tbody > tr.expandRow.odd > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .table > tbody > tr.expandRow.even > td:before{
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
/* expandRow展开部分样式结束 */
|
||||||
|
|
||||||
|
body[theme="light"] .progress {
|
||||||
|
background-image: none;
|
||||||
|
background-color: rgba(0, 0, 0, 0.015);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-success {
|
||||||
|
background-image: linear-gradient(to right, rgba(50, 205, 50, 0.6) 0, rgba(68, 157, 68, 0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-warning {
|
||||||
|
background-image: linear-gradient(to right, rgba(236, 151, 31, 0.6) 0, rgba(255, 125, 80, 0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .progress-bar-danger {
|
||||||
|
background-image: linear-gradient(to right, rgba(255, 70, 0, 0.6) 0, rgba(201, 48, 44,0.6) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[theme="light"] .toolbox i{
|
||||||
|
background-color: rgba(255, 255, 255, .55);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 767px) {
|
||||||
|
body[theme="light"] .navbar .navbar-nav .open .dropdown-menu {
|
||||||
|
background-color: rgba(235, 235, 235, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
+177
-44
@@ -3,16 +3,26 @@ body {
|
|||||||
padding-bottom: 30px !important;
|
padding-bottom: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container-fluid{
|
||||||
|
max-width: 1680px;
|
||||||
|
width: 95vw;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-fluid::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nezha {
|
||||||
|
min-height: calc(100vh - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
/* 导航部分 开始*/
|
/* 导航部分 开始*/
|
||||||
.navbar {
|
.navbar {
|
||||||
min-height: 40px !important;
|
min-height: 40px !important;
|
||||||
margin:0 auto;
|
margin:0 auto;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
z-index:99999999;
|
||||||
|
|
||||||
.navbar .container{
|
|
||||||
max-width: 95vw;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-inverse{
|
.navbar-inverse{
|
||||||
@@ -46,11 +56,6 @@ body {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .node-cell-expand-label {
|
|
||||||
/*margin-right: 5px;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar .dropdown:hover,
|
|
||||||
.navbar .dropdown a{
|
.navbar .dropdown a{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -75,12 +80,10 @@ body {
|
|||||||
.navbar .navbar-collapse{
|
.navbar .navbar-collapse{
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 导航部分 结束 */
|
/* 导航部分 结束 */
|
||||||
|
|
||||||
|
|
||||||
/* toolbox 开始 */
|
/* toolbox 开始 */
|
||||||
|
|
||||||
.toolbox {
|
.toolbox {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom:20px;
|
bottom:20px;
|
||||||
@@ -121,7 +124,6 @@ body {
|
|||||||
.toolbox .showGoTop i.goTop {
|
.toolbox .showGoTop i.goTop {
|
||||||
font-size: 1.55rem;
|
font-size: 1.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* toolbox 结束 */
|
/* toolbox 结束 */
|
||||||
|
|
||||||
|
|
||||||
@@ -138,12 +140,12 @@ body {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th, .table td {
|
.table-responsive{
|
||||||
text-align: left;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.accordion-toggle{
|
.table th, .table td {
|
||||||
cursor:pointer;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table > tbody > tr > td,
|
.table > tbody > tr > td,
|
||||||
@@ -171,6 +173,15 @@ tr.accordion-toggle{
|
|||||||
height: 0.7px;
|
height: 0.7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:5px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.accordion-toggle{
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding-bottom:15px;
|
padding-bottom:15px;
|
||||||
@@ -243,7 +254,7 @@ tr.accordion-toggle{
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*加快Agent详情下拉展示速度*/
|
/*agent详情下拉速度*/
|
||||||
.collapsing{
|
.collapsing{
|
||||||
-webkit-transition-property:height,visibility;
|
-webkit-transition-property:height,visibility;
|
||||||
-o-transition-property:height,visibility;
|
-o-transition-property:height,visibility;
|
||||||
@@ -255,13 +266,10 @@ tr.accordion-toggle{
|
|||||||
-o-transition-timing-function: linear;
|
-o-transition-timing-function: linear;
|
||||||
transition-timing-function: linear
|
transition-timing-function: linear
|
||||||
}
|
}
|
||||||
|
|
||||||
/*正文结束*/
|
/*正文结束*/
|
||||||
|
|
||||||
/* 服务页 正文*/
|
/* 服务页 正文*/
|
||||||
.service-status {
|
.service-status {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-status .service-status-th{
|
.service-status .service-status-th{
|
||||||
min-width:60px;
|
min-width:60px;
|
||||||
@@ -307,44 +315,139 @@ tr.accordion-toggle{
|
|||||||
.service-status .tooltip-inner {
|
.service-status .tooltip-inner {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.ping-network-quality {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
/* 服务页 正文结束 */
|
/* 服务页 正文结束 */
|
||||||
|
|
||||||
|
/* 网络页 正文*/
|
||||||
/* 服务器分布图 */
|
.network-box .btn-group.open .dropdown-toggle{
|
||||||
#mapChartBox{
|
box-shadow:unset;
|
||||||
z-index: 999999999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content{
|
.network-box .network-box-header{
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 5px 0px 15px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu {
|
||||||
|
max-height: 22.5em;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index:99999998;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li{
|
||||||
|
height: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.input-group{
|
||||||
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
position: sticky;
|
||||||
pointer-events: auto;
|
top: 0;
|
||||||
|
z-index: 99999997;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.input-group input{
|
||||||
|
width: 100%;
|
||||||
|
height: 2em;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li a {
|
||||||
|
padding: 5px 5px 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.hidden {
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .network-box-header .dropdown-menu li.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .chartTitle {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 18px 0px 15px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-box .chartTitle i.chartCountryCode{
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 12.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网络页 正文结束 */
|
||||||
|
|
||||||
|
/* 地图版服务器分布图 */
|
||||||
|
#mapChartBox{
|
||||||
|
z-index: 999999999;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-open .modal{
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-dialog-centered{
|
.modal-dialog-centered{
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 1134px;
|
width: 100%;
|
||||||
max-width: 1134px;
|
max-width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center; /* 垂直居中 */
|
align-items: center; /* 垂直居中 */
|
||||||
justify-content: center; /* 水平居中 */
|
justify-content: center; /* 水平居中 */
|
||||||
min-height: calc(100%) /* 确保模态框占据整个视口高度 */
|
min-height: calc(100%) /* 确保模态框占据整个视口高度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
pointer-events: auto;
|
||||||
|
border-radius: unset;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-header{
|
.modal-header{
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header i{
|
.modal-header h4{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header i.xclose{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 2em;
|
font-size: 30px;
|
||||||
top: 13px;
|
top: 12px;
|
||||||
right: 13px;
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body{
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.template-item, a.cdn-item{
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer{
|
footer{
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer p{
|
footer p{
|
||||||
@@ -356,21 +459,39 @@ footer p{
|
|||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:0px 0px 2px 0px;
|
||||||
|
}
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding-bottom:6px;
|
padding-bottom:6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 彩虹旗 */
|
||||||
|
.fi-rb {
|
||||||
|
background-image: url(/static/theme-server-status/img/rb.png);
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 767px) {
|
@media only screen and (max-width: 767px) {
|
||||||
body {
|
body {
|
||||||
font-size: 10px !important;
|
font-size: 10px !important;
|
||||||
padding-top:60px !important;
|
padding-top:60px !important;
|
||||||
}
|
}
|
||||||
|
.nezha {
|
||||||
|
min-height: calc(74.5vh);
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
footer{
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.cycle-transfer-stats .dataTables_wrapper{
|
||||||
|
margin-bottom: -10px;
|
||||||
|
}
|
||||||
.navbar .navbar-nav .open .dropdown-menu>li>a {
|
.navbar .navbar-nav .open .dropdown-menu>li>a {
|
||||||
color: #f1f1f1;
|
color: #f1f1f1;
|
||||||
}
|
}
|
||||||
@@ -405,10 +526,17 @@ footer p{
|
|||||||
.accordian-body{
|
.accordian-body{
|
||||||
margin: 5px 0px 5px 10px;
|
margin: 5px 0px 5px 10px;
|
||||||
}
|
}
|
||||||
|
table.monitor-detail{
|
||||||
|
margin:0px 0px 2px 0px;
|
||||||
|
}
|
||||||
|
.table .network-thead-tr th,
|
||||||
.table .node-group-tag th{
|
.table .node-group-tag th{
|
||||||
font-size:16px;
|
font-size:14px;
|
||||||
padding-bottom:6px;
|
padding-bottom:6px;
|
||||||
}
|
}
|
||||||
|
.table tr.network td{
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
.service-status .service-status-th{
|
.service-status .service-status-th{
|
||||||
min-width:30px;
|
min-width:30px;
|
||||||
}
|
}
|
||||||
@@ -432,15 +560,20 @@ footer p{
|
|||||||
.toolbox {
|
.toolbox {
|
||||||
right: 18px;
|
right: 18px;
|
||||||
}
|
}
|
||||||
.modal-dialog-centered{
|
td.ping-network-quality {
|
||||||
width: 95%;
|
width: 110px;
|
||||||
}
|
}
|
||||||
.modal-title{
|
.network-box .network-box-header {
|
||||||
line-height: 0.5;
|
margin: 8px 0px 0px 8px;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.modal-header i{
|
.network-box .chartTitle {
|
||||||
top: 8px;
|
font-size: 16px;
|
||||||
right: 8px;
|
margin: 10px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
.network-box .chartTitle i.chartCountryCode{
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 12.5%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
-326
@@ -1,326 +0,0 @@
|
|||||||
/**
|
|
||||||
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
|
||||||
*/
|
|
||||||
;(function(window, document) {
|
|
||||||
/*jshint evil:true */
|
|
||||||
/** version */
|
|
||||||
var version = '3.7.3';
|
|
||||||
|
|
||||||
/** Preset options */
|
|
||||||
var options = window.html5 || {};
|
|
||||||
|
|
||||||
/** Used to skip problem elements */
|
|
||||||
var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
|
|
||||||
|
|
||||||
/** Not all elements can be cloned in IE **/
|
|
||||||
var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
|
|
||||||
|
|
||||||
/** Detect whether the browser supports default html5 styles */
|
|
||||||
var supportsHtml5Styles;
|
|
||||||
|
|
||||||
/** Name of the expando, to work with multiple documents or to re-shiv one document */
|
|
||||||
var expando = '_html5shiv';
|
|
||||||
|
|
||||||
/** The id for the documents expando */
|
|
||||||
var expanID = 0;
|
|
||||||
|
|
||||||
/** Cached data for each document */
|
|
||||||
var expandoData = {};
|
|
||||||
|
|
||||||
/** Detect whether the browser supports unknown elements */
|
|
||||||
var supportsUnknownElements;
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
try {
|
|
||||||
var a = document.createElement('a');
|
|
||||||
a.innerHTML = '<xyz></xyz>';
|
|
||||||
//if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
|
|
||||||
supportsHtml5Styles = ('hidden' in a);
|
|
||||||
|
|
||||||
supportsUnknownElements = a.childNodes.length == 1 || (function() {
|
|
||||||
// assign a false positive if unable to shiv
|
|
||||||
(document.createElement)('a');
|
|
||||||
var frag = document.createDocumentFragment();
|
|
||||||
return (
|
|
||||||
typeof frag.cloneNode == 'undefined' ||
|
|
||||||
typeof frag.createDocumentFragment == 'undefined' ||
|
|
||||||
typeof frag.createElement == 'undefined'
|
|
||||||
);
|
|
||||||
}());
|
|
||||||
} catch(e) {
|
|
||||||
// assign a false positive if detection fails => unable to shiv
|
|
||||||
supportsHtml5Styles = true;
|
|
||||||
supportsUnknownElements = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a style sheet with the given CSS text and adds it to the document.
|
|
||||||
* @private
|
|
||||||
* @param {Document} ownerDocument The document.
|
|
||||||
* @param {String} cssText The CSS text.
|
|
||||||
* @returns {StyleSheet} The style element.
|
|
||||||
*/
|
|
||||||
function addStyleSheet(ownerDocument, cssText) {
|
|
||||||
var p = ownerDocument.createElement('p'),
|
|
||||||
parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
|
|
||||||
|
|
||||||
p.innerHTML = 'x<style>' + cssText + '</style>';
|
|
||||||
return parent.insertBefore(p.lastChild, parent.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of `html5.elements` as an array.
|
|
||||||
* @private
|
|
||||||
* @returns {Array} An array of shived element node names.
|
|
||||||
*/
|
|
||||||
function getElements() {
|
|
||||||
var elements = html5.elements;
|
|
||||||
return typeof elements == 'string' ? elements.split(' ') : elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends the built-in list of html5 elements
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {String|Array} newElements whitespace separated list or array of new element names to shiv
|
|
||||||
* @param {Document} ownerDocument The context document.
|
|
||||||
*/
|
|
||||||
function addElements(newElements, ownerDocument) {
|
|
||||||
var elements = html5.elements;
|
|
||||||
if(typeof elements != 'string'){
|
|
||||||
elements = elements.join(' ');
|
|
||||||
}
|
|
||||||
if(typeof newElements != 'string'){
|
|
||||||
newElements = newElements.join(' ');
|
|
||||||
}
|
|
||||||
html5.elements = elements +' '+ newElements;
|
|
||||||
shivDocument(ownerDocument);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data associated to the given document
|
|
||||||
* @private
|
|
||||||
* @param {Document} ownerDocument The document.
|
|
||||||
* @returns {Object} An object of data.
|
|
||||||
*/
|
|
||||||
function getExpandoData(ownerDocument) {
|
|
||||||
var data = expandoData[ownerDocument[expando]];
|
|
||||||
if (!data) {
|
|
||||||
data = {};
|
|
||||||
expanID++;
|
|
||||||
ownerDocument[expando] = expanID;
|
|
||||||
expandoData[expanID] = data;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a shived element for the given nodeName and document
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {String} nodeName name of the element
|
|
||||||
* @param {Document|DocumentFragment} ownerDocument The context document.
|
|
||||||
* @returns {Object} The shived element.
|
|
||||||
*/
|
|
||||||
function createElement(nodeName, ownerDocument, data){
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
if(supportsUnknownElements){
|
|
||||||
return ownerDocument.createElement(nodeName);
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
data = getExpandoData(ownerDocument);
|
|
||||||
}
|
|
||||||
var node;
|
|
||||||
|
|
||||||
if (data.cache[nodeName]) {
|
|
||||||
node = data.cache[nodeName].cloneNode();
|
|
||||||
} else if (saveClones.test(nodeName)) {
|
|
||||||
node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
|
|
||||||
} else {
|
|
||||||
node = data.createElem(nodeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid adding some elements to fragments in IE < 9 because
|
|
||||||
// * Attributes like `name` or `type` cannot be set/changed once an element
|
|
||||||
// is inserted into a document/fragment
|
|
||||||
// * Link elements with `src` attributes that are inaccessible, as with
|
|
||||||
// a 403 response, will cause the tab/window to crash
|
|
||||||
// * Script elements appended to fragments will execute when their `src`
|
|
||||||
// or `text` property is set
|
|
||||||
return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a shived DocumentFragment for the given document
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {Document} ownerDocument The context document.
|
|
||||||
* @returns {Object} The shived DocumentFragment.
|
|
||||||
*/
|
|
||||||
function createDocumentFragment(ownerDocument, data){
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
if(supportsUnknownElements){
|
|
||||||
return ownerDocument.createDocumentFragment();
|
|
||||||
}
|
|
||||||
data = data || getExpandoData(ownerDocument);
|
|
||||||
var clone = data.frag.cloneNode(),
|
|
||||||
i = 0,
|
|
||||||
elems = getElements(),
|
|
||||||
l = elems.length;
|
|
||||||
for(;i<l;i++){
|
|
||||||
clone.createElement(elems[i]);
|
|
||||||
}
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
|
|
||||||
* @private
|
|
||||||
* @param {Document|DocumentFragment} ownerDocument The document.
|
|
||||||
* @param {Object} data of the document.
|
|
||||||
*/
|
|
||||||
function shivMethods(ownerDocument, data) {
|
|
||||||
if (!data.cache) {
|
|
||||||
data.cache = {};
|
|
||||||
data.createElem = ownerDocument.createElement;
|
|
||||||
data.createFrag = ownerDocument.createDocumentFragment;
|
|
||||||
data.frag = data.createFrag();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ownerDocument.createElement = function(nodeName) {
|
|
||||||
//abort shiv
|
|
||||||
if (!html5.shivMethods) {
|
|
||||||
return data.createElem(nodeName);
|
|
||||||
}
|
|
||||||
return createElement(nodeName, ownerDocument, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
|
|
||||||
'var n=f.cloneNode(),c=n.createElement;' +
|
|
||||||
'h.shivMethods&&(' +
|
|
||||||
// unroll the `createElement` calls
|
|
||||||
getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
|
|
||||||
data.createElem(nodeName);
|
|
||||||
data.frag.createElement(nodeName);
|
|
||||||
return 'c("' + nodeName + '")';
|
|
||||||
}) +
|
|
||||||
');return n}'
|
|
||||||
)(html5, data.frag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shivs the given document.
|
|
||||||
* @memberOf html5
|
|
||||||
* @param {Document} ownerDocument The document to shiv.
|
|
||||||
* @returns {Document} The shived document.
|
|
||||||
*/
|
|
||||||
function shivDocument(ownerDocument) {
|
|
||||||
if (!ownerDocument) {
|
|
||||||
ownerDocument = document;
|
|
||||||
}
|
|
||||||
var data = getExpandoData(ownerDocument);
|
|
||||||
|
|
||||||
if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
|
|
||||||
data.hasCSS = !!addStyleSheet(ownerDocument,
|
|
||||||
// corrects block display not defined in IE6/7/8/9
|
|
||||||
'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
|
|
||||||
// adds styling not present in IE6/7/8/9
|
|
||||||
'mark{background:#FF0;color:#000}' +
|
|
||||||
// hides non-rendered elements
|
|
||||||
'template{display:none}'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!supportsUnknownElements) {
|
|
||||||
shivMethods(ownerDocument, data);
|
|
||||||
}
|
|
||||||
return ownerDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `html5` object is exposed so that more elements can be shived and
|
|
||||||
* existing shiving can be detected on iframes.
|
|
||||||
* @type Object
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* // options can be changed before the script is included
|
|
||||||
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
|
|
||||||
*/
|
|
||||||
var html5 = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array or space separated string of node names of the elements to shiv.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Array|String
|
|
||||||
*/
|
|
||||||
'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* current version of html5shiv
|
|
||||||
*/
|
|
||||||
'version': version,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag to indicate that the HTML5 style sheet should be inserted.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Boolean
|
|
||||||
*/
|
|
||||||
'shivCSS': (options.shivCSS !== false),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is equal to true if a browser supports creating unknown/HTML5 elements
|
|
||||||
* @memberOf html5
|
|
||||||
* @type boolean
|
|
||||||
*/
|
|
||||||
'supportsUnknownElements': supportsUnknownElements,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
|
|
||||||
* methods should be overwritten.
|
|
||||||
* @memberOf html5
|
|
||||||
* @type Boolean
|
|
||||||
*/
|
|
||||||
'shivMethods': (options.shivMethods !== false),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A string to describe the type of `html5` object ("default" or "default print").
|
|
||||||
* @memberOf html5
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
'type': 'default',
|
|
||||||
|
|
||||||
// shivs the document according to the specified `html5` object options
|
|
||||||
'shivDocument': shivDocument,
|
|
||||||
|
|
||||||
//creates a shived element
|
|
||||||
createElement: createElement,
|
|
||||||
|
|
||||||
//creates a shived documentFragment
|
|
||||||
createDocumentFragment: createDocumentFragment,
|
|
||||||
|
|
||||||
//extends list of elements
|
|
||||||
addElements: addElements
|
|
||||||
};
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
// expose html5
|
|
||||||
window.html5 = html5;
|
|
||||||
|
|
||||||
// shiv the document
|
|
||||||
shivDocument(document);
|
|
||||||
|
|
||||||
if(typeof module == 'object' && module.exports){
|
|
||||||
module.exports = html5;
|
|
||||||
}
|
|
||||||
|
|
||||||
}(typeof window !== "undefined" ? window : this, document));
|
|
||||||
+29
-3
@@ -1,24 +1,31 @@
|
|||||||
const mixinsVue = {
|
const mixinsVue = {
|
||||||
data: {
|
data: {
|
||||||
cache: [],
|
cache: [],
|
||||||
|
isMobile: false,
|
||||||
theme: "light",
|
theme: "light",
|
||||||
isSystemTheme: false,
|
isSystemTheme: false,
|
||||||
showGroup: false,
|
showGroup: false,
|
||||||
showGoTop: false,
|
showGoTop: false,
|
||||||
|
showTools: false,
|
||||||
preferredTemplate: null,
|
preferredTemplate: null,
|
||||||
isMobile: false,
|
semiTransparent: false,
|
||||||
staticUrl: '/static/theme-server-status',
|
staticUrl: '/static/theme-server-status',
|
||||||
adaptedTemplates: [
|
adaptedTemplates: [
|
||||||
{ key: 'default', name: 'Default', icon: 'th large' },
|
{ key: 'default', name: 'Default', icon: 'th large' },
|
||||||
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
||||||
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
|
{ key: 'server-status', name: 'ServerStatus', icon: 'list' }
|
||||||
]
|
],
|
||||||
|
colors: [],
|
||||||
|
colorsDark: ['#4992FF', '#08C091', '#FDDD5F', '#FF6E76', '#58D9F9', '#7CFFB2', '#FF8A44', '#8D48E3', '#DD79FF', '#5470C6', '#3BA272', '#FAC758', '#EE6666', '#72C0DE', '#91CC76', '#FB8352', '#9A60B4', '#EA7BCC'],
|
||||||
|
colorsLight: ['#5470C6', '#3BA272', '#FAC758', '#EE6666', '#72C0DE', '#91CC76', '#FB8352', '#9A60B4', '#EA7BCC', '#4992FF', '#08C091', '#FDDD5F', '#FF6E76', '#58D9F9', '#7CFFB2', '#FF8A44', '#8D48E3', '#DD79FF'],
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.isMobile = this.checkIsMobile();
|
this.isMobile = this.checkIsMobile();
|
||||||
this.theme = this.initTheme();
|
this.theme = this.initTheme();
|
||||||
this.showGroup = this.initShowGroup();
|
this.showGroup = this.initShowGroup();
|
||||||
|
this.semiTransparent = this.initSemiTransparent();
|
||||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||||
|
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
@@ -32,6 +39,9 @@ const mixinsVue = {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleShowTools() {
|
||||||
|
this.showTools = !this.showTools;
|
||||||
|
},
|
||||||
initTheme() {
|
initTheme() {
|
||||||
const storedTheme = localStorage.getItem("theme");
|
const storedTheme = localStorage.getItem("theme");
|
||||||
const theme = (storedTheme === 'dark' || storedTheme === 'light') ? storedTheme : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
const theme = (storedTheme === 'dark' || storedTheme === 'light') ? storedTheme : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
@@ -45,7 +55,7 @@ const mixinsVue = {
|
|||||||
// 重新赋值全局调色
|
// 重新赋值全局调色
|
||||||
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
this.colors = this.theme == "dark" ? this.colorsDark : this.colorsLight;
|
||||||
|
|
||||||
if(this.$root.page == 'index') {
|
if(this.$root.page == 'index' || this.$root.page == 'network') {
|
||||||
this.reloadCharts(); // 重新载入echarts图表
|
this.reloadCharts(); // 重新载入echarts图表
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -64,6 +74,21 @@ const mixinsVue = {
|
|||||||
this.$root.initTooltip();
|
this.$root.initTooltip();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
initSemiTransparent() {
|
||||||
|
const storedSemiTransparent = localStorage.getItem("semiTransparent");
|
||||||
|
const semiTransparent = storedSemiTransparent !== null ? JSON.parse(storedSemiTransparent) : false;
|
||||||
|
if (storedSemiTransparent === null) {
|
||||||
|
localStorage.setItem("semiTransparent", semiTransparent);
|
||||||
|
}
|
||||||
|
return semiTransparent;
|
||||||
|
},
|
||||||
|
toggleSemiTransparent(){
|
||||||
|
this.semiTransparent = !this.semiTransparent;
|
||||||
|
localStorage.setItem("semiTransparent", this.semiTransparent);
|
||||||
|
if(this.$root.page == 'index' || this.$root.page == 'network') {
|
||||||
|
this.reloadCharts(); // 重新载入echarts图表
|
||||||
|
}
|
||||||
|
},
|
||||||
updateCookie(name, value) {
|
updateCookie(name, value) {
|
||||||
document.cookie = name + "=" + value +"; path=/";
|
document.cookie = name + "=" + value +"; path=/";
|
||||||
},
|
},
|
||||||
@@ -106,6 +131,7 @@ const mixinsVue = {
|
|||||||
},
|
},
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
this.showGoTop = window.scrollY >= 100;
|
this.showGoTop = window.scrollY >= 100;
|
||||||
|
if(this.showTools) this.showTools = false;
|
||||||
},
|
},
|
||||||
groupingData(data, field) {
|
groupingData(data, field) {
|
||||||
let map = new Map();
|
let map = new Map();
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
|
|
||||||
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
|
|
||||||
* */
|
|
||||||
|
|
||||||
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
|
|
||||||
Vendored
+1
@@ -17,5 +17,6 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
Vendored
+6
-1
@@ -11,7 +11,12 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/dist/semantic.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/main.css?v2022042314">
|
<link rel="stylesheet" type="text/css" href="/static/main.css?v20240813">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/darkmode.css?v20240813">
|
||||||
|
<script>
|
||||||
|
document.documentElement.setAttribute('nz-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
{{if ts .CustomCodeDashboard}} {{.CustomCodeDashboard|safe}} {{end}}
|
||||||
<body>
|
<body>
|
||||||
{{end}}
|
{{end}}
|
||||||
Vendored
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class=" actions">
|
<div class="actions">
|
||||||
<div class="ui negative button">{{tr "Cancel"}}</div>
|
<div class="ui negative button">{{tr "Cancel"}}</div>
|
||||||
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=" actions">
|
<div class="actions">
|
||||||
<div class="ui negative button">{{tr "Cancel"}}</div>
|
<div class="ui negative button">{{tr "Cancel"}}</div>
|
||||||
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class=" actions">
|
<div class="actions">
|
||||||
<div class="ui negative button">{{tr "Cancel"}}</div>
|
<div class="ui negative button">{{tr "Cancel"}}</div>
|
||||||
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
<button class="ui positive nezha-primary-btn right labeled icon button">{{tr "Confirm"}}<i class="checkmark icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{tr "Token"}}</th>
|
<th>{{tr "Token"}}</th>
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
+494
@@ -0,0 +1,494 @@
|
|||||||
|
{{define "dashboard-default/file"}}
|
||||||
|
<!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.0">
|
||||||
|
<title>File List</title>
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css" />
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
margin: 5px 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-app-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-directory {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0 15px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: calc(100% - 100px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="top-app-bar">
|
||||||
|
<mdui-dropdown>
|
||||||
|
<mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
|
||||||
|
<mdui-menu>
|
||||||
|
<mdui-menu-item id="refresh">Refresh</mdui-menu-item>
|
||||||
|
<mdui-menu-item id="copy">Copy path</mdui-menu-item>
|
||||||
|
<mdui-menu-item id="goto">Go to</mdui-menu-item>
|
||||||
|
</mdui-menu>
|
||||||
|
</mdui-dropdown>
|
||||||
|
<span id="current-directory"></span>
|
||||||
|
<mdui-button-icon id="upload" icon="upload"></mdui-button-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mdui-list id="file-list" class="file-list"></mdui-list>
|
||||||
|
|
||||||
|
<mdui-dialog id="error-dialog" headline="Error"
|
||||||
|
description="Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."></mdui-dialog>
|
||||||
|
|
||||||
|
<mdui-dialog id="upd-modal" class="modal">
|
||||||
|
<mdui-linear-progress id="upd-progress"></mdui-linear-progress>
|
||||||
|
</mdui-dialog>
|
||||||
|
|
||||||
|
<mdui-dialog id="goto-dialog" headline="Go to a folder" close-on-overlay-click>
|
||||||
|
<mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
|
||||||
|
<mdui-button id="goto-go" slot="action" variant="text">Go</mdui-button>
|
||||||
|
<mdui-button id="goto-close" slot="action" variant="tonal">Close</mdui-button>
|
||||||
|
</mdui-dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentPath = '/opt/nezha/';
|
||||||
|
let fileName = '';
|
||||||
|
let receivedBuffer = []; // 用于缓存数据块
|
||||||
|
let expectedLength = 0;
|
||||||
|
let receivedLength = 0;
|
||||||
|
let isFirstChunk = true;
|
||||||
|
let isUpCompleted = false;
|
||||||
|
let handleReady = false;
|
||||||
|
let worker;
|
||||||
|
|
||||||
|
function updateDirectoryTitle() {
|
||||||
|
const directoryTitle = document.getElementById('current-directory');
|
||||||
|
directoryTitle.textContent = `${currentPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFileList(items) {
|
||||||
|
const fileListElement = document.getElementById('file-list');
|
||||||
|
fileListElement.innerHTML = '';
|
||||||
|
|
||||||
|
if (currentPath !== '/') {
|
||||||
|
const upItem = document.createElement('mdui-list-item');
|
||||||
|
upItem.className = 'file-item up-directory';
|
||||||
|
upItem.setAttribute('icon', 'arrow_back');
|
||||||
|
upItem.textContent = "..";
|
||||||
|
upItem.onclick = function () {
|
||||||
|
const lastSlashIndex = currentPath.lastIndexOf('/', currentPath.length - 2);
|
||||||
|
currentPath = currentPath.substring(0, lastSlashIndex + 1) || '/';
|
||||||
|
listFile();
|
||||||
|
};
|
||||||
|
fileListElement.appendChild(upItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.sort((a, b) => {
|
||||||
|
if (a.fileType === 'dir' && b.fileType !== 'dir') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.fileType !== 'dir' && b.fileType === 'dir') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
const listItem = document.createElement('mdui-list-item');
|
||||||
|
listItem.className = `file-item ${item.fileType.toLowerCase()}`;
|
||||||
|
listItem.setAttribute('nonclickable', 'true');
|
||||||
|
listItem.setAttribute('icon', 'insert_drive_file');
|
||||||
|
listItem.textContent = `${item.name}`;
|
||||||
|
|
||||||
|
if (item.fileType === 'dir') {
|
||||||
|
listItem.setAttribute('nonclickable', 'false');
|
||||||
|
listItem.setAttribute('icon', 'folder');
|
||||||
|
listItem.style.cursor = 'pointer';
|
||||||
|
listItem.onclick = function () {
|
||||||
|
currentPath += `${item.name}/`;
|
||||||
|
listFile();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const downloadButton = document.createElement('mdui-button-icon');
|
||||||
|
downloadButton.setAttribute('slot', 'end-icon');
|
||||||
|
downloadButton.setAttribute('icon', 'download');
|
||||||
|
downloadButton.onclick = function () {
|
||||||
|
const filePath = currentPath + item.name;
|
||||||
|
fileName = item.name;
|
||||||
|
downloadFile(filePath);
|
||||||
|
};
|
||||||
|
listItem.appendChild(downloadButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileListElement.appendChild(listItem);
|
||||||
|
});
|
||||||
|
updateDirectoryTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUpdState() {
|
||||||
|
receivedBuffer = [];
|
||||||
|
expectedLength = 0;
|
||||||
|
receivedLength = 0;
|
||||||
|
isFirstChunk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFile(filePath) {
|
||||||
|
showUpdModal('d');
|
||||||
|
|
||||||
|
const prefix = new Int8Array([1]); // Request download
|
||||||
|
const filePathMessage = new TextEncoder().encode(filePath);
|
||||||
|
|
||||||
|
const msg = new Int8Array(prefix.length + filePathMessage.length);
|
||||||
|
msg.set(prefix);
|
||||||
|
msg.set(filePathMessage, prefix.length);
|
||||||
|
|
||||||
|
socket.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function listFile() {
|
||||||
|
const prefix = new Int8Array([0]);
|
||||||
|
const resizeMessage = new TextEncoder().encode(currentPath);
|
||||||
|
|
||||||
|
const msg = new Int8Array(prefix.length + resizeMessage.length);
|
||||||
|
msg.set(prefix);
|
||||||
|
msg.set(resizeMessage, prefix.length);
|
||||||
|
|
||||||
|
socket.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFile(file) {
|
||||||
|
showUpdModal('u');
|
||||||
|
|
||||||
|
const chunkSize = 1048576; // 1MB chunk
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
const filePath = `${currentPath}${file.name}`;
|
||||||
|
const fileSize = file.size;
|
||||||
|
const messageType = 2;
|
||||||
|
|
||||||
|
// Build header (type + file size + path)
|
||||||
|
const filePathBytes = new TextEncoder().encode(filePath);
|
||||||
|
const header = new ArrayBuffer(1 + 8 + filePathBytes.length);
|
||||||
|
const headerView = new DataView(header);
|
||||||
|
|
||||||
|
headerView.setUint8(0, messageType);
|
||||||
|
headerView.setBigUint64(1, BigInt(fileSize), false);
|
||||||
|
|
||||||
|
new Uint8Array(header, 9).set(filePathBytes);
|
||||||
|
|
||||||
|
// Send header
|
||||||
|
socket.send(header);
|
||||||
|
|
||||||
|
// Send data chunks
|
||||||
|
while (offset < fileSize) {
|
||||||
|
const chunk = file.slice(offset, offset + chunkSize);
|
||||||
|
const arrayBuffer = await readFileAsArrayBuffer(chunk);
|
||||||
|
socket.send(arrayBuffer);
|
||||||
|
offset += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCompletion = setInterval(() => {
|
||||||
|
if (isUpCompleted) {
|
||||||
|
clearInterval(checkCompletion); // 任务完成后停止检查
|
||||||
|
hideUpdModal();
|
||||||
|
resetUpdState();
|
||||||
|
listFile();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseFileList(arrayBuffer) {
|
||||||
|
const dataView = new DataView(arrayBuffer);
|
||||||
|
const items = [];
|
||||||
|
let offset = 4;
|
||||||
|
|
||||||
|
const pathLength = dataView.getUint32(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
const pathArray = new Uint8Array(arrayBuffer, offset, pathLength);
|
||||||
|
currentPath = new TextDecoder('utf-8').decode(pathArray);
|
||||||
|
offset += pathLength;
|
||||||
|
|
||||||
|
while (offset < dataView.byteLength) {
|
||||||
|
const fileType = dataView.getUint8(offset);
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
const nameLength = dataView.getUint8(offset);
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
if (offset + nameLength > dataView.byteLength) {
|
||||||
|
console.error('Error: Name length exceeds buffer size');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameArray = new Uint8Array(arrayBuffer, offset, nameLength);
|
||||||
|
const name = new TextDecoder('utf-8').decode(nameArray);
|
||||||
|
offset += nameLength;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
fileType: fileType === 0x01 ? 'dir' : 'f',
|
||||||
|
name: name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { items };
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFileAsArrayBuffer(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = () => reject(reader.error);
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function concatenateArrayBuffers(buffers) {
|
||||||
|
let totalLength = 0;
|
||||||
|
buffers.forEach(buf => totalLength += buf.byteLength);
|
||||||
|
|
||||||
|
const result = new Uint8Array(totalLength);
|
||||||
|
let offset = 0;
|
||||||
|
buffers.forEach(buf => {
|
||||||
|
result.set(new Uint8Array(buf), offset);
|
||||||
|
offset += buf.byteLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arraysEqual(a, b) {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyTextToClipboard(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy text to clipboard: ', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleError(errMsg) {
|
||||||
|
try {
|
||||||
|
console.error('Received error: ', errMsg);
|
||||||
|
hideUpdModal();
|
||||||
|
const errorDialog = document.getElementById('error-dialog');
|
||||||
|
errorDialog.open = true;
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.close(1000, 'Closing due to error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error while handling error and closing WebSocket:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUpdModal(operation) {
|
||||||
|
const modal = document.getElementById('upd-modal');
|
||||||
|
modal.open = true;
|
||||||
|
if (operation === 'd') {
|
||||||
|
modal.setAttribute('headline', 'Downloading...');
|
||||||
|
} else if (operation === 'u') {
|
||||||
|
modal.setAttribute('headline', 'Uploading...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideUpdModal() {
|
||||||
|
const modal = document.getElementById('upd-modal');
|
||||||
|
modal.open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForHandleReady() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const checkReady = () => {
|
||||||
|
if (handleReady) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkReady, 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkReady();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/file/' + '{{.SessionID}}');
|
||||||
|
socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
socket.onmessage = async function (event) {
|
||||||
|
try {
|
||||||
|
const arrayBuffer = event.data;
|
||||||
|
|
||||||
|
if (isFirstChunk) {
|
||||||
|
const identifier = new Uint8Array(arrayBuffer, 0, 4);
|
||||||
|
const fileIdentifier = new Uint8Array([0x4E, 0x5A, 0x54, 0x44]); // NZTD
|
||||||
|
const fileNameIdentifier = new Uint8Array([0x4E, 0x5A, 0x46, 0x4E]); // NZFN
|
||||||
|
const errorIdentifier = new Uint8Array([0x4E, 0x45, 0x52, 0x52]); // NERR
|
||||||
|
const completeIdentifier = new Uint8Array([0x4E, 0x5A, 0x55, 0x50]); // NZUP
|
||||||
|
|
||||||
|
if (arraysEqual(identifier, fileIdentifier)) {
|
||||||
|
worker = new Worker('/static/file.js');
|
||||||
|
worker.onmessage = async function (event) {
|
||||||
|
switch (event.data.type) {
|
||||||
|
case 'error':
|
||||||
|
console.error('Error from worker:', event.data.error);
|
||||||
|
break;
|
||||||
|
case 'progress':
|
||||||
|
handleReady = true;
|
||||||
|
break;
|
||||||
|
case 'result':
|
||||||
|
handleReady = false;
|
||||||
|
const url = URL.createObjectURL(event.data.blob);
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.href = url;
|
||||||
|
anchor.download = event.data.fileName;
|
||||||
|
anchor.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
// Delete the file in OPFS
|
||||||
|
window.addEventListener('beforeunload', async () => {
|
||||||
|
await worker.postMessage({ operation: 3, arrayBuffer: null, fileName: event.data.fileName });
|
||||||
|
});
|
||||||
|
|
||||||
|
hideUpdModal();
|
||||||
|
resetUpdState();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await worker.postMessage({ operation: 1, arrayBuffer: arrayBuffer, fileName: fileName });
|
||||||
|
isFirstChunk = false;
|
||||||
|
} else if (arraysEqual(identifier, fileNameIdentifier)) {
|
||||||
|
// List files
|
||||||
|
const { items } = await parseFileList(arrayBuffer);
|
||||||
|
updateFileList(items);
|
||||||
|
return;
|
||||||
|
} else if (arraysEqual(identifier, errorIdentifier)) {
|
||||||
|
// Handle error
|
||||||
|
const errBytes = arrayBuffer.slice(4);
|
||||||
|
const errMsg = new TextDecoder('utf-8').decode(errBytes);
|
||||||
|
await handleError(errMsg);
|
||||||
|
return;
|
||||||
|
} else if (arraysEqual(identifier, completeIdentifier)) {
|
||||||
|
// Upload is completed
|
||||||
|
isUpCompleted = true;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log('Unknown identifier');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await waitForHandleReady();
|
||||||
|
await worker.postMessage({ operation: 2, arrayBuffer: arrayBuffer, fileName: fileName });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing received data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onopen = function (event) {
|
||||||
|
listFile();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function (event) {
|
||||||
|
console.error('WebSocket error:', event);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = function (event) {
|
||||||
|
console.log('WebSocket connection closed:', event);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('refresh').addEventListener('click', listFile);
|
||||||
|
|
||||||
|
document.getElementById('copy').addEventListener('click', async () => {
|
||||||
|
await copyTextToClipboard(currentPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('goto').addEventListener('click', function () {
|
||||||
|
const dialog = document.getElementById('goto-dialog');
|
||||||
|
const textField = document.getElementById('goto-text');
|
||||||
|
const goButton = document.getElementById('goto-go');
|
||||||
|
const closeButton = document.getElementById('goto-close');
|
||||||
|
|
||||||
|
dialog.open = true;
|
||||||
|
|
||||||
|
// Ensure the path ends with a separator
|
||||||
|
const updateText = function (event) {
|
||||||
|
let text = event.target.value;
|
||||||
|
if (!text.endsWith('/')) {
|
||||||
|
text += '/';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGoClick = function () {
|
||||||
|
let text = updateText({ target: textField });
|
||||||
|
currentPath = text;
|
||||||
|
listFile();
|
||||||
|
dialog.open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
textField.removeEventListener('change', updateText);
|
||||||
|
textField.addEventListener('change', updateText);
|
||||||
|
|
||||||
|
goButton.removeEventListener('click', handleGoClick);
|
||||||
|
goButton.addEventListener('click', handleGoClick);
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", () => dialog.open = false);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('upload').addEventListener('click', async function () {
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.style.display = 'none';
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', async function (event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
await uploadFile(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(fileInput);
|
||||||
|
fileInput.click();
|
||||||
|
document.body.removeChild(fileInput);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
{{end}}
|
||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
+2
-2
@@ -10,7 +10,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui very basic table">
|
<table class="ui basic table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><button onclick="checkAllServer()" class="ui mini nezha-primary-btn button">{{tr "SelectAll"}}</button></th>
|
<th><button onclick="checkAllServer()" class="ui mini nezha-primary-btn button">{{tr "SelectAll"}}</button></th>
|
||||||
|
|||||||
+7
-3
@@ -41,6 +41,10 @@
|
|||||||
<label>{{tr "CustomCodes"}}</label>
|
<label>{{tr "CustomCodes"}}</label>
|
||||||
<textarea name="CustomCode">{{.Conf.Site.CustomCode}}</textarea>
|
<textarea name="CustomCode">{{.Conf.Site.CustomCode}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>{{tr "CustomCodesDashboard"}}</label>
|
||||||
|
<textarea name="CustomCodeDashboard">{{.Conf.Site.CustomCodeDashboard}}</textarea>
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{tr "AccessPassword"}}</label>
|
<label>{{tr "AccessPassword"}}</label>
|
||||||
<input type="text" name="ViewPassword" placeholder="" value="{{.Conf.Site.ViewPassword}}">
|
<input type="text" name="ViewPassword" placeholder="" value="{{.Conf.Site.ViewPassword}}">
|
||||||
@@ -128,13 +132,13 @@
|
|||||||
})
|
})
|
||||||
$('.checkbox').checkbox()
|
$('.checkbox').checkbox()
|
||||||
$('#settingForm').find("select[name=Cover]").val({{.Conf.Cover }});
|
$('#settingForm').find("select[name=Cover]").val({{.Conf.Cover }});
|
||||||
{{if .Conf.EnableIPChangeNotification}}
|
{{ if .Conf.EnableIPChangeNotification}}
|
||||||
$('.checkbox.ip-change').checkbox('set checked')
|
$('.checkbox.ip-change').checkbox('set checked')
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{if .Conf.EnablePlainIPInNotification}}
|
{{ if .Conf.EnablePlainIPInNotification}}
|
||||||
$('.checkbox.plain-ip').checkbox('set checked')
|
$('.checkbox.plain-ip').checkbox('set checked')
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{if .Conf.DisableSwitchTemplateInFrontend }}
|
{{ if .Conf.DisableSwitchTemplateInFrontend }}
|
||||||
$('.checkbox.disable-switch-template').checkbox('set checked')
|
$('.checkbox.disable-switch-template').checkbox('set checked')
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+64
-2
@@ -1,6 +1,7 @@
|
|||||||
{{define "dashboard-default/terminal"}}
|
{{define "dashboard-default/terminal"}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{.Conf.Language}}">
|
<html lang="{{.Conf.Language}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
@@ -22,17 +23,53 @@
|
|||||||
body {
|
body {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#file-list-iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 30%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background-color: white;
|
||||||
|
display: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#folder-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body onresize="onResize()">
|
<body onresize="onResize()">
|
||||||
<div id="terminal-container"></div>
|
<div id="terminal-container"></div>
|
||||||
|
<iframe id="file-list-iframe" src=""></iframe>
|
||||||
|
<button id="folder-button">📁</button>
|
||||||
|
|
||||||
<script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
|
<script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
|
||||||
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-attach.js"></script>
|
|
||||||
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-fit.js"></script>
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-fit.js"></script>
|
||||||
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-web-links.js"></script>
|
||||||
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-attach.js"></script>
|
||||||
<script>
|
<script>
|
||||||
let sendResizing = false;
|
let sendResizing = false;
|
||||||
|
|
||||||
function doResize() {
|
function doResize() {
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
|
|
||||||
const w = fitAddon.proposeDimensions();
|
const w = fitAddon.proposeDimensions();
|
||||||
const prefix = new Int8Array([1]);
|
const prefix = new Int8Array([1]);
|
||||||
const resizeMessage = new TextEncoder().encode(JSON.stringify({
|
const resizeMessage = new TextEncoder().encode(JSON.stringify({
|
||||||
@@ -70,10 +107,13 @@
|
|||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
});
|
});
|
||||||
const socket = new WebSocket((window.location.protocol == 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/terminal/' + '{{.SessionID}}');
|
const socket = new WebSocket((window.location.protocol == 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/terminal/' + '{{.SessionID}}');
|
||||||
|
|
||||||
const attachAddon = new AttachAddon.AttachAddon(socket);
|
const attachAddon = new AttachAddon.AttachAddon(socket);
|
||||||
const fitAddon = new FitAddon.FitAddon();
|
|
||||||
term.loadAddon(attachAddon);
|
term.loadAddon(attachAddon);
|
||||||
|
|
||||||
|
const fitAddon = new FitAddon.FitAddon();
|
||||||
term.loadAddon(fitAddon);
|
term.loadAddon(fitAddon);
|
||||||
|
|
||||||
term.open(document.getElementById('terminal-container'));
|
term.open(document.getElementById('terminal-container'));
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
@@ -88,7 +128,29 @@
|
|||||||
socket.onerror = () => {
|
socket.onerror = () => {
|
||||||
alert('{{tr "TerminalConnectionFailed"}}')
|
alert('{{tr "TerminalConnectionFailed"}}')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理文件夹按钮点击事件
|
||||||
|
const folderButton = document.getElementById('folder-button');
|
||||||
|
const fileListIframe = document.getElementById('file-list-iframe');
|
||||||
|
let fileListVisible = false;
|
||||||
|
|
||||||
|
folderButton.addEventListener('click', () => {
|
||||||
|
if (!fileListVisible) {
|
||||||
|
// 显示文件列表
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
id: "{{.ServerID}}"
|
||||||
|
}).toString();
|
||||||
|
fileListIframe.src = `/file?${params}`;
|
||||||
|
fileListIframe.style.display = 'block';
|
||||||
|
fileListVisible = true;
|
||||||
|
} else {
|
||||||
|
// 隐藏文件列表
|
||||||
|
fileListIframe.style.display = 'none';
|
||||||
|
fileListVisible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
+1
-1
@@ -25,7 +25,7 @@
|
|||||||
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
||||||
CPU: @#server.Host.CPU#@<br />
|
CPU: @#server.Host.CPU#@<br />
|
||||||
<template v-if="server.Host.GPU">
|
<template v-if="server.Host.GPU">
|
||||||
GPU: @#server.Host.GPU#@
|
GPU: @#server.Host.GPU#@<br />
|
||||||
</template>
|
</template>
|
||||||
{{tr "DiskUsed"}}:
|
{{tr "DiskUsed"}}:
|
||||||
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
||||||
|
|||||||
+2
-4
@@ -10,14 +10,12 @@
|
|||||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||||
<script>
|
<script>
|
||||||
function showSwitchTemplate(list, currentBackendTheme) {
|
function showSwitchTemplate(list, currentBackendTheme) {
|
||||||
console.log(list, currentBackendTheme);
|
// console.log(list, currentBackendTheme);
|
||||||
console.log("currentBackendTheme:",currentBackendTheme);
|
// console.log("currentBackendTheme:",currentBackendTheme);
|
||||||
}
|
}
|
||||||
showSwitchTemplate({{ .Themes }}, {{ .Conf.Site.Theme }})
|
showSwitchTemplate({{ .Themes }}, {{ .Conf.Site.Theme }})
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+8
-3
@@ -1,6 +1,7 @@
|
|||||||
{{define "theme-default/header"}}
|
{{define "theme-default/header"}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{.Conf.Language}}">
|
<html lang="{{.Conf.Language}}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -13,14 +14,18 @@
|
|||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
|
<link rel="stylesheet" type="text/css" href="/static/semantic-ui-alerts.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/theme-default/css/main.css?v20240616">
|
<link rel="stylesheet" type="text/css" href="/static/theme-default/css/main.css?v20240816">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/darkmode.css?v20240816">
|
||||||
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/semantic.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
||||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||||
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
|
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
|
||||||
|
<script>
|
||||||
|
document.documentElement.setAttribute('nz-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
+2
-5
@@ -240,12 +240,12 @@
|
|||||||
const itemHeight = isMobile ? 10 : 10;
|
const itemHeight = isMobile ? 10 : 10;
|
||||||
const gridLeft = 25;
|
const gridLeft = 25;
|
||||||
const gridRight = 12;
|
const gridRight = 12;
|
||||||
const fontColor = "rgba(0, 0, 0, 0.68)";
|
|
||||||
const backgroundColor = '';
|
const backgroundColor = '';
|
||||||
const borderColor = "#ffffff";
|
const borderColor = "#ffffff";
|
||||||
const chartData = this.chartDataList[id - 1];
|
const chartData = this.chartDataList[id - 1];
|
||||||
const chartContainer = this.$refs[`chart${id}`][0];
|
const chartContainer = this.$refs[`chart${id}`][0];
|
||||||
const chart = echarts.init(chartContainer, null, {
|
const chartTheme = $('html').attr('nz-theme') == "dark" ? "dark" : "";
|
||||||
|
const chart = echarts.init(chartContainer, chartTheme, {
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
useDirtyRect: false,
|
useDirtyRect: false,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
@@ -283,7 +283,6 @@
|
|||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
@@ -292,7 +291,6 @@
|
|||||||
show: true,
|
show: true,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor
|
|
||||||
},
|
},
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
cap: 'butt'
|
cap: 'butt'
|
||||||
@@ -332,7 +330,6 @@
|
|||||||
series: seriesData,
|
series: seriesData,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: fontColor
|
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: '30',
|
top: '30',
|
||||||
|
|||||||
+6
-5
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
<div ref="chartDom" style="margin-top: 15px;height: 520px;overflow: hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,10 +65,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '8%',
|
left: this.isMobile ? '8%' : '3.8%',
|
||||||
right: '8%',
|
right: this.isMobile ? '8%' : '3.8%',
|
||||||
},
|
},
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
backgroundColor: '',
|
||||||
toolbox: {
|
toolbox: {
|
||||||
feature: {
|
feature: {
|
||||||
dataZoom: {
|
dataZoom: {
|
||||||
@@ -243,7 +243,8 @@
|
|||||||
return str.includes('Windows')
|
return str.includes('Windows')
|
||||||
},
|
},
|
||||||
renderChart() {
|
renderChart() {
|
||||||
this.myChart = echarts.init(this.$refs.chartDom);
|
const chartTheme = $('html').attr('nz-theme') == "dark" ? "dark" : "";
|
||||||
|
this.myChart = echarts.init(this.$refs.chartDom,chartTheme);
|
||||||
this.myChart.setOption(this.option);
|
this.myChart.setOption(this.option);
|
||||||
},
|
},
|
||||||
resizeHandle () {
|
resizeHandle () {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{{define "theme-server-status/content-footer"}}
|
|
||||||
<footer>
|
|
||||||
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
|
|
||||||
</footer>
|
|
||||||
<aside class="toolbox">
|
|
||||||
<span v-if="page=='index' && countryMapChartData.length!=0" class="showMapChart">
|
|
||||||
<i @click="showMapChart" data-toggle="modal" data-target="#mapChartBox" class="bi bi-geo-alt"></i>
|
|
||||||
</span>
|
|
||||||
<span class="toggleView">
|
|
||||||
<i v-if="showGroup" @click="toggleShowGroup" class="show-nogroup bi bi-justify"></i>
|
|
||||||
<i v-else @click="toggleShowGroup" class="show-group bi bi-view-stacked"></i>
|
|
||||||
</span>
|
|
||||||
<span class="setTheme">
|
|
||||||
<i v-if="theme === 'light'" @click="setTheme('dark')" class="setTheme-dark bi bi-moon-fill"></i>
|
|
||||||
<i v-else @click="setTheme('light')" class="setTheme-light bi bi-brightness-high-fill"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="showGoTop" class="showGoTop">
|
|
||||||
<i @click="goTop" class="goTop bi bi-arrow-up"></i>
|
|
||||||
</span>
|
|
||||||
</aside>
|
|
||||||
{{end}}
|
|
||||||
@@ -1,4 +1,38 @@
|
|||||||
{{define "theme-server-status/footer"}}
|
{{define "theme-server-status/footer"}}
|
||||||
|
</div>
|
||||||
|
<footer class="container-fluid">
|
||||||
|
<p>{{ .Conf.Site.Brand }} | Theme ServerStatus | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}</p>
|
||||||
|
</footer>
|
||||||
|
<aside class="toolbox">
|
||||||
|
<template v-if="showTools">
|
||||||
|
<span v-if="page=='index' && countryMapChartData.length!=0" class="showMapChart">
|
||||||
|
<i @click="showMapChart" data-toggle="modal" data-target="#mapChartBox" class="bi bi-geo-alt"></i>
|
||||||
|
</span>
|
||||||
|
<span class="toggleView">
|
||||||
|
<i v-if="showGroup" @click="toggleShowGroup" class="show-nogroup bi bi-justify"></i>
|
||||||
|
<i v-else @click="toggleShowGroup" class="show-group bi bi-view-stacked"></i>
|
||||||
|
</span>
|
||||||
|
<span class="toggleSemiTransparent" @click="toggleSemiTransparent">
|
||||||
|
<i class="bi" :class="semiTransparent ? 'bi-droplet' : 'bi-droplet-half'"></i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<span v-if="!showTools">
|
||||||
|
<i @click="toggleShowTools" class="bi bi-three-dots"></i>
|
||||||
|
</span>
|
||||||
|
<span class="setTheme">
|
||||||
|
<i v-if="theme === 'light'" @click="setTheme('dark')" class="setTheme-dark bi bi-moon-fill"></i>
|
||||||
|
<i v-else @click="setTheme('light')" class="setTheme-light bi bi-brightness-high-fill"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="showGoTop" class="showGoTop">
|
||||||
|
<i @click="goTop" class="goTop bi bi-arrow-up"></i>
|
||||||
|
</span>
|
||||||
|
</aside>
|
||||||
|
<template v-if="semiTransparent">
|
||||||
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.plus.css?v20240807">
|
||||||
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.plus.css?v20240807">
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
{{if ts .CustomCode}}{{.CustomCode|safe}}{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
+5
-13
@@ -8,10 +8,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap-theme.min.css">
|
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240724">
|
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v20240724">
|
|
||||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240407">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/font/bootstrap-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/assets/font-logos.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/[email protected]/css/flag-icons.min.css">
|
||||||
@@ -20,17 +16,13 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/js/bootstrap.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
|
||||||
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
<script src="https://unpkg.com/[email protected]/dist/echarts.min.js"></script>
|
||||||
<script src="/static/theme-server-status/js/mixin.js?v20240711"></script>
|
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v202408016">
|
||||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
|
||||||
<!--[if lt IE 9]>
|
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
|
||||||
<script src="/static/theme-server-status/js/html5shiv.js"></script>
|
<script src="/static/theme-server-status/js/mixin.js?v20240811"></script>
|
||||||
<script src="/static/theme-server-status/js/respond.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
{{if ts .CustomCode}}
|
|
||||||
{{.CustomCode|safe}}
|
|
||||||
{{end}}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="app">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+56
-44
@@ -1,34 +1,35 @@
|
|||||||
{{define "theme-server-status/home"}}
|
{{define "theme-server-status/home"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<!-- showGroup true -->
|
||||||
<!-- showGroup true -->
|
<template v-if="showGroup">
|
||||||
<template v-if="showGroup">
|
<section class="container-fluid table-responsive content" v-for="group in nodesTag">
|
||||||
<section class="container table-responsive content" style="max-width: 95vw" v-for="group in nodesTag">
|
{{template "theme-server-status/home-group-true" .}}
|
||||||
{{template "theme-server-status/home-group-true" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<!-- showGroup false -->
|
||||||
<!-- showGroup false -->
|
<template v-else>
|
||||||
<template v-else>
|
<section class="container-fluid table-responsive content">
|
||||||
<section class="container table-responsive content" style="max-width: 95vw">
|
{{template "theme-server-status/home-group-false" .}}
|
||||||
{{template "theme-server-status/home-group-false" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true" style="padding-left:0">
|
||||||
<div class="modal fade" id="mapChartBox" tabindex="-1" role="dialog" aria-labelledby="mapChartTitle" aria-hidden="true">
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
<div class="modal-content">
|
||||||
<div class="modal-content">
|
<div class="modal-header">
|
||||||
<div class="modal-header">
|
<h4 class="modal-title" id="mapChartTitle">
|
||||||
<h4 class="modal-title" id="mapChartTitle">{{tr "ServersOnWorldMap"}}</h4>
|
<i class="bi bi-geo-alt"></i>
|
||||||
<i class="bi bi-x" data-dismiss="modal" aria-label="Close"></i>
|
<span>{{tr "ServersOnWorldMap"}}</span>
|
||||||
</div>
|
</h4>
|
||||||
<div class="modal-body">
|
<i class="bi bi-x xclose" data-dismiss="modal" aria-label="Close"></i>
|
||||||
<div id="mapChart" style="width:100%;height:auto;"></div>
|
</div>
|
||||||
</div>
|
<div class="modal-body">
|
||||||
|
<div id="mapChart"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "theme-server-status/content-footer" .}}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -154,14 +155,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const unit = this.language=='zh-CN' ? '台' : 'servers';
|
const unit = this.language=='zh-CN' ? '台' : 'servers';
|
||||||
const isMobile = this.checkIsMobile();
|
const width = window.innerWidth;
|
||||||
const width = isMobile ? 338 : 1102;
|
const height = 0.95 * window.innerHeight;
|
||||||
const height = isMobile ? 200 : 500;
|
|
||||||
const backgroundColor = this.theme == "dark" ? '' : '';
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
||||||
const inRangeColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
|
const inRangeColor = this.theme == "dark" ? '#D2B206' : '#FFDF32';
|
||||||
const tooltipBackgroundColor = this.theme == "dark" ? "#ffffff" : '#ffffff';
|
const tooltipBackgroundColor = this.theme == "dark" ? "#ffffff" : '#ffffff';
|
||||||
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
const tooltipBorderColor = this.theme == "dark" ? "#ffffff" : "#ffffff";
|
||||||
const fontSize = isMobile ? 10 : 12;
|
const fontSize = this.isMobile ? 10 : 12;
|
||||||
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
const fontColor = this.theme == "dark" ? "#000000" : "#000000";
|
||||||
const chartContainer = document.getElementById('mapChart');
|
const chartContainer = document.getElementById('mapChart');
|
||||||
const mapChart = echarts.init(chartContainer, '', { // init图表
|
const mapChart = echarts.init(chartContainer, '', { // init图表
|
||||||
@@ -509,20 +509,21 @@
|
|||||||
}
|
}
|
||||||
// 定义图表参数值
|
// 定义图表参数值
|
||||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
const isMobile = this.checkIsMobile();
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
const fontSize = isMobile ? 10 : 14;
|
const gridLeft = this.isMobile ? 25 : 36;
|
||||||
const gridLeft = isMobile ? 25 : 36;
|
const gridRight = this.isMobile ? 5 : 20;
|
||||||
const gridRight = isMobile ? 5 : 20;
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
const legendLeft = isMobile ? 'center' : 'center';
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
const legendTop = isMobile ? 5 : 5;
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
const legendPadding= isMobile ? [5,0,5,0] : [5,0,5,0];
|
|
||||||
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
||||||
const chartTheme = theme == "dark" ? "dark" : "default";
|
const chartTheme = theme == "dark" ? "dark" : "default";
|
||||||
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
const backgroundColor = theme == "dark" ? '' : '';
|
const backgroundColor = theme == "dark" ? '' : '';
|
||||||
const tooltipBackgroundColor = theme == "dark" ? "#1C1D26" : '#ffffff';
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
const tooltipBorderColor = theme == "dark" ? "#31363B" : "#ffffff";
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
// 渲染图表
|
// 渲染图表
|
||||||
const chart = echarts.init(chartContainer, chartTheme, {
|
const chart = echarts.init(chartContainer, chartTheme, {
|
||||||
renderer: 'canvas',
|
renderer: 'canvas',
|
||||||
@@ -549,19 +550,26 @@
|
|||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data,
|
data: data,
|
||||||
connectNulls: true
|
connectNulls: true,
|
||||||
|
legendHoverLink: false,
|
||||||
|
emphasis: {
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: lineStyleWidth
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const legendData = chartData.map(item => item.monitor_name);
|
const legendData = chartData.map(item => item.monitor_name);
|
||||||
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
||||||
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
const autoIncrement = Math.floor((legendData.length - 1) / (isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (isMobile ? 20 : 28)
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
||||||
const height = 300 + autoIncrement;
|
const height = 300 + autoIncrement;
|
||||||
const gridTop = 40 + autoIncrement;
|
const gridTop = 40 + autoIncrement;
|
||||||
const legendIcon = isMobile ? 'rect' : "";
|
const legendIcon = this.isMobile ? 'rect' : "";
|
||||||
const itemWidth = isMobile ? 10 : 25;
|
const itemWidth = this.isMobile ? 10 : 25;
|
||||||
const itemHeight = isMobile ? 10 : 14;
|
const itemHeight = this.isMobile ? 10 : 14;
|
||||||
chart.resize({
|
chart.resize({
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: height
|
height: height
|
||||||
@@ -611,6 +619,11 @@
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: fontSize
|
fontSize: fontSize
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: splitLineWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
@@ -645,5 +658,4 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
{{define "theme-server-status/content-nav"}}
|
{{define "theme-server-status/menu"}}
|
||||||
<header role="navigation" class="navbar navbar-inverse navbar-fixed-top" style="z-index:99999999;">
|
<header role="navigation" class="navbar navbar-inverse navbar-fixed-top">
|
||||||
<div class="container">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||||
<span class="sr-only">Toggle navigation</span>
|
<span class="sr-only">Toggle navigation</span>
|
||||||
@@ -64,5 +64,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div class="nezha">
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
+327
-210
@@ -1,243 +1,360 @@
|
|||||||
{{define "theme-server-status/network"}}
|
{{define "theme-server-status/network"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<div class="container-fluid content network-box">
|
||||||
<div class="container table-responsive content" style="max-width: 95vw">
|
<div class="network-box-header btn-group">
|
||||||
<table class="table table-striped table-condensed table-hover">
|
<div class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<button class="ui nezha-primary-btn button"
|
<i class="bi bi-list-ul"></i> {{tr "NetworkSpiterList"}} <i class="bi bi-chevron-compact-down"></i>
|
||||||
v-for="server in servers"
|
</div>
|
||||||
style="margin-top: 3px"
|
<ul class="dropdown-menu">
|
||||||
@click="redirectNetwork(server.ID)">
|
<li class="input-group fixed-top">
|
||||||
@#server.Name#@ <i :class="'fi fi-' + server.Host.CountryCode"></i><span class="node-cell-location-text text-uppercase"> @#server.Host.CountryCode#@</span>
|
<input type="text" id="dropdown-search" class="form-control" placeholder="Search...">
|
||||||
</button>
|
</li>
|
||||||
</table>
|
<li class="dropdown-item" v-for="server in servers" @click="showCharts(server.ID)">
|
||||||
|
<a><i :class="'fi fi-' + (server.Host.CountryCode || 'rb')"></i> @#server.Name#@ <i v-if="server.ID == currentServerId" class="check icon"></i></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="container table-responsive content" style="max-width: 95vw">
|
<div class="chartTitle"><i class="chartCountryCode" :class="'fi fi-' + chartCountryCode"></i> @#chartTitle#@</div>
|
||||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
<div id="chartbox" style="width:100%;height:auto;"></div>
|
||||||
</div>
|
|
||||||
{{template "theme-server-status/content-footer" .}}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
|
||||||
const initData = JSON.parse('{{.Servers}}').servers;
|
|
||||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
delimiters: ['@#', '#@'],
|
delimiters: ['@#', '#@'],
|
||||||
data: {
|
data: {
|
||||||
page: 'network',
|
page: 'network',
|
||||||
defaultTemplate: {{.Conf.Site.Theme}},
|
defaultTemplate: "{{.Conf.Site.Theme}}",
|
||||||
templates: {{.Themes}},
|
templates: "{{.Themes }}",
|
||||||
servers: initData,
|
servers: [],
|
||||||
option: {
|
chartDataList: [],
|
||||||
tooltip: {
|
chartTitle: '',
|
||||||
trigger: 'axis',
|
chartCountryCode: '',
|
||||||
position: function (pt) {
|
chart: null,
|
||||||
return [pt[0], '10%'];
|
currentServerId: ''
|
||||||
},
|
|
||||||
formatter: function(params){
|
|
||||||
let result = params[0].axisValueLabel + "<br />";
|
|
||||||
params.forEach(function(item){
|
|
||||||
result += item.marker + item.seriesName + ": " + item.value[1].toFixed(2) + " ms<br />";
|
|
||||||
})
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
confine: true,
|
|
||||||
transitionDuration: 0
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
left: 'center',
|
|
||||||
text: "",
|
|
||||||
textStyle: {}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
top: '5%',
|
|
||||||
data: [],
|
|
||||||
textStyle: {
|
|
||||||
fontSize: 14
|
|
||||||
}
|
|
||||||
},
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
||||||
toolbox: {
|
|
||||||
feature: {
|
|
||||||
dataZoom: {
|
|
||||||
yAxisIndex: 'none'
|
|
||||||
},
|
|
||||||
restore: {},
|
|
||||||
saveAsImage: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
start: 0,
|
|
||||||
end: 100
|
|
||||||
}
|
|
||||||
],
|
|
||||||
xAxis: {
|
|
||||||
type: 'time',
|
|
||||||
boundaryGap: false
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
boundaryGap: false
|
|
||||||
},
|
|
||||||
series: [],
|
|
||||||
},
|
|
||||||
chartOnOff: true,
|
|
||||||
},
|
},
|
||||||
mixins: [mixinsVue],
|
mixins: [mixinsVue],
|
||||||
|
created() {
|
||||||
|
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||||
|
this.showCharts(this.servers[0].ID);
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.renderChart();
|
this.initSearch();
|
||||||
this.parseMonitorInfo(monitorInfo);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getFontLogoClass(str) {
|
showCharts(id) {
|
||||||
if (["almalinux",
|
// 发起数据请求
|
||||||
"alpine",
|
const url = `/api/v1/monitor/${id}`;
|
||||||
"aosc",
|
fetch(url)
|
||||||
"apple",
|
.then(response => response.json())
|
||||||
"archlinux",
|
.then(data => {
|
||||||
"archlabs",
|
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
||||||
"artix",
|
this.chartDataList[id] = data.result;
|
||||||
"budgie",
|
this.$nextTick(() => {
|
||||||
"centos",
|
this.renderCharts(id);
|
||||||
"coreos",
|
|
||||||
"debian",
|
|
||||||
"deepin",
|
|
||||||
"devuan",
|
|
||||||
"docker",
|
|
||||||
"elementary",
|
|
||||||
"fedora",
|
|
||||||
"ferris",
|
|
||||||
"flathub",
|
|
||||||
"freebsd",
|
|
||||||
"gentoo",
|
|
||||||
"gnu-guix",
|
|
||||||
"illumos",
|
|
||||||
"kali-linux",
|
|
||||||
"linuxmint",
|
|
||||||
"mageia",
|
|
||||||
"mandriva",
|
|
||||||
"manjaro",
|
|
||||||
"nixos",
|
|
||||||
"openbsd",
|
|
||||||
"opensuse",
|
|
||||||
"pop-os",
|
|
||||||
"raspberry-pi",
|
|
||||||
"redhat",
|
|
||||||
"rocky-linux",
|
|
||||||
"sabayon",
|
|
||||||
"slackware",
|
|
||||||
"snappy",
|
|
||||||
"solus",
|
|
||||||
"tux",
|
|
||||||
"ubuntu",
|
|
||||||
"void",
|
|
||||||
"zorin"].indexOf(str)
|
|
||||||
> -1) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
if (['openwrt', 'linux', "immortalwrt"].indexOf(str) > -1) {
|
|
||||||
return 'tux';
|
|
||||||
}
|
|
||||||
if (str == 'amazon') {
|
|
||||||
return 'redhat';
|
|
||||||
}
|
|
||||||
if (str == 'arch') {
|
|
||||||
return 'archlinux';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
redirectNetwork(id) {
|
|
||||||
this.getMonitorHistory(id)
|
|
||||||
.then(function(monitorInfo) {
|
|
||||||
var vm = app.__vue__;
|
|
||||||
vm.parseMonitorInfo(monitorInfo);
|
|
||||||
})
|
|
||||||
.catch(function(error){
|
|
||||||
window.location.href = "/404";
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getMonitorHistory(id) {
|
|
||||||
return $.ajax({
|
|
||||||
url: "/api/v1/monitor/"+id,
|
|
||||||
method: "GET"
|
|
||||||
});
|
|
||||||
},
|
|
||||||
parseMonitorInfo(monitorInfo) {
|
|
||||||
let tSeries = [];
|
|
||||||
let tLegendData = [];
|
|
||||||
var lcolors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
|
|
||||||
for (let i = 0; i < monitorInfo.result.length; i++) {
|
|
||||||
var lcolor = lcolors[i % lcolors.length];
|
|
||||||
var rgbaColorMarker = 'rgba(' + parseInt(lcolor.slice(1, 3), 16) + ',' + parseInt(lcolor.slice(3, 5), 16) + ',' + parseInt(lcolor.slice(5, 7), 16) + ',0.5)';
|
|
||||||
var rgbaColorBar = 'rgba(' + parseInt(lcolor.slice(1, 3), 16) + ',' + parseInt(lcolor.slice(3, 5), 16) + ',' + parseInt(lcolor.slice(5, 7), 16) + ',0.35)';
|
|
||||||
let loss = 0;
|
|
||||||
let data = [];
|
|
||||||
let datal = [];
|
|
||||||
for (let j = 0; j < monitorInfo.result[i].created_at.length; j++) {
|
|
||||||
avgDelay = Math.round(monitorInfo.result[i].avg_delay[j]);
|
|
||||||
if (avgDelay > 0 && avgDelay < MaxTCPPingValue) {
|
|
||||||
data.push([monitorInfo.result[i].created_at[j], avgDelay]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
loss += 1;
|
|
||||||
datal.push({
|
|
||||||
xAxis: monitorInfo.result[i].created_at[j],
|
|
||||||
label: { show: false },
|
|
||||||
emphasis: { disabled: true },
|
|
||||||
lineStyle: {
|
|
||||||
type: "solid",
|
|
||||||
color: rgbaColorBar
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log('this server (id:'+ id + ') has no monitor.');
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
.catch(error => {
|
||||||
if (lossRate > 99) {
|
console.error('Error fetching data:', error);
|
||||||
datal = [];
|
});
|
||||||
}
|
},
|
||||||
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
|
renderCharts(id, reload = false) {
|
||||||
tLegendData.push(legendName);
|
if(!this.chartDataList[id]) return;
|
||||||
tSeries.push({
|
if(this.chart) this.disposeCharts(this.chart);
|
||||||
|
this.currentServerId = id;
|
||||||
|
this.chartCountryCode = this.getServerCountryCode(id);
|
||||||
|
this.chartTitle = this.chartDataList[id][0].server_name;
|
||||||
|
const chartData = this.chartDataList[id];
|
||||||
|
const chartContainer = document.getElementById('chartbox');
|
||||||
|
// 定义图表参数值
|
||||||
|
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||||
|
const autoheight = this.isMobile ? (window.innerHeight - 200) : (window.innerHeight - 250);
|
||||||
|
const fontSize = this.isMobile ? 10 : 14;
|
||||||
|
const gridLeft = this.isMobile ? 30 : 36;
|
||||||
|
const gridRight = this.isMobile ? 12 : 20;
|
||||||
|
const legendLeft = this.isMobile ? 'center' : 'center';
|
||||||
|
const legendTop = this.isMobile ? 5 : 5;
|
||||||
|
const legendPadding= this.isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||||
|
const fontColor = this.theme == "dark" ? "#f1f1f1" : "#000000";
|
||||||
|
const backgroundColor = this.theme == "dark" ? '' : '';
|
||||||
|
const tooltipBackgroundColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.85)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.85)" : "rgba(255,255,255,1)");
|
||||||
|
const tooltipBorderColor = this.theme == "dark" ? (this.semiTransparent ? "rgba(28,29,38,0.9)" : "rgba(28,29,38,1)") : (this.semiTransparent ? "rgba(255,255,255,0.9)" : "rgba(255,255,255,1)");
|
||||||
|
const lineStyleWidth = this.isMobile ? 1 : 2;
|
||||||
|
const splitLineWidth = this.isMobile ? 0.5 : 1;
|
||||||
|
const splitLineColor = this.theme == "dark" ? "rgba(110, 112, 121, 0.95)" : "rgba(224, 230, 241, 0.95)";
|
||||||
|
const markPointLabelColor = this.theme == "dark" ? "#111111" : "#000000";
|
||||||
|
const markPointItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
|
const markLineItemStyleOpacity = this.semiTransparent ? 1 : 0.75;
|
||||||
|
const markLineLineStyleWidth = this.isMobile ? 0.15 : 0.3;
|
||||||
|
this.chart = echarts.init(chartContainer, '', { // init图表
|
||||||
|
renderer: 'canvas',
|
||||||
|
useDirtyRect: false,
|
||||||
|
width: 'auto',
|
||||||
|
height: autoheight,
|
||||||
|
});
|
||||||
|
// 获取图表数据
|
||||||
|
let legendData = [];
|
||||||
|
let seriesData = [];
|
||||||
|
chartData.forEach((item,key)=> {
|
||||||
|
let loss = 0;
|
||||||
|
let totalLossRate = 0;
|
||||||
|
let legendName = '';
|
||||||
|
let data = { main: [], markLine: []};
|
||||||
|
item.avg_delay.forEach((avgDelay, index) => {
|
||||||
|
const threshold = 0.9 * MaxTCPPingValue; // 定义阀值,用于判断是否丢包
|
||||||
|
// 定义丢包 1. avgDelay==0 2. avgDelay>=MaxTCPPingValue 3. avgDelay>=threshold
|
||||||
|
if(avgDelay == 0 || avgDelay >= MaxTCPPingValue){ //绝对丢包
|
||||||
|
loss += 1;
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
if(lossrate != 100) {
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], MaxTCPPingValue, lossrate]
|
||||||
|
);
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: { disabled: true },
|
||||||
|
lineStyle: { type: "solid" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (avgDelay >= threshold && avgDelay < MaxTCPPingValue){ // 相对丢包
|
||||||
|
loss += 1;
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
if(lossrate != 100) {
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
|
data['markLine'].push({
|
||||||
|
xAxis: item.created_at[index],
|
||||||
|
label: { show: false },
|
||||||
|
emphasis: { disabled: true },
|
||||||
|
lineStyle: { type: "solid" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else { // 未丢包
|
||||||
|
const lossrate = 100 * loss / (index + 1);
|
||||||
|
data['main'].push(
|
||||||
|
[item.created_at[index], avgDelay, lossrate]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 处理legendData
|
||||||
|
totalLossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||||
|
legendName = `${item.monitor_name} ${totalLossRate}%`;
|
||||||
|
legendData.push(legendName);
|
||||||
|
// 处理seriesData
|
||||||
|
seriesData.push(
|
||||||
|
{
|
||||||
name: legendName,
|
name: legendName,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data,
|
connectNulls: true,
|
||||||
|
legendHoverLink: false,
|
||||||
|
emphasis: {
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: lineStyleWidth
|
||||||
|
},
|
||||||
|
data: data['main'],
|
||||||
markLine: {
|
markLine: {
|
||||||
symbol: "none",
|
symbol: "none",
|
||||||
symbolSize :0,
|
symbolSize :0,
|
||||||
data: datal
|
data: data['markLine'],
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markLineItemStyleOpacity
|
||||||
|
},
|
||||||
|
lineStyle:{
|
||||||
|
width: markLineLineStyleWidth
|
||||||
|
}
|
||||||
},
|
},
|
||||||
markPoint: {
|
markPoint: {
|
||||||
data: [
|
data: [
|
||||||
{ type: 'max', symbol: 'pin', name: 'Max', itemStyle: { color: rgbaColorMarker }, symbolSize: 30, label: { fontSize: 8 } },
|
{
|
||||||
{ type: 'min', symbol: 'pin', name: 'Min', itemStyle: { color: rgbaColorMarker }, symbolSize: 30, label: { fontSize: 8, offset: [0, 7.5] }, symbolRotate: 180 }
|
name: 'Max',
|
||||||
|
type: 'max',
|
||||||
|
symbol: 'pin',
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markPointItemStyleOpacity
|
||||||
|
},
|
||||||
|
symbolSize: 30,
|
||||||
|
label: {
|
||||||
|
fontSize: 8,
|
||||||
|
color: markPointLabelColor,
|
||||||
|
formatter: function (params) {
|
||||||
|
return Math.round(params.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Min',
|
||||||
|
type: 'min',
|
||||||
|
symbol: 'pin',
|
||||||
|
itemStyle: {
|
||||||
|
opacity: markPointItemStyleOpacity
|
||||||
|
},
|
||||||
|
symbolSize: 30,
|
||||||
|
label: {
|
||||||
|
fontSize: 8,
|
||||||
|
color: markPointLabelColor,
|
||||||
|
offset: [0, 8],
|
||||||
|
formatter: function (params) {
|
||||||
|
return Math.round(params.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
symbolRotate: 180
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const maxLegendsPerRowMobile = localStorage.getItem("maxLegendsPerRowMobile") ? localStorage.getItem("maxLegendsPerRowMobile") : 3;
|
||||||
|
const maxLegendsPerRowPc = localStorage.getItem("maxLegendsPerRowPc") ? localStorage.getItem("maxLegendsPerRowPc") : 6;
|
||||||
|
const autoIncrement = Math.floor((legendData.length - 1) / (this.isMobile ? maxLegendsPerRowMobile : maxLegendsPerRowPc)) * (this.isMobile ? 20 : 28);
|
||||||
|
const height = autoheight + autoIncrement;
|
||||||
|
const gridTop = 40 + autoIncrement;
|
||||||
|
const legendIcon = this.isMobile ? 'rect' : "";
|
||||||
|
const itemWidth = this.isMobile ? 10 : 25;
|
||||||
|
const itemHeight = this.isMobile ? 10 : 14;
|
||||||
|
this.chart.resize({
|
||||||
|
width: 'auto',
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
// 设置图表配置项
|
||||||
|
const option = {
|
||||||
|
// 全局调色盘
|
||||||
|
color: this.colors,
|
||||||
|
// 背景颜色
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
// 文字样式
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
// 图表网格设置
|
||||||
|
grid: {
|
||||||
|
top: gridTop,
|
||||||
|
left: gridLeft,
|
||||||
|
right: gridRight
|
||||||
|
},
|
||||||
|
// 图表标题设置
|
||||||
|
title: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
// 图表系列数据设置
|
||||||
|
series: seriesData.flat(),
|
||||||
|
// X轴设置
|
||||||
|
xAxis: {
|
||||||
|
type: 'time',
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Y轴设置
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: splitLineWidth,
|
||||||
|
color: splitLineColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 图例设置
|
||||||
|
legend: {
|
||||||
|
data: legendData,
|
||||||
|
show: true,
|
||||||
|
icon: legendIcon,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
top: legendTop,
|
||||||
|
bottom: 0,
|
||||||
|
left: legendLeft,
|
||||||
|
padding: legendPadding,
|
||||||
|
itemWidth: itemWidth,
|
||||||
|
itemHeight: itemHeight,
|
||||||
|
},
|
||||||
|
// 提示框设置
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: tooltipBackgroundColor,
|
||||||
|
borderColor: tooltipBorderColor,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
},
|
||||||
|
formatter: function (params) {
|
||||||
|
let tooltipContent = '';
|
||||||
|
const formattedTime = new Date(params[0].value[0]).toLocaleString();
|
||||||
|
tooltipContent += `<span style="line-height:2em">${formattedTime}</span><br>`;
|
||||||
|
params.forEach(param => {
|
||||||
|
const formattedTime = new Date(param.value[0]).toLocaleString();
|
||||||
|
if (!param.seriesName.includes('stack')) {
|
||||||
|
const name = param.seriesName.replace(/\s\d+(\.\d+)?%$/, '');
|
||||||
|
tooltipContent += `<span style="line-height:2em">${param.marker} ${name} ${param.value[2].toFixed(1)}% ${param.value[1].toFixed(2)}</span><br>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tooltipContent;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 数据缩放设置
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'slider',
|
||||||
|
start: 0,
|
||||||
|
end: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// 设置图表的配置选项
|
||||||
|
this.chart.setOption(option);
|
||||||
|
},
|
||||||
|
reloadCharts() { // 重新加载所有图表
|
||||||
|
this.servers.forEach(node => {
|
||||||
|
const id = node.ID;
|
||||||
|
const chartData = this.chartDataList[id];
|
||||||
|
if (chartData) {
|
||||||
|
this.renderCharts(id,true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
disposeCharts(chart){
|
||||||
|
chart.dispose();
|
||||||
|
chart = null;
|
||||||
|
},
|
||||||
|
getServerCountryCode(id){
|
||||||
|
const result = this.servers.find(item => item.ID == id);
|
||||||
|
return result.Host.CountryCode ? result.Host.CountryCode : 'rb';
|
||||||
|
},
|
||||||
|
initSearch() {
|
||||||
|
$('#dropdown-search').on('keyup', function() {
|
||||||
|
var searchTerm = $(this).val().toLowerCase();
|
||||||
|
$('.dropdown-menu .dropdown-item').each(function() {
|
||||||
|
var text = $(this).text().toLowerCase();
|
||||||
|
if (text.indexOf(searchTerm) > -1) {
|
||||||
|
$(this).removeClass('hidden').addClass('visible'); // 显示元素
|
||||||
|
} else {
|
||||||
|
$(this).removeClass('visible').addClass('hidden'); // 隐藏元素
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
this.option.title.text = monitorInfo.result[0].server_name;
|
}
|
||||||
this.option.series = tSeries;
|
}
|
||||||
this.option.legend.data = tLegendData;
|
|
||||||
this.myChart.clear();
|
|
||||||
this.myChart.setOption(this.option);
|
|
||||||
},
|
|
||||||
isWindowsPlatform(str) {
|
|
||||||
return str.includes('Windows')
|
|
||||||
},
|
|
||||||
renderChart() {
|
|
||||||
this.myChart = echarts.init(this.$refs.chartDom);
|
|
||||||
this.myChart.setOption(this.option);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.myChart.dispose();
|
|
||||||
this.myChart = null;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+68
-71
@@ -1,77 +1,75 @@
|
|||||||
{{define "theme-server-status/service"}}
|
{{define "theme-server-status/service"}}
|
||||||
{{template "theme-server-status/header" .}}
|
{{template "theme-server-status/header" .}}
|
||||||
<div id="app">
|
{{template "theme-server-status/menu" .}}
|
||||||
{{template "theme-server-status/content-nav" .}}
|
<!-- showGroup true -->
|
||||||
<!-- showGroup true -->
|
<template v-if="showGroup">
|
||||||
<template v-if="showGroup">
|
<section v-if="servicesTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-if="servicesTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
</section>
|
||||||
</section>
|
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
||||||
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;" v-for="group in servicesTag">
|
{{template "theme-server-status/service-group-true" .}}
|
||||||
{{template "theme-server-status/service-group-true" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
<!-- showGroup false -->
|
||||||
<!-- showGroup false -->
|
<template v-else>
|
||||||
<template v-else>
|
<section v-if="servicesNoTag.length === 0" class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-if="servicesNoTag.length === 0" class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
||||||
<p>No Valid Service Monitor Configuration Entries Found. Please Verify in the <a href="/monitor">Admin Panel</a>.</p>
|
</section>
|
||||||
</section>
|
<section v-else class="container-fluid content" style="min-height: .01%;overflow-x: auto;">
|
||||||
<section v-else class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
{{template "theme-server-status/service-group-false" .}}
|
||||||
{{template "theme-server-status/service-group-false" .}}
|
</section>
|
||||||
</section>
|
</template>
|
||||||
</template>
|
{{if .CycleTransferStats}}
|
||||||
{{if .CycleTransferStats}}
|
<section class="container-fluid content table-responsive">
|
||||||
<section class="container content table-responsive" style="max-width: 95vw">
|
<table class="table table-striped table-condensed table-hover">
|
||||||
<table class="table table-striped table-condensed table-hover">
|
<thead>
|
||||||
<thead>
|
<tr class="node-group-tag">
|
||||||
<tr class="node-group-tag">
|
<th colspan="16" style="border:none;">
|
||||||
<th colspan="16" style="border:none;">
|
{{tr "CycleTransferStats"}}
|
||||||
{{tr "CycleTransferStats"}}
|
</th>
|
||||||
</th>
|
</tr>
|
||||||
</tr>
|
<tr class="node-group-cell">
|
||||||
<tr class="node-group-cell">
|
<th class="node-cell center">ID</th>
|
||||||
<th class="node-cell center">ID</th>
|
<th class="node-cell center">{{tr "Rules"}}</th>
|
||||||
<th class="node-cell center">{{tr "Rules"}}</th>
|
<th class="node-cell center">{{tr "Server"}}</th>
|
||||||
<th class="node-cell center">{{tr "Server"}}</th>
|
<th class="node-cell center">{{tr "From"}}</th>
|
||||||
<th class="node-cell center">{{tr "From"}}</th>
|
<th class="node-cell center">{{tr "To"}}</th>
|
||||||
<th class="node-cell center">{{tr "To"}}</th>
|
<th class="node-cell center">MAX</th>
|
||||||
<th class="node-cell center">MAX</th>
|
<th class="node-cell center">MIN</th>
|
||||||
<th class="node-cell center">MIN</th>
|
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
||||||
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
||||||
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
<th class="node-cell center">{{tr "Transleft"}}</th>
|
||||||
<th class="node-cell center">{{tr "Transleft"}}</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
{{range $id, $stats := .CycleTransferStats}}
|
||||||
{{range $id, $stats := .CycleTransferStats}}
|
{{range $innerId, $transfer := $stats.Transfer}}
|
||||||
{{range $innerId, $transfer := $stats.Transfer}}
|
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
||||||
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
<tr>
|
||||||
<tr>
|
<td class="node-cell center">{{$id}}</td>
|
||||||
<td class="node-cell center">{{$id}}</td>
|
<td class="node-cell center">{{$stats.Name}}</td>
|
||||||
<td class="node-cell center">{{$stats.Name}}</td>
|
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
|
||||||
<td class="node-cell center">{{index $stats.ServerName $innerId}}</td>
|
<td class="node-cell center">{{$stats.From|tf}}</td>
|
||||||
<td class="node-cell center">{{$stats.From|tf}}</td>
|
<td class="node-cell center">{{$stats.To|tf}}</td>
|
||||||
<td class="node-cell center">{{$stats.To|tf}}</td>
|
<td class="node-cell center">{{$stats.Max|bf}}</td>
|
||||||
<td class="node-cell center">{{$stats.Max|bf}}</td>
|
<td class="node-cell center">{{$stats.Min|bf}}</td>
|
||||||
<td class="node-cell center">{{$stats.Min|bf}}</td>
|
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
|
||||||
<td class="node-cell center">{{(index $stats.NextUpdate $innerId)|sft}}</td>
|
<td class="node-cell center">{{$transfer|bf}}</td>
|
||||||
<td class="node-cell center">{{$transfer|bf}}</td>
|
<td class="node-cell center">
|
||||||
<td class="node-cell center">
|
<div class="progress">
|
||||||
<div class="progress">
|
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
|
||||||
<div style="width: {{$TransLeftPercent}}%" :class="'progress-bar progress-bar-' + toSSBar('{{TransClassName $TransLeftPercent}}')">
|
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
|
||||||
<small style="display: inline-block;width: max-content;">{{TransLeft $stats.Max $transfer}} / {{$TransLeftPercent}} %</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</td>
|
||||||
{{end}}
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
{{end}}
|
||||||
</table>
|
</tbody>
|
||||||
</section>
|
</table>
|
||||||
{{end}}
|
</section>
|
||||||
{{template "theme-server-status/content-footer" .}}
|
{{end}}
|
||||||
</div>
|
{{template "theme-server-status/footer" .}}
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
@@ -240,5 +238,4 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{{template "theme-server-status/footer" .}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
+5
-2
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.18.2"
|
NZ_VERSION="v0.18.3"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -53,10 +53,13 @@ geo_check() {
|
|||||||
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||||
set -- $api_list
|
set -- $api_list
|
||||||
for url in $api_list; do
|
for url in $api_list; do
|
||||||
text="$(curl -A $ua -m 10 -s $url)"
|
text="$(curl -A "$ua" -m 10 -s $url)"
|
||||||
|
endpoint="$(echo $text | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
|
||||||
if echo $text | grep -qw 'CN'; then
|
if echo $text | grep -qw 'CN'; then
|
||||||
isCN=true
|
isCN=true
|
||||||
break
|
break
|
||||||
|
elif echo $url | grep -q $endpoint; then
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
|
|||||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||||
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
|
||||||
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
|
||||||
NZ_VERSION="v0.18.2"
|
NZ_VERSION="v0.18.3"
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
@@ -53,10 +53,13 @@ geo_check() {
|
|||||||
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||||
set -- $api_list
|
set -- $api_list
|
||||||
for url in $api_list; do
|
for url in $api_list; do
|
||||||
text="$(curl -A $ua -m 10 -s $url)"
|
text="$(curl -A "$ua" -m 10 -s $url)"
|
||||||
|
endpoint="$(echo $text | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
|
||||||
if echo $text | grep -qw 'CN'; then
|
if echo $text | grep -qw 'CN'; then
|
||||||
isCN=true
|
isCN=true
|
||||||
break
|
break
|
||||||
|
elif echo $url | grep -q $endpoint; then
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ LOOP:
|
|||||||
endCh := make(chan struct{})
|
endCh := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, innerErr := io.Copy(stream.userIo, stream.agentIo)
|
_, innerErr := io.CopyBuffer(stream.userIo, stream.agentIo, make([]byte, 1048576))
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ LOOP:
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
_, innerErr := io.Copy(stream.agentIo, stream.userIo)
|
_, innerErr := io.CopyBuffer(stream.agentIo, stream.userIo, make([]byte, 1048576))
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -90,12 +90,14 @@ func (s *NezhaHandler) RequestTask(h *pb.Host, stream pb.NezhaService_RequestTas
|
|||||||
}
|
}
|
||||||
closeCh := make(chan error)
|
closeCh := make(chan error)
|
||||||
singleton.ServerLock.RLock()
|
singleton.ServerLock.RLock()
|
||||||
|
singleton.ServerList[clientID].TaskCloseLock.Lock()
|
||||||
// 修复不断的请求 task 但是没有 return 导致内存泄漏
|
// 修复不断的请求 task 但是没有 return 导致内存泄漏
|
||||||
if singleton.ServerList[clientID].TaskClose != nil {
|
if singleton.ServerList[clientID].TaskClose != nil {
|
||||||
close(singleton.ServerList[clientID].TaskClose)
|
close(singleton.ServerList[clientID].TaskClose)
|
||||||
}
|
}
|
||||||
singleton.ServerList[clientID].TaskStream = stream
|
singleton.ServerList[clientID].TaskStream = stream
|
||||||
singleton.ServerList[clientID].TaskClose = closeCh
|
singleton.ServerList[clientID].TaskClose = closeCh
|
||||||
|
singleton.ServerList[clientID].TaskCloseLock.Unlock()
|
||||||
singleton.ServerLock.RUnlock()
|
singleton.ServerLock.RUnlock()
|
||||||
return <-closeCh
|
return <-closeCh
|
||||||
}
|
}
|
||||||
@@ -112,10 +114,10 @@ func (s *NezhaHandler) ReportSystemState(c context.Context, r *pb.State) (*pb.Re
|
|||||||
singleton.ServerList[clientID].LastActive = time.Now()
|
singleton.ServerList[clientID].LastActive = time.Now()
|
||||||
singleton.ServerList[clientID].State = &state
|
singleton.ServerList[clientID].State = &state
|
||||||
|
|
||||||
// 如果从未记录过,先打点,等到小时时间点时入库
|
// 应对 dashboard 重启的情况,如果从未记录过,先打点,等到小时时间点时入库
|
||||||
if singleton.ServerList[clientID].PrevHourlyTransferIn == 0 || singleton.ServerList[clientID].PrevHourlyTransferOut == 0 {
|
if singleton.ServerList[clientID].PrevTransferInSnapshot == 0 || singleton.ServerList[clientID].PrevTransferOutSnapshot == 0 {
|
||||||
singleton.ServerList[clientID].PrevHourlyTransferIn = int64(state.NetInTransfer)
|
singleton.ServerList[clientID].PrevTransferInSnapshot = int64(state.NetInTransfer)
|
||||||
singleton.ServerList[clientID].PrevHourlyTransferOut = int64(state.NetOutTransfer)
|
singleton.ServerList[clientID].PrevTransferOutSnapshot = int64(state.NetOutTransfer)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.Receipt{Proced: true}, nil
|
return &pb.Receipt{Proced: true}, nil
|
||||||
@@ -135,9 +137,8 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
// 检查并更新DDNS
|
// 检查并更新DDNS
|
||||||
if singleton.Conf.DDNS.Enable &&
|
if singleton.Conf.DDNS.Enable &&
|
||||||
singleton.ServerList[clientID].EnableDDNS &&
|
singleton.ServerList[clientID].EnableDDNS &&
|
||||||
singleton.ServerList[clientID].Host != nil &&
|
|
||||||
host.IP != "" &&
|
host.IP != "" &&
|
||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
(singleton.ServerList[clientID].Host == nil || singleton.ServerList[clientID].Host.IP != host.IP) {
|
||||||
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
||||||
if singleton.Conf.DDNS.Provider == "" {
|
if singleton.Conf.DDNS.Provider == "" {
|
||||||
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
|
provider, err = singleton.GetDDNSProviderFromProfile(singleton.ServerList[clientID].DDNSProfile)
|
||||||
@@ -164,10 +165,9 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 发送IP变动通知
|
// 发送IP变动通知
|
||||||
if singleton.Conf.EnableIPChangeNotification &&
|
if singleton.ServerList[clientID].Host != nil && singleton.Conf.EnableIPChangeNotification &&
|
||||||
((singleton.Conf.Cover == model.ConfigCoverAll && !singleton.Conf.IgnoredIPNotificationServerIDs[clientID]) ||
|
((singleton.Conf.Cover == model.ConfigCoverAll && !singleton.Conf.IgnoredIPNotificationServerIDs[clientID]) ||
|
||||||
(singleton.Conf.Cover == model.ConfigCoverIgnoreAll && singleton.Conf.IgnoredIPNotificationServerIDs[clientID])) &&
|
(singleton.Conf.Cover == model.ConfigCoverIgnoreAll && singleton.Conf.IgnoredIPNotificationServerIDs[clientID])) &&
|
||||||
singleton.ServerList[clientID].Host != nil &&
|
|
||||||
singleton.ServerList[clientID].Host.IP != "" &&
|
singleton.ServerList[clientID].Host.IP != "" &&
|
||||||
host.IP != "" &&
|
host.IP != "" &&
|
||||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||||
@@ -184,10 +184,14 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
nil)
|
nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否是机器重启,如果是机器重启要录入最后记录的流量里面
|
/**
|
||||||
if singleton.ServerList[clientID].Host.BootTime < host.BootTime {
|
* 这里的 singleton 中的数据都是关机前的旧数据
|
||||||
singleton.ServerList[clientID].PrevHourlyTransferIn = singleton.ServerList[clientID].PrevHourlyTransferIn - int64(singleton.ServerList[clientID].State.NetInTransfer)
|
* 当 agent 重启时,bootTime 变大,agent 端会先上报 host 信息,然后上报 state 信息
|
||||||
singleton.ServerList[clientID].PrevHourlyTransferOut = singleton.ServerList[clientID].PrevHourlyTransferOut - int64(singleton.ServerList[clientID].State.NetOutTransfer)
|
* 这是可以借助上报顺序的空档,将停机前的流量统计数据标记下来,加到下一个小时的数据点上
|
||||||
|
*/
|
||||||
|
if singleton.ServerList[clientID].Host != nil && singleton.ServerList[clientID].Host.BootTime < host.BootTime {
|
||||||
|
singleton.ServerList[clientID].PrevTransferInSnapshot = singleton.ServerList[clientID].PrevTransferInSnapshot - int64(singleton.ServerList[clientID].State.NetInTransfer)
|
||||||
|
singleton.ServerList[clientID].PrevTransferOutSnapshot = singleton.ServerList[clientID].PrevTransferOutSnapshot - int64(singleton.ServerList[clientID].State.NetOutTransfer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不要冲掉国家码
|
// 不要冲掉国家码
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ func loadServers() {
|
|||||||
innerS := s
|
innerS := s
|
||||||
innerS.Host = &model.Host{}
|
innerS.Host = &model.Host{}
|
||||||
innerS.State = &model.HostState{}
|
innerS.State = &model.HostState{}
|
||||||
|
innerS.TaskCloseLock = new(sync.Mutex)
|
||||||
ServerList[innerS.ID] = &innerS
|
ServerList[innerS.ID] = &innerS
|
||||||
SecretToID[innerS.Secret] = innerS.ID
|
SecretToID[innerS.Secret] = innerS.ID
|
||||||
ServerTagToIDList[innerS.Tag] = append(ServerTagToIDList[innerS.Tag], innerS.ID)
|
ServerTagToIDList[innerS.Tag] = append(ServerTagToIDList[innerS.Tag], innerS.ID)
|
||||||
|
|||||||
@@ -100,14 +100,14 @@ func RecordTransferHourlyUsage() {
|
|||||||
for id, server := range ServerList {
|
for id, server := range ServerList {
|
||||||
tx := model.Transfer{
|
tx := model.Transfer{
|
||||||
ServerID: id,
|
ServerID: id,
|
||||||
In: server.State.NetInTransfer - uint64(server.PrevHourlyTransferIn),
|
In: utils.Uint64SubInt64(server.State.NetInTransfer, server.PrevTransferInSnapshot),
|
||||||
Out: server.State.NetOutTransfer - uint64(server.PrevHourlyTransferOut),
|
Out: utils.Uint64SubInt64(server.State.NetOutTransfer, server.PrevTransferOutSnapshot),
|
||||||
}
|
}
|
||||||
if tx.In == 0 && tx.Out == 0 {
|
if tx.In == 0 && tx.Out == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
server.PrevHourlyTransferIn = int64(server.State.NetInTransfer)
|
server.PrevTransferInSnapshot = int64(server.State.NetInTransfer)
|
||||||
server.PrevHourlyTransferOut = int64(server.State.NetOutTransfer)
|
server.PrevTransferOutSnapshot = int64(server.State.NetOutTransfer)
|
||||||
tx.CreatedAt = nowTrimSeconds
|
tx.CreatedAt = nowTrimSeconds
|
||||||
txs = append(txs, tx)
|
txs = append(txs, tx)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user