Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9a7002906 | ||
|
|
d9188f5228 | ||
|
|
eb3e2883e2 | ||
|
|
83ba5ebc8a | ||
|
|
6d7d3c5e85 | ||
|
|
194f3290bc | ||
|
|
dc5c79711d | ||
|
|
d88989fb85 | ||
|
|
0373d03fb2 | ||
|
|
140f9809a0 | ||
|
|
f647a5f8e6 | ||
|
|
506c87dbd8 | ||
|
|
8b5d513c6a | ||
|
|
6c718c4f38 | ||
|
|
5a2ab8a7ee | ||
|
|
0ea0c9ad8c | ||
|
|
7d4d8c46b8 | ||
|
|
a4636ed774 | ||
|
|
bb48e0912d | ||
|
|
a575d1a6e0 | ||
|
|
420d1ca8ff | ||
|
|
29554ec7ec | ||
|
|
9a8c0e7118 | ||
|
|
800af7ce4e | ||
|
|
78134a0e8b | ||
|
|
bad910b686 | ||
|
|
a7d1bd7487 | ||
|
|
20a10ac6b8 | ||
|
|
7d85c4c4a5 | ||
|
|
b1348eb545 | ||
|
|
753199a3b8 | ||
|
|
64ba3cd0a7 | ||
|
|
3a395e66c0 | ||
|
|
193455c675 | ||
|
|
dfb08caae1 | ||
|
|
7f7763606d | ||
|
|
77de1b44bc | ||
|
|
da6a3463ed | ||
|
|
6fa149519d | ||
|
|
8e67d3487e | ||
|
|
8033c6a0b7 | ||
|
|
abb108a83e | ||
|
|
cf9d9bd248 | ||
|
|
5e528c42c7 | ||
|
|
daa7e03240 | ||
|
|
ab39782c78 | ||
|
|
e24925b049 | ||
|
|
1d4245ee09 | ||
|
|
8df863a3e0 | ||
|
|
07e0382598 | ||
|
|
8dd509aa08 | ||
|
|
99ac12c9fd | ||
|
|
ebc45f9b8f | ||
|
|
3b5ee464a7 | ||
|
|
c4b2c47beb | ||
|
|
0835f13f28 | ||
|
|
ee3cda8aed | ||
|
|
604fc7d4c0 | ||
|
|
5c549261dd | ||
|
|
00041c0ebd | ||
|
|
dbfea9a00b | ||
|
|
4e5cc0b642 | ||
|
|
fe2872bbb3 | ||
|
|
e8b208edfa | ||
|
|
7d38634933 | ||
|
|
5bd4b57cbf | ||
|
|
fdb2fd8839 | ||
|
|
f3fac9325c | ||
|
|
626613441d | ||
|
|
b86b73a433 | ||
|
|
e8b8e59bd7 | ||
|
|
c9bcba6f28 | ||
|
|
ad9dc5470a | ||
|
|
8539fb6559 | ||
|
|
11bcc47d49 | ||
|
|
bb789f7652 | ||
|
|
51c1e41123 |
@@ -22,11 +22,11 @@ jobs:
|
||||
uses: crazy-max/ghaction-xgo@v2
|
||||
with:
|
||||
xgo_version: latest
|
||||
go_version: 1.21
|
||||
go_version: 1.20.x
|
||||
dest: dist
|
||||
pkg: cmd/dashboard
|
||||
prefix: dashboard
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64 # linux/386,
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
||||
v: true
|
||||
x: false
|
||||
race: false
|
||||
|
||||
@@ -25,11 +25,11 @@ jobs:
|
||||
uses: crazy-max/ghaction-xgo@v2
|
||||
with:
|
||||
xgo_version: latest
|
||||
go_version: 1.21
|
||||
go_version: 1.20.x
|
||||
dest: dist
|
||||
pkg: cmd/dashboard
|
||||
prefix: dashboard
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64 # linux/386,
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
||||
v: true
|
||||
x: false
|
||||
race: false
|
||||
|
||||
@@ -32,11 +32,11 @@ jobs:
|
||||
uses: crazy-max/ghaction-xgo@v2
|
||||
with:
|
||||
xgo_version: latest
|
||||
go_version: 1.21
|
||||
go_version: 1.20.x
|
||||
dest: dist
|
||||
pkg: cmd/dashboard
|
||||
prefix: dashboard
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64 # linux/386,
|
||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
||||
v: true
|
||||
x: false
|
||||
race: false
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<br>
|
||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||
<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.15.4-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.15.9-brightgreen?style=for-the-badge&logo=linux">
|
||||
<br>
|
||||
<br>
|
||||
<p>:trollface: <b>Nezha Monitoring: Self-hostable, lightweight, servers and websites monitoring and O&M tool.</b></p>
|
||||
@@ -38,117 +38,49 @@ You can change the dashboard language in the settings page (`/setting`) after th
|
||||
|
||||
## Contributors
|
||||
|
||||
<!--GAMFC_DELIMITER--><a href="https://github.com/naiba" title="naiba">
|
||||
<img src="https://avatars.githubusercontent.com/u/29243953?v=4" width="50;" alt="naiba"/>
|
||||
</a>
|
||||
<a href="https://github.com/AkkiaS7" title="Akkia">
|
||||
<img src="https://avatars.githubusercontent.com/u/68485070?v=4" width="50;" alt="Akkia"/>
|
||||
</a>
|
||||
<a href="https://github.com/Erope" title="卖女孩的小火柴">
|
||||
<img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/>
|
||||
</a>
|
||||
<a href="https://github.com/dysf888" title="黑歌">
|
||||
<img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/>
|
||||
</a>
|
||||
<a href="https://github.com/MikoyChinese" title="MikoyChinese">
|
||||
<img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/>
|
||||
</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/ilay1678" title="我若为王">
|
||||
<img src="https://avatars.githubusercontent.com/u/7021399?v=4" width="50;" alt="我若为王"/>
|
||||
</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/cantoblanco" title="Kris">
|
||||
<img src="https://avatars.githubusercontent.com/u/116849421?v=4" width="50;" alt="Kris"/>
|
||||
</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/applexad" title="Applexad">
|
||||
<img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="Applexad"/>
|
||||
</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/Mmx233" title="Mmx">
|
||||
<img src="https://avatars.githubusercontent.com/u/36563672?v=4" width="50;" alt="Mmx"/>
|
||||
</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/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/Creling" title="Creling">
|
||||
<img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/>
|
||||
</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/Es-dese" title="Esdese">
|
||||
<img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/>
|
||||
</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/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/KorenKrita" title="KorenKrita">
|
||||
<img src="https://avatars.githubusercontent.com/u/22239339?v=4" width="50;" alt="KorenKrita"/>
|
||||
</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/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/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/guoyongchang" title="guoyongchang">
|
||||
<img src="https://avatars.githubusercontent.com/u/10484506?v=4" width="50;" alt="guoyongchang"/>
|
||||
</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/nap0o" title="nap0o">
|
||||
<img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/>
|
||||
</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/ysicing" title="缘生">
|
||||
<img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/>
|
||||
</a>
|
||||
<a href="https://github.com/colour93" title="玖叁">
|
||||
<img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/>
|
||||
</a><!--GAMFC_DELIMITER_END-->
|
||||
<!--GAMFC_DELIMITER--><a href="https://github.com/naiba" title="naiba"><img src="https://avatars.githubusercontent.com/u/29243953?v=4" width="50;" alt="naiba"/></a>
|
||||
<a href="https://github.com/AkkiaS7" title="Akkia"><img src="https://avatars.githubusercontent.com/u/68485070?v=4" width="50;" alt="Akkia"/></a>
|
||||
<a href="https://github.com/Erope" title="卖女孩的小火柴"><img src="https://avatars.githubusercontent.com/u/44471469?v=4" width="50;" alt="卖女孩的小火柴"/></a>
|
||||
<a href="https://github.com/dysf888" title="黑歌"><img src="https://avatars.githubusercontent.com/u/47450409?v=4" width="50;" alt="黑歌"/></a>
|
||||
<a href="https://github.com/xykt" title="xykt"><img src="https://avatars.githubusercontent.com/u/152045469?v=4" width="50;" alt="xykt"/></a>
|
||||
<a href="https://github.com/MikoyChinese" title="MikoyChinese"><img src="https://avatars.githubusercontent.com/u/22676744?v=4" width="50;" alt="MikoyChinese"/></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/lemoeo" title="Lemoe"><img src="https://avatars.githubusercontent.com/u/18618627?v=4" width="50;" alt="Lemoe"/></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/nap0o" title="nap0o"><img src="https://avatars.githubusercontent.com/u/144927971?v=4" width="50;" alt="nap0o"/></a>
|
||||
<a href="https://github.com/uubulb" title="UUBulb"><img src="https://avatars.githubusercontent.com/u/35923940?v=4" width="50;" alt="UUBulb"/></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/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/hhhkkk520" title="Kris"><img src="https://avatars.githubusercontent.com/u/52115472?v=4" width="50;" alt="Kris"/></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/rootmelo92118" title="rootmelo92118"><img src="https://avatars.githubusercontent.com/u/32770959?v=4" width="50;" alt="rootmelo92118"/></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/colour93" title="玖叁"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="玖叁"/></a>
|
||||
<a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a>
|
||||
<a href="https://github.com/zhucaidan" title="zhucaidan"><img src="https://avatars.githubusercontent.com/u/47970938?v=4" width="50;" alt="zhucaidan"/></a>
|
||||
<a href="https://github.com/ysicing" title="缘生"><img src="https://avatars.githubusercontent.com/u/8605565?v=4" width="50;" alt="缘生"/></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/yuanweize" title="I"><img src="https://avatars.githubusercontent.com/u/30067203?v=4" width="50;" alt="I"/></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/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></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/hmsjy2017" title="Tony"><img src="https://avatars.githubusercontent.com/u/42692274?v=4" width="50;" alt="Tony"/></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/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/KorenKrita" title="KorenKrita"><img src="https://avatars.githubusercontent.com/u/22239339?v=4" width="50;" alt="KorenKrita"/></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/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><!--GAMFC_DELIMITER_END-->
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/naiba/nezha/pkg/mygin"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/mygin"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
)
|
||||
|
||||
type apiV1 struct {
|
||||
@@ -14,18 +17,32 @@ type apiV1 struct {
|
||||
|
||||
func (v *apiV1) serve() {
|
||||
r := v.r.Group("")
|
||||
// API
|
||||
// 强制认证的 API
|
||||
r.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||
Member: true,
|
||||
IsPage: false,
|
||||
AllowAPI: true,
|
||||
Msg: "访问此接口需要认证",
|
||||
Btn: "点此登录",
|
||||
Redirect: "/login",
|
||||
MemberOnly: true,
|
||||
AllowAPI: true,
|
||||
IsPage: false,
|
||||
Msg: "访问此接口需要认证",
|
||||
Btn: "点此登录",
|
||||
Redirect: "/login",
|
||||
}))
|
||||
r.GET("/server/list", v.serverList)
|
||||
r.GET("/server/details", v.serverDetails)
|
||||
|
||||
// 不强制认证的 API
|
||||
mr := v.r.Group("monitor")
|
||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||
MemberOnly: false,
|
||||
IsPage: false,
|
||||
AllowAPI: true,
|
||||
Msg: "访问此接口需要认证",
|
||||
Btn: "点此登录",
|
||||
Redirect: "/login",
|
||||
}))
|
||||
mr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
||||
IsPage: false,
|
||||
AbortWhenFail: true,
|
||||
}))
|
||||
mr.GET("/:id", v.monitorHistoriesById)
|
||||
}
|
||||
|
||||
// serverList 获取服务器列表 不传入Query参数则获取全部
|
||||
@@ -65,3 +82,31 @@ func (v *apiV1) serverDetails(c *gin.Context) {
|
||||
}
|
||||
c.JSON(200, singleton.ServerAPI.GetAllStatus())
|
||||
}
|
||||
|
||||
func (v *apiV1) monitorHistoriesById(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"code": 400, "message": "id参数错误"})
|
||||
return
|
||||
}
|
||||
server, ok := singleton.ServerList[id]
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(404, gin.H{
|
||||
"code": 404,
|
||||
"message": "id不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
|
||||
authorized := isMember || isViewPasswordVerfied
|
||||
|
||||
if server.HideForGuest && !authorized {
|
||||
c.AbortWithStatusJSON(403, gin.H{"code": 403, "message": "需要认证"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": server.ID}))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -43,11 +44,18 @@ type commonPage struct {
|
||||
func (cp *commonPage) serve() {
|
||||
cr := cp.r.Group("")
|
||||
cr.Use(mygin.Authorize(mygin.AuthorizeOption{}))
|
||||
cr.GET("/terminal/:id", cp.terminal)
|
||||
cr.Use(mygin.PreferredTheme)
|
||||
cr.POST("/view-password", cp.issueViewPassword)
|
||||
cr.Use(cp.checkViewPassword) // 前端查看密码鉴权
|
||||
cr.GET("/terminal/:id", cp.terminal)
|
||||
cr.Use(mygin.ValidateViewPassword(mygin.ValidateViewPasswordOption{
|
||||
IsPage: true,
|
||||
AbortWhenFail: true,
|
||||
}))
|
||||
cr.GET("/", cp.home)
|
||||
cr.GET("/service", cp.service)
|
||||
// TODO: 界面直接跳转使用该接口
|
||||
cr.GET("/network/:id", cp.network)
|
||||
cr.GET("/network", cp.network)
|
||||
cr.GET("/ws", cp.ws)
|
||||
cr.POST("/terminal", cp.createTerminal)
|
||||
}
|
||||
@@ -59,6 +67,7 @@ type viewPasswordForm struct {
|
||||
func (p *commonPage) issueViewPassword(c *gin.Context) {
|
||||
var vpf viewPasswordForm
|
||||
err := c.ShouldBind(&vpf)
|
||||
log.Println("bingo", vpf)
|
||||
var hash []byte
|
||||
if err == nil && vpf.Password != singleton.Conf.Site.ViewPassword {
|
||||
err = errors.New(singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "WrongAccessPassword"}))
|
||||
@@ -81,31 +90,6 @@ func (p *commonPage) issueViewPassword(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, c.Request.Referer())
|
||||
}
|
||||
|
||||
func (p *commonPage) checkViewPassword(c *gin.Context) {
|
||||
if singleton.Conf.Site.ViewPassword == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if _, authorized := c.Get(model.CtxKeyAuthorizedUser); authorized {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证查看密码
|
||||
viewPassword, _ := c.Cookie(singleton.Conf.Site.CookieName + "-vp")
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword)); err != nil {
|
||||
c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/viewpassword", mygin.CommonEnvironment(c, gin.H{
|
||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}),
|
||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
||||
}))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(model.CtxKeyViewPasswordVerified, true)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (p *commonPage) service(c *gin.Context) {
|
||||
res, _, _ := p.requestGroup.Do("servicePage", func() (interface{}, error) {
|
||||
singleton.AlertsLock.RLock()
|
||||
@@ -114,12 +98,17 @@ func (p *commonPage) service(c *gin.Context) {
|
||||
var statsStore map[uint64]model.CycleTransferStats
|
||||
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
|
||||
copier.Copy(&statsStore, singleton.AlertsCycleTransferStatsStore)
|
||||
for k, service := range stats {
|
||||
if !service.Monitor.EnableShowInService {
|
||||
delete(stats, k)
|
||||
}
|
||||
}
|
||||
return []interface {
|
||||
}{
|
||||
stats, statsStore,
|
||||
}, nil
|
||||
})
|
||||
c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/service", mygin.CommonEnvironment(c, gin.H{
|
||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/service"), mygin.CommonEnvironment(c, gin.H{
|
||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "ServicesStatus"}),
|
||||
"Services": res.([]interface{})[0],
|
||||
"CycleTransferStats": res.([]interface{})[1],
|
||||
@@ -127,6 +116,112 @@ func (p *commonPage) service(c *gin.Context) {
|
||||
}))
|
||||
}
|
||||
|
||||
func (cp *commonPage) network(c *gin.Context) {
|
||||
var (
|
||||
monitorHistory *model.MonitorHistory
|
||||
servers []*model.Server
|
||||
serverIdsWithMonitor []uint64
|
||||
monitorInfos = []byte("{}")
|
||||
id uint64
|
||||
)
|
||||
if len(singleton.SortedServerList) > 0 {
|
||||
id = singleton.SortedServerList[0].ID
|
||||
}
|
||||
if err := singleton.DB.Model(&model.MonitorHistory{}).Select("monitor_id, server_id").
|
||||
Where("monitor_id != 0 and server_id != 0").Limit(1).First(&monitorHistory).Error; err != nil {
|
||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
Code: http.StatusForbidden,
|
||||
Title: "请求失败",
|
||||
Msg: "请求参数有误:" + "server monitor history not found",
|
||||
Link: "/",
|
||||
Btn: "返回重试",
|
||||
}, true)
|
||||
return
|
||||
} else {
|
||||
if monitorHistory == nil || monitorHistory.ServerID == 0 {
|
||||
if len(singleton.SortedServerList) > 0 {
|
||||
id = singleton.SortedServerList[0].ID
|
||||
}
|
||||
} else {
|
||||
id = monitorHistory.ServerID
|
||||
}
|
||||
}
|
||||
|
||||
idStr := c.Param("id")
|
||||
if idStr != "" {
|
||||
var err error
|
||||
id, err = strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
Code: http.StatusForbidden,
|
||||
Title: "请求失败",
|
||||
Msg: "请求参数有误:" + err.Error(),
|
||||
Link: "/",
|
||||
Btn: "返回重试",
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
_, ok := singleton.ServerList[id]
|
||||
if !ok {
|
||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
Code: http.StatusForbidden,
|
||||
Title: "请求失败",
|
||||
Msg: "请求参数有误:" + "server id not found",
|
||||
Link: "/",
|
||||
Btn: "返回重试",
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
monitorHistories := singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": id})
|
||||
monitorInfos, _ = utils.Json.Marshal(monitorHistories)
|
||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
|
||||
|
||||
if err := singleton.DB.Model(&model.MonitorHistory{}).
|
||||
Select("distinct(server_id)").
|
||||
Where("server_id != 0").
|
||||
Find(&serverIdsWithMonitor).
|
||||
Error; err != nil {
|
||||
mygin.ShowErrorPage(c, mygin.ErrInfo{
|
||||
Code: http.StatusForbidden,
|
||||
Title: "请求失败",
|
||||
Msg: "请求参数有误:" + "no server with monitor histories",
|
||||
Link: "/",
|
||||
Btn: "返回重试",
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
if isMember || isViewPasswordVerfied {
|
||||
for _, server := range singleton.SortedServerList {
|
||||
for _, id := range serverIdsWithMonitor {
|
||||
if server.ID == id {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, server := range singleton.SortedServerListForGuest {
|
||||
for _, id := range serverIdsWithMonitor {
|
||||
if server.ID == id {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
serversBytes, _ := utils.Json.Marshal(Data{
|
||||
Now: time.Now().Unix() * 1000,
|
||||
Servers: servers,
|
||||
})
|
||||
|
||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/network"), mygin.CommonEnvironment(c, gin.H{
|
||||
"Servers": string(serversBytes),
|
||||
"MonitorInfos": string(monitorInfos),
|
||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
||||
"MaxTCPPingValue": singleton.Conf.MaxTCPPingValue,
|
||||
}))
|
||||
}
|
||||
|
||||
func (cp *commonPage) getServerStat(c *gin.Context) ([]byte, error) {
|
||||
v, err, _ := cp.requestGroup.Do("serverStats", func() (any, error) {
|
||||
singleton.SortedServerLock.RLock()
|
||||
@@ -165,7 +260,7 @@ func (cp *commonPage) home(c *gin.Context) {
|
||||
}, true)
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusOK, "theme-"+singleton.Conf.Site.Theme+"/home", mygin.CommonEnvironment(c, gin.H{
|
||||
c.HTML(http.StatusOK, mygin.GetPreferredTheme(c, "/home"), mygin.CommonEnvironment(c, gin.H{
|
||||
"Servers": string(stat),
|
||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
||||
}))
|
||||
|
||||
@@ -19,11 +19,11 @@ type guestPage struct {
|
||||
func (gp *guestPage) serve() {
|
||||
gr := gp.r.Group("")
|
||||
gr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||
Guest: true,
|
||||
IsPage: true,
|
||||
Msg: "您已登录",
|
||||
Btn: "返回首页",
|
||||
Redirect: "/",
|
||||
GuestOnly: true,
|
||||
IsPage: true,
|
||||
Msg: "您已登录",
|
||||
Btn: "返回首页",
|
||||
Redirect: "/",
|
||||
}))
|
||||
|
||||
gr.GET("/login", gp.login)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/copier"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/mygin"
|
||||
@@ -27,11 +28,11 @@ type memberAPI struct {
|
||||
func (ma *memberAPI) serve() {
|
||||
mr := ma.r.Group("")
|
||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||
Member: true,
|
||||
IsPage: false,
|
||||
Msg: "访问此接口需要登录",
|
||||
Btn: "点此登录",
|
||||
Redirect: "/login",
|
||||
MemberOnly: true,
|
||||
IsPage: false,
|
||||
Msg: "访问此接口需要登录",
|
||||
Btn: "点此登录",
|
||||
Redirect: "/login",
|
||||
}))
|
||||
|
||||
mr.GET("/search-server", ma.searchServer)
|
||||
@@ -185,7 +186,17 @@ func (ma *memberAPI) delete(c *gin.Context) {
|
||||
var err error
|
||||
switch c.Param("model") {
|
||||
case "server":
|
||||
err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
|
||||
err := singleton.DB.Transaction(func(tx *gorm.DB) error {
|
||||
err = singleton.DB.Unscoped().Delete(&model.Server{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "server_id = ?", id).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
// 删除服务器
|
||||
singleton.ServerLock.Lock()
|
||||
@@ -289,6 +300,10 @@ type serverForm struct {
|
||||
Tag string
|
||||
Note string
|
||||
HideForGuest string
|
||||
EnableDDNS string
|
||||
EnableIPv4 string
|
||||
EnableIpv6 string
|
||||
DDNSDomain string
|
||||
}
|
||||
|
||||
func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
@@ -304,6 +319,10 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) {
|
||||
s.Tag = sf.Tag
|
||||
s.Note = sf.Note
|
||||
s.HideForGuest = sf.HideForGuest == "on"
|
||||
s.EnableDDNS = sf.EnableDDNS == "on"
|
||||
s.EnableIPv4 = sf.EnableIPv4 == "on"
|
||||
s.EnableIpv6 = sf.EnableIpv6 == "on"
|
||||
s.DDNSDomain = sf.DDNSDomain
|
||||
if s.ID == 0 {
|
||||
s.Secret, err = utils.GenerateRandomString(18)
|
||||
if err == nil {
|
||||
@@ -383,6 +402,7 @@ type monitorForm struct {
|
||||
MaxLatency float32
|
||||
LatencyNotify string
|
||||
EnableTriggerTask string
|
||||
EnableShowInService string
|
||||
FailTriggerTasksRaw string
|
||||
RecoverTriggerTasksRaw string
|
||||
}
|
||||
@@ -404,6 +424,7 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
|
||||
m.LatencyNotify = mf.LatencyNotify == "on"
|
||||
m.MinLatency = mf.MinLatency
|
||||
m.MaxLatency = mf.MaxLatency
|
||||
m.EnableShowInService = mf.EnableShowInService == "on"
|
||||
m.EnableTriggerTask = mf.EnableTriggerTask == "on"
|
||||
m.RecoverTriggerTasksRaw = mf.RecoverTriggerTasksRaw
|
||||
m.FailTriggerTasksRaw = mf.FailTriggerTasksRaw
|
||||
@@ -427,6 +448,13 @@ func (ma *memberAPI) addOrEditMonitor(c *gin.Context) {
|
||||
err = singleton.DB.Save(&m).Error
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if m.Cover == 0 {
|
||||
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
|
||||
} else {
|
||||
err = singleton.DB.Unscoped().Delete(&model.MonitorHistory{}, "monitor_id = ? and server_id not in (?)", m.ID, strings.Split(m.SkipServersRaw[1:len(m.SkipServersRaw)-1], ",")).Error
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = singleton.ServiceSentinelShared.OnMonitorUpdate(m)
|
||||
@@ -834,8 +862,9 @@ type settingForm struct {
|
||||
GRPCHost string
|
||||
Cover uint8
|
||||
|
||||
EnableIPChangeNotification string
|
||||
EnablePlainIPInNotification string
|
||||
EnableIPChangeNotification string
|
||||
EnablePlainIPInNotification string
|
||||
DisableSwitchTemplateInFrontend string
|
||||
}
|
||||
|
||||
func (ma *memberAPI) updateSetting(c *gin.Context) {
|
||||
@@ -883,6 +912,7 @@ func (ma *memberAPI) updateSetting(c *gin.Context) {
|
||||
singleton.Conf.Language = sf.Language
|
||||
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification == "on"
|
||||
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification == "on"
|
||||
singleton.Conf.DisableSwitchTemplateInFrontend = sf.DisableSwitchTemplateInFrontend == "on"
|
||||
singleton.Conf.Cover = sf.Cover
|
||||
singleton.Conf.GRPCHost = sf.GRPCHost
|
||||
singleton.Conf.IgnoredIPNotification = sf.IgnoredIPNotification
|
||||
|
||||
@@ -17,11 +17,11 @@ type memberPage struct {
|
||||
func (mp *memberPage) serve() {
|
||||
mr := mp.r.Group("")
|
||||
mr.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||
Member: true,
|
||||
IsPage: true,
|
||||
Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}),
|
||||
Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
|
||||
Redirect: "/login",
|
||||
MemberOnly: true,
|
||||
IsPage: true,
|
||||
Msg: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "YouAreNotAuthorized"}),
|
||||
Btn: singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Login"}),
|
||||
Redirect: "/login",
|
||||
}))
|
||||
mr.GET("/server", mp.server)
|
||||
mr.GET("/monitor", mp.monitor)
|
||||
@@ -81,7 +81,6 @@ func (mp *memberPage) setting(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "dashboard-"+singleton.Conf.Site.DashboardTheme+"/setting", mygin.CommonEnvironment(c, gin.H{
|
||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "Settings"}),
|
||||
"Languages": model.Languages,
|
||||
"Themes": model.Themes,
|
||||
"DashboardThemes": model.DashboardThemes,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -49,10 +49,20 @@ func DispatchTask(serviceSentinelDispatchBus <-chan model.Monitor) {
|
||||
workedServerIndex++
|
||||
continue
|
||||
}
|
||||
if task.Cover == model.MonitorCoverIgnoreAll && task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
|
||||
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
|
||||
workedServerIndex++
|
||||
continue
|
||||
}
|
||||
if task.Cover == model.MonitorCoverAll && !task.SkipServers[singleton.SortedServerList[workedServerIndex].ID] {
|
||||
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
|
||||
workedServerIndex++
|
||||
continue
|
||||
}
|
||||
// 找到合适机器执行任务,跳出循环
|
||||
singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
|
||||
workedServerIndex++
|
||||
break
|
||||
// singleton.SortedServerList[workedServerIndex].TaskStream.Send(task.PB())
|
||||
// workedServerIndex++
|
||||
// break
|
||||
}
|
||||
singleton.SortedServerLock.RUnlock()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
module github.com/naiba/nezha
|
||||
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.3
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20231017140541-3b893ed0421b
|
||||
code.gitea.io/sdk/gitea v0.16.0
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca
|
||||
code.gitea.io/sdk/gitea v0.17.1
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/gin-contrib/pprof v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
@@ -15,32 +13,30 @@ require (
|
||||
github.com/hashicorp/go-uuid v1.0.3
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/ory/graceful v0.1.3
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/samber/lo v1.39.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/xanzy/go-gitlab v0.94.0
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/oauth2 v0.14.0
|
||||
golang.org/x/sync v0.5.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/xanzy/go-gitlab v0.102.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
google.golang.org/grpc v1.63.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.9
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
@@ -48,7 +44,6 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
|
||||
@@ -66,12 +61,11 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
@@ -79,11 +73,10 @@ require (
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,83 +1,25 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20231017140541-3b893ed0421b h1:/2OEIBwZAaJ8n8iTXrM4v/+bdyLDTLwcW6RZtkO4+r0=
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20231017140541-3b893ed0421b/go.mod h1:CKNYSQxmKcMCNIKoRG5rRR4AIgJMIoK65ya+Z5xHnk4=
|
||||
code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00=
|
||||
code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca h1:pyybVFhefCroOyqu71Rrnm0eORYFJMvTDsAsyPLjvEI=
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20240405144452-ebb2996022ca/go.mod h1:21qd3QlOUZT1oQ2RccnLvWRu7CZWSN+3ZlUh2mzoE10=
|
||||
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
|
||||
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
|
||||
@@ -89,14 +31,9 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@@ -107,97 +44,34 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-github/v47 v47.1.0 h1:Cacm/WxQBOa9lF0FT0EMjZ2BWMetQ1TQfyurn4yF1z8=
|
||||
github.com/google/go-github/v47 v47.1.0/go.mod h1:VPZBXNbFSJGjyjFRUKo9vZGawTajnWzC/YjGw/oFKi0=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -206,18 +80,13 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -228,7 +97,6 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
@@ -241,12 +109,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.2 h1:Iv/FL6pvYmDqybEZkr4TrOv8jSHezwpE77K68kcaft8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.2/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
|
||||
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
|
||||
github.com/ory/graceful v0.1.3 h1:FaeXcHZh168WzS+bqruqWEw/HgXWLdNv2nJ+fbhxbhc=
|
||||
github.com/ory/graceful v0.1.3/go.mod h1:4zFz687IAF7oNHHiB586U4iL+/4aV09o/PYLE34t2bA=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
@@ -257,40 +123,34 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
||||
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -308,19 +168,9 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xanzy/go-gitlab v0.94.0 h1:GmBl2T5zqUHqyjkxFSvsT7CbelGdAH/dmBqUBqS+4BE=
|
||||
github.com/xanzy/go-gitlab v0.94.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
|
||||
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
@@ -329,329 +179,78 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -660,27 +259,16 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
const CtxKeyAuthorizedUser = "ckau"
|
||||
const CtxKeyViewPasswordVerified = "ckvpv"
|
||||
const CtxKeyPreferredTheme = "ckpt"
|
||||
const CacheKeyOauth2State = "p:a:state"
|
||||
|
||||
type Common struct {
|
||||
|
||||
+32
-1
@@ -98,7 +98,8 @@ type Config struct {
|
||||
ProxyGRPCPort uint
|
||||
TLS bool
|
||||
|
||||
EnablePlainIPInNotification bool // 通知信息IP不打码
|
||||
EnablePlainIPInNotification bool // 通知信息IP不打码
|
||||
DisableSwitchTemplateInFrontend bool // 前台禁用切换模板功能
|
||||
|
||||
// IP变更提醒
|
||||
EnableIPChangeNotification bool
|
||||
@@ -110,6 +111,21 @@ type Config struct {
|
||||
|
||||
v *viper.Viper
|
||||
IgnoredIPNotificationServerIDs map[uint64]bool // [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内)
|
||||
MaxTCPPingValue int32
|
||||
AvgPingCount int
|
||||
|
||||
// 动态域名解析更新
|
||||
DDNS struct {
|
||||
Enable bool
|
||||
Provider string
|
||||
AccessID string
|
||||
AccessSecret string
|
||||
WebhookURL string
|
||||
WebhookMethod string
|
||||
WebhookRequestBody string
|
||||
WebhookHeaders string
|
||||
MaxRetries uint32
|
||||
}
|
||||
}
|
||||
|
||||
// Read 读取配置文件并应用
|
||||
@@ -144,6 +160,21 @@ func (c *Config) Read(path string) error {
|
||||
if c.Location == "" {
|
||||
c.Location = "Asia/Shanghai"
|
||||
}
|
||||
if c.MaxTCPPingValue == 0 {
|
||||
c.MaxTCPPingValue = 1000
|
||||
}
|
||||
if c.AvgPingCount == 0 {
|
||||
c.AvgPingCount = 2
|
||||
}
|
||||
if c.DDNS.Provider == "" {
|
||||
c.DDNS.Provider = "webhook"
|
||||
}
|
||||
if c.DDNS.WebhookMethod == "" {
|
||||
c.DDNS.WebhookMethod = "POST"
|
||||
}
|
||||
if c.DDNS.MaxRetries == 0 {
|
||||
c.DDNS.MaxRetries = 3
|
||||
}
|
||||
|
||||
c.updateIgnoredIPNotificationID()
|
||||
return nil
|
||||
|
||||
+4
-2
@@ -4,10 +4,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
pb "github.com/naiba/nezha/proto"
|
||||
"github.com/robfig/cron/v3"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
pb "github.com/naiba/nezha/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,6 +50,7 @@ type Monitor struct {
|
||||
Cover uint8
|
||||
|
||||
EnableTriggerTask bool `gorm:"default: false"`
|
||||
EnableShowInService bool `gorm:"default: false"`
|
||||
FailTriggerTasksRaw string `gorm:"default:'[]'"`
|
||||
RecoverTriggerTasksRaw string `gorm:"default:'[]'"`
|
||||
FailTriggerTasks []uint64 `gorm:"-" json:"-"` // 失败时执行的触发任务id
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MonitorHistory 历史监控记录
|
||||
type MonitorHistory struct {
|
||||
Common
|
||||
MonitorID uint64
|
||||
AvgDelay float32 // 平均延迟,毫秒
|
||||
Up uint64 // 检查状态良好计数
|
||||
Down uint64 // 检查状态异常计数
|
||||
ID uint64 `gorm:"primaryKey"`
|
||||
CreatedAt time.Time `gorm:"index;<-:create;index:idx_server_id_created_at_monitor_id_avg_delay"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
MonitorID uint64 `gorm:"index:idx_server_id_created_at_monitor_id_avg_delay"`
|
||||
ServerID uint64 `gorm:"index:idx_server_id_created_at_monitor_id_avg_delay"`
|
||||
AvgDelay float32 `gorm:"index:idx_server_id_created_at_monitor_id_avg_delay"` // 平均延迟,毫秒
|
||||
Up uint64 // 检查状态良好计数
|
||||
Down uint64 // 检查状态异常计数
|
||||
Data string
|
||||
}
|
||||
|
||||
|
||||
+24
-10
@@ -5,8 +5,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -85,21 +83,37 @@ func execCase(t *testing.T, item testSt) {
|
||||
Server: &server,
|
||||
Loc: time.Local,
|
||||
}
|
||||
assert.Equal(t, item.expectURL, ns.reqURL(msg))
|
||||
if item.expectURL != ns.reqURL(msg) {
|
||||
t.Fatalf("Expected %s, but got %s", item.expectURL, ns.reqURL(msg))
|
||||
}
|
||||
reqBody, err := ns.reqBody(msg)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, item.expectBody, reqBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s", err)
|
||||
}
|
||||
if item.expectBody != reqBody {
|
||||
t.Fatalf("Expected %s, but got %s", item.expectBody, reqBody)
|
||||
}
|
||||
reqMethod, err := n.reqMethod()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, item.expectMethod, reqMethod)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s", err)
|
||||
}
|
||||
if item.expectMethod != reqMethod {
|
||||
t.Fatalf("Expected %s, but got %s", item.expectMethod, reqMethod)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("", "", strings.NewReader(""))
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s", err)
|
||||
}
|
||||
n.setContentType(req)
|
||||
assert.Equal(t, item.expectContentType, req.Header.Get("Content-Type"))
|
||||
if item.expectContentType != req.Header.Get("Content-Type") {
|
||||
t.Fatalf("Expected %s, but got %s", item.expectContentType, req.Header.Get("Content-Type"))
|
||||
}
|
||||
n.setRequestHeader(req)
|
||||
for k, v := range item.expectHeader {
|
||||
assert.Equal(t, v, req.Header.Get(k))
|
||||
if v != req.Header.Get(k) {
|
||||
t.Fatalf("Expected %s, but got %s", v, req.Header.Get(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -17,6 +17,10 @@ type Server struct {
|
||||
Note string `json:"-"` // 管理员可见备注
|
||||
DisplayIndex int // 展示排序,越大越靠前
|
||||
HideForGuest bool // 对游客隐藏
|
||||
EnableDDNS bool // 是否启用DDNS 未在配置文件中启用DDNS 或 DDNS检查时间为0时此项无效
|
||||
EnableIPv4 bool // 是否启用DDNS IPv4
|
||||
EnableIpv6 bool // 是否启用DDNS IPv6
|
||||
DDNSDomain string // DDNS中的前缀 如基础域名为abc.oracle DDNSName为mjj 就会把mjj.abc.oracle解析服务器IP 为空则停用
|
||||
|
||||
Host *Host `gorm:"-"`
|
||||
State *HostState `gorm:"-"`
|
||||
@@ -51,5 +55,6 @@ func (s Server) Marshal() template.JS {
|
||||
tag, _ := utils.Json.Marshal(s.Tag)
|
||||
note, _ := utils.Json.Marshal(s.Note)
|
||||
secret, _ := utils.Json.Marshal(s.Secret)
|
||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest))) // #nosec
|
||||
ddnsDomain, _ := utils.Json.Marshal(s.DDNSDomain)
|
||||
return template.JS(fmt.Sprintf(`{"ID":%d,"Name":%s,"Secret":%s,"DisplayIndex":%d,"Tag":%s,"Note":%s,"HideForGuest": %s,"EnableDDNS": %s,"EnableIPv4": %s,"EnableIpv6": %s,"DDNSDomain": %s}`, s.ID, name, secret, s.DisplayIndex, tag, note, boolToString(s.HideForGuest), boolToString(s.EnableDDNS), boolToString(s.EnableIPv4), boolToString(s.EnableIpv6), ddnsDomain)) // #nosec
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServerMarshal(t *testing.T) {
|
||||
@@ -21,7 +20,11 @@ func TestServerMarshal(t *testing.T) {
|
||||
}
|
||||
serverStr := string(server.Marshal())
|
||||
var serverRestore Server
|
||||
assert.Nil(t, utils.Json.Unmarshal([]byte(serverStr), &serverRestore))
|
||||
assert.Equal(t, server, serverRestore)
|
||||
if utils.Json.Unmarshal([]byte(serverStr), &serverRestore) != nil {
|
||||
t.Fatalf("Error: %s", serverStr)
|
||||
}
|
||||
if server.Name != serverRestore.Name {
|
||||
t.Fatalf("Expected %s, but got %s", server.Name, serverRestore.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ProviderCloudflare struct {
|
||||
Secret string
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
|
||||
if domainConfig == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
zoneID, err := provider.getZoneID(domainConfig.FullDomain)
|
||||
if err != nil {
|
||||
log.Printf("无法获取 zone ID: %s\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// 当IPv4和IPv6同时成功才算作成功
|
||||
var resultV4 = true
|
||||
var resultV6 = true
|
||||
if domainConfig.EnableIPv4 {
|
||||
if !provider.addDomainRecord(zoneID, domainConfig, true) {
|
||||
resultV4 = false
|
||||
}
|
||||
}
|
||||
|
||||
if domainConfig.EnableIpv6 {
|
||||
if !provider.addDomainRecord(zoneID, domainConfig, false) {
|
||||
resultV6 = false
|
||||
}
|
||||
}
|
||||
|
||||
return resultV4 && resultV6
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
|
||||
record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain, isIpv4)
|
||||
if err != nil {
|
||||
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
// 添加 DNS 记录
|
||||
return provider.createDNSRecord(zoneID, domainConfig, isIpv4)
|
||||
} else {
|
||||
// 更新 DNS 记录
|
||||
return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig, isIpv4)
|
||||
}
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) getZoneID(domain string) (string, error) {
|
||||
_, realDomain := SplitDomain(domain)
|
||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
|
||||
body, err := provider.sendRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var res map[string]interface{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := res["result"].([]interface{})
|
||||
if len(result) > 0 {
|
||||
zoneID := result[0].(map[string]interface{})["id"].(string)
|
||||
return zoneID, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("找不到 Zone ID")
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
|
||||
var ipType = "A"
|
||||
if !isIPv4 {
|
||||
ipType = "AAAA"
|
||||
}
|
||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=%s&name=%s", zoneID, ipType, domain)
|
||||
body, err := provider.sendRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res map[string]interface{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := res["result"].([]interface{})
|
||||
if len(result) > 0 {
|
||||
return result[0].(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
return nil, nil // 没有找到 DNS 记录
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
||||
var ipType = "A"
|
||||
var ipAddr = domainConfig.Ipv4Addr
|
||||
if !isIPv4 {
|
||||
ipType = "AAAA"
|
||||
ipAddr = domainConfig.Ipv6Addr
|
||||
}
|
||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
|
||||
data := map[string]interface{}{
|
||||
"type": ipType,
|
||||
"name": domainConfig.FullDomain,
|
||||
"content": ipAddr,
|
||||
"ttl": 60,
|
||||
"proxied": false,
|
||||
}
|
||||
jsonData, _ := json.Marshal(data)
|
||||
_, err := provider.sendRequest("POST", url, jsonData)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (provider ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
||||
var ipType = "A"
|
||||
var ipAddr = domainConfig.Ipv4Addr
|
||||
if !isIPv4 {
|
||||
ipType = "AAAA"
|
||||
ipAddr = domainConfig.Ipv6Addr
|
||||
}
|
||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
|
||||
data := map[string]interface{}{
|
||||
"type": ipType,
|
||||
"name": domainConfig.FullDomain,
|
||||
"content": ipAddr,
|
||||
"ttl": 60,
|
||||
"proxied": false,
|
||||
}
|
||||
jsonData, _ := json.Marshal(data)
|
||||
_, err := provider.sendRequest("PATCH", url, jsonData)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// 以下为辅助方法,如发送 HTTP 请求等
|
||||
func (provider ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ddns
|
||||
|
||||
type DomainConfig struct {
|
||||
EnableIPv4 bool
|
||||
EnableIpv6 bool
|
||||
FullDomain string
|
||||
Ipv4Addr string
|
||||
Ipv6Addr string
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
// UpdateDomain Return is updated
|
||||
UpdateDomain(domainConfig *DomainConfig) bool
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ddns
|
||||
|
||||
type ProviderDummy struct{}
|
||||
|
||||
func (provider ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ddns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (provider ProviderWebHook) FormatWebhookString(s string, config *DomainConfig, ipType string) string {
|
||||
if config == nil {
|
||||
return s
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(s)
|
||||
result = strings.Replace(s, "{ip}", config.Ipv4Addr, -1)
|
||||
result = strings.Replace(result, "{domain}", config.FullDomain, -1)
|
||||
result = strings.Replace(result, "{type}", ipType, -1)
|
||||
// remove \r
|
||||
result = strings.Replace(result, "\r", "", -1)
|
||||
return result
|
||||
}
|
||||
|
||||
func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
for _, element := range headers {
|
||||
kv := strings.SplitN(element, ":", 2)
|
||||
if len(kv) == 2 {
|
||||
req.Header.Add(kv[0], kv[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SplitDomain 分割域名为前缀和一级域名
|
||||
func SplitDomain(domain string) (prefix string, topLevelDomain string) {
|
||||
// 带有二级TLD的一些常见例子,需要特别处理
|
||||
secondLevelTLDs := map[string]bool{
|
||||
".co.uk": true, ".com.cn": true, ".gov.cn": true, ".net.cn": true, ".org.cn": true,
|
||||
}
|
||||
|
||||
// 分割域名为"."的各部分
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// 处理特殊情况,例如 ".co.uk"
|
||||
for i := len(parts) - 2; i > 0; i-- {
|
||||
potentialTLD := fmt.Sprintf(".%s.%s", parts[i], parts[i+1])
|
||||
if secondLevelTLDs[potentialTLD] {
|
||||
if i > 1 {
|
||||
return strings.Join(parts[:i-1], "."), strings.Join(parts[i-1:], ".")
|
||||
}
|
||||
return "", domain // 当域名仅为二级TLD时,无前缀
|
||||
}
|
||||
}
|
||||
|
||||
// 常规处理,查找最后一个"."前的所有内容作为前缀
|
||||
if len(parts) > 2 {
|
||||
return strings.Join(parts[:len(parts)-2], "."), strings.Join(parts[len(parts)-2:], ".")
|
||||
}
|
||||
return "", domain // 当域名不包含子域名时,无前缀
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package ddns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ProviderWebHook struct {
|
||||
URL string
|
||||
RequestMethod string
|
||||
RequestBody string
|
||||
RequestHeader string
|
||||
}
|
||||
|
||||
func (provider ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
|
||||
if domainConfig == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if domainConfig.FullDomain == "" {
|
||||
log.Println("NEZHA>> Failed to update an empty domain")
|
||||
return false
|
||||
}
|
||||
updated := false
|
||||
client := &http.Client{}
|
||||
if domainConfig.EnableIPv4 && domainConfig.Ipv4Addr != "" {
|
||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv4")
|
||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv4")
|
||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv4")
|
||||
headers := strings.Split(header, "\n")
|
||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
||||
if err == nil && req != nil {
|
||||
SetStringHeadersToRequest(req, headers)
|
||||
if _, err := client.Do(req); err != nil {
|
||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
||||
} else {
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if domainConfig.EnableIpv6 && domainConfig.Ipv6Addr != "" {
|
||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv6")
|
||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv6")
|
||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv6")
|
||||
headers := strings.Split(header, "\n")
|
||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
||||
if err == nil && req != nil {
|
||||
SetStringHeadersToRequest(req, headers)
|
||||
if _, err := client.Do(req); err != nil {
|
||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
||||
} else {
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated
|
||||
}
|
||||
+12
-10
@@ -12,19 +12,19 @@ import (
|
||||
)
|
||||
|
||||
type AuthorizeOption struct {
|
||||
Guest bool
|
||||
Member bool
|
||||
IsPage bool
|
||||
AllowAPI bool
|
||||
Msg string
|
||||
Redirect string
|
||||
Btn string
|
||||
GuestOnly bool
|
||||
MemberOnly bool
|
||||
IsPage bool
|
||||
AllowAPI bool
|
||||
Msg string
|
||||
Redirect string
|
||||
Btn string
|
||||
}
|
||||
|
||||
func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
var code = http.StatusForbidden
|
||||
if opt.Guest {
|
||||
if opt.GuestOnly {
|
||||
code = http.StatusBadRequest
|
||||
}
|
||||
|
||||
@@ -67,13 +67,15 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 已登录且只能游客访问
|
||||
if isLogin && opt.Guest {
|
||||
if isLogin && opt.GuestOnly {
|
||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
||||
return
|
||||
}
|
||||
|
||||
// 未登录且需要登录
|
||||
if !isLogin && opt.Member {
|
||||
if !isLogin && opt.MemberOnly {
|
||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
|
||||
data["MatchedPath"] = c.MustGet("MatchedPath")
|
||||
data["Version"] = singleton.Version
|
||||
data["Conf"] = singleton.Conf
|
||||
data["Themes"] = model.Themes
|
||||
// 是否是管理页面
|
||||
data["IsAdminPage"] = adminPage[data["MatchedPath"].(string)]
|
||||
// 站点标题
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package mygin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
)
|
||||
|
||||
func PreferredTheme(c *gin.Context) {
|
||||
// 采用前端传入的主题
|
||||
if theme, err := c.Cookie("preferred_theme"); err == nil {
|
||||
if _, has := model.Themes[theme]; has {
|
||||
// 检验自定义主题
|
||||
if theme == "custom" && singleton.Conf.Site.Theme != "custom" && !utils.IsFileExists("resource/template/theme-custom/home.html") {
|
||||
return
|
||||
}
|
||||
c.Set(model.CtxKeyPreferredTheme, theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetPreferredTheme(c *gin.Context, path string) string {
|
||||
if theme, has := c.Get(model.CtxKeyPreferredTheme); has {
|
||||
return fmt.Sprintf("theme-%s%s", theme, path)
|
||||
}
|
||||
return fmt.Sprintf("theme-%s%s", singleton.Conf.Site.Theme, path)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mygin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/service/singleton"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type ValidateViewPasswordOption struct {
|
||||
IsPage bool
|
||||
AbortWhenFail bool
|
||||
}
|
||||
|
||||
func ValidateViewPassword(opt ValidateViewPasswordOption) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if singleton.Conf.Site.ViewPassword == "" {
|
||||
return
|
||||
}
|
||||
_, authorized := c.Get(model.CtxKeyAuthorizedUser)
|
||||
if authorized {
|
||||
return
|
||||
}
|
||||
viewPassword, err := c.Cookie(singleton.Conf.Site.CookieName + "-vp")
|
||||
if err == nil {
|
||||
err = bcrypt.CompareHashAndPassword([]byte(viewPassword), []byte(singleton.Conf.Site.ViewPassword))
|
||||
}
|
||||
if err == nil {
|
||||
c.Set(model.CtxKeyViewPasswordVerified, true)
|
||||
return
|
||||
}
|
||||
if !opt.AbortWhenFail {
|
||||
return
|
||||
}
|
||||
if opt.IsPage {
|
||||
c.HTML(http.StatusOK, GetPreferredTheme(c, "/viewpassword"), CommonEnvironment(c, gin.H{
|
||||
"Title": singleton.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "VerifyPassword"}),
|
||||
"CustomCode": singleton.Conf.Site.CustomCode,
|
||||
}))
|
||||
|
||||
} else {
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: http.StatusForbidden,
|
||||
Message: "访问受限",
|
||||
})
|
||||
}
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -2,8 +2,6 @@ package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testSt struct {
|
||||
@@ -36,7 +34,9 @@ func TestNotification(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
assert.Equal(t, IPDesensitize(c.input), c.output)
|
||||
if c.output != IPDesensitize(c.input) {
|
||||
t.Fatalf("Expected %s, but got %s", c.output, IPDesensitize(c.input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,15 @@ func TestGenerGenerateRandomString(t *testing.T) {
|
||||
generatedString := make(map[string]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
str, err := GenerateRandomString(32)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(str), 32)
|
||||
assert.False(t, generatedString[str])
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s", err)
|
||||
}
|
||||
if len(str) != 32 {
|
||||
t.Fatalf("Expected 32, but got %d", len(str))
|
||||
}
|
||||
if generatedString[str] {
|
||||
t.Fatalf("Duplicated string: %s", str)
|
||||
}
|
||||
generatedString[str] = true
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+40
-10
@@ -32,10 +32,10 @@ other = "Logout"
|
||||
other = "Login"
|
||||
|
||||
[ConfirmLogout]
|
||||
other = "Confirm Logout?"
|
||||
other = "Do you want to Logout?"
|
||||
|
||||
[AfterLoggingOutYouHaveToLoginAgain]
|
||||
other = "You need to login again after logout to continue"
|
||||
other = "You need to Login again to continue"
|
||||
|
||||
[Cancel]
|
||||
other = "Cancel"
|
||||
@@ -227,7 +227,7 @@ other = "Secret"
|
||||
other = "Note"
|
||||
|
||||
[LinuxOneKeyInstall]
|
||||
other = "Linux One-Key Install"
|
||||
other = "Linux One-Command Install"
|
||||
|
||||
[NoDomainAlert]
|
||||
other = "Set CDN bypassed Domain/IP in setting before using this function"
|
||||
@@ -257,7 +257,7 @@ other = "Execute by Trigger"
|
||||
other = "Delete Scheduled Task"
|
||||
|
||||
[ConfirmToDeleteThisScheduledTask]
|
||||
other = "Confirm Delete?"
|
||||
other = "Are you sure you want to Delete?"
|
||||
|
||||
[AccessDenied]
|
||||
other = "Access Denied"
|
||||
@@ -293,7 +293,7 @@ other = "Delete Notification Method"
|
||||
other = "Confirm to Delete This Notification Method?"
|
||||
|
||||
[ForceUpdate]
|
||||
other = "Force Update"
|
||||
other = "Force to Update"
|
||||
|
||||
[SelectAll]
|
||||
other = "Select All"
|
||||
@@ -302,7 +302,7 @@ other = "Select All"
|
||||
other = "Version"
|
||||
|
||||
[OneKeyInstall]
|
||||
other = "One-Key Install"
|
||||
other = "One-Command Install"
|
||||
|
||||
[ClickToCopy]
|
||||
other = "Click To Copy"
|
||||
@@ -398,7 +398,7 @@ other = "Virtualization"
|
||||
other = "Swap"
|
||||
|
||||
[NetTransfer]
|
||||
other = "Network Transfer"
|
||||
other = "Usage"
|
||||
|
||||
[Load]
|
||||
other = "Load"
|
||||
@@ -419,7 +419,7 @@ other = "Last Active"
|
||||
other = "Version"
|
||||
|
||||
[NetSpeed]
|
||||
other = "Network Speed"
|
||||
other = "NIC"
|
||||
|
||||
[Uptime]
|
||||
other = "Uptime"
|
||||
@@ -473,7 +473,7 @@ other = "Next check"
|
||||
other = "Current Usage"
|
||||
|
||||
[VerifyPassword]
|
||||
other = "Verify Password"
|
||||
other = "Please enter your password"
|
||||
|
||||
[LightMode]
|
||||
other = "Light Mode"
|
||||
@@ -482,7 +482,7 @@ other = "Light Mode"
|
||||
other = "Dark Mode"
|
||||
|
||||
[FollowSystem]
|
||||
other = "Follow System"
|
||||
other = "System Auto"
|
||||
|
||||
[GridLayout]
|
||||
other = "Grid Layout"
|
||||
@@ -609,3 +609,33 @@ other = "Hide for Guest"
|
||||
|
||||
[Menu]
|
||||
other = "Menu"
|
||||
|
||||
[NetworkSpiter]
|
||||
other = "Network"
|
||||
|
||||
[EnableShowInService]
|
||||
other = "Enable Show in Service"
|
||||
|
||||
[EnableDDNS]
|
||||
other = "Enable DDNS"
|
||||
|
||||
[EnableIPv4]
|
||||
other = "Enable DDNS IPv4"
|
||||
|
||||
[EnableIpv6]
|
||||
other = "Enable DDNS IPv6"
|
||||
|
||||
[DDNSDomain]
|
||||
other = "DDNS Domain"
|
||||
|
||||
[Feature]
|
||||
other = "Feature"
|
||||
|
||||
[Template]
|
||||
other = "Template"
|
||||
|
||||
[Stat]
|
||||
other = "Asset"
|
||||
|
||||
[DisableSwitchTemplateInFrontend]
|
||||
other = "Disable Switch Template in Frontend"
|
||||
|
||||
Vendored
+31
-1
@@ -203,7 +203,7 @@ other = "Agregar Servidor"
|
||||
other = "Editar Grupo de Servidores en Lote"
|
||||
|
||||
[BatchDeleteServer]
|
||||
other = "Eliminar Servidores en Lote""
|
||||
other = "Eliminar Servidores en Lote"
|
||||
|
||||
[InputServerGroupName]
|
||||
other = "Ingrese Nombre del Grupo de Servidores"
|
||||
@@ -609,3 +609,33 @@ other = "Ocultar para Invitados"
|
||||
|
||||
[Menu]
|
||||
other = "Menú"
|
||||
|
||||
[NetworkSpiter]
|
||||
other = "Red"
|
||||
|
||||
[EnableShowInService]
|
||||
other = "Mostrar en servicio"
|
||||
|
||||
[EnableDDNS]
|
||||
other = "Habilitar DDNS"
|
||||
|
||||
[EnableIPv4]
|
||||
other = "Habilitar DDNS IPv4"
|
||||
|
||||
[EnableIpv6]
|
||||
other = "Habilitar DDNS IPv6"
|
||||
|
||||
[DDNSDomain]
|
||||
other = "Dominio DDNS"
|
||||
|
||||
[Feature]
|
||||
other = "Característica"
|
||||
|
||||
[Template]
|
||||
other = "Plantilla"
|
||||
|
||||
[Stat]
|
||||
other = "Stat"
|
||||
|
||||
[DisableSwitchTemplateInFrontend]
|
||||
other = "Deshabilitar Cambio de Plantilla en Frontend"
|
||||
Vendored
+31
-1
@@ -608,4 +608,34 @@ other = "信息"
|
||||
other = "对游客隐藏"
|
||||
|
||||
[Menu]
|
||||
other = "菜单"
|
||||
other = "菜单"
|
||||
|
||||
[NetworkSpiter]
|
||||
other = "网络"
|
||||
|
||||
[EnableShowInService]
|
||||
other = "在服务中显示"
|
||||
|
||||
[EnableDDNS]
|
||||
other = "启用DDNS"
|
||||
|
||||
[EnableIPv4]
|
||||
other = "启用DDNS IPv4"
|
||||
|
||||
[EnableIpv6]
|
||||
other = "启用DDNS IPv6"
|
||||
|
||||
[DDNSDomain]
|
||||
other = "DDNS域名"
|
||||
|
||||
[Feature]
|
||||
other = "功能"
|
||||
|
||||
[Template]
|
||||
other = "主题"
|
||||
|
||||
[Stat]
|
||||
other = "信息"
|
||||
|
||||
[DisableSwitchTemplateInFrontend]
|
||||
other = "禁止前台切换模板"
|
||||
|
||||
Vendored
+31
-1
@@ -608,4 +608,34 @@ other = "信息"
|
||||
other = "對遊客隱藏"
|
||||
|
||||
[Menu]
|
||||
other = "菜單"
|
||||
other = "菜單"
|
||||
|
||||
[NetworkSpiter]
|
||||
other = "網絡"
|
||||
|
||||
[EnableShowInService]
|
||||
other = "在服務中顯示"
|
||||
|
||||
[EnableDDNS]
|
||||
other = "啟用DDNS"
|
||||
|
||||
[EnableIPv4]
|
||||
other = "啟用DDNS IPv4"
|
||||
|
||||
[EnableIpv6]
|
||||
other = "啟用DDNS IPv6"
|
||||
|
||||
[DDNSDomain]
|
||||
other = "DDNS網域"
|
||||
|
||||
[Feature]
|
||||
other = "功能"
|
||||
|
||||
[Template]
|
||||
other = "主題"
|
||||
|
||||
[Stat]
|
||||
other = "信息"
|
||||
|
||||
[DisableSwitchTemplateInFrontend]
|
||||
other = "禁止前台切換主題"
|
||||
|
||||
@@ -302,6 +302,7 @@ function addOrEditServer(server, conf) {
|
||||
modal.find("input[name=id]").val(server ? server.ID : null);
|
||||
modal.find("input[name=name]").val(server ? server.Name : null);
|
||||
modal.find("input[name=Tag]").val(server ? server.Tag : null);
|
||||
modal.find("input[name=DDNSDomain]").val(server ? server.DDNSDomain : null);
|
||||
modal
|
||||
.find("input[name=DisplayIndex]")
|
||||
.val(server ? server.DisplayIndex : null);
|
||||
@@ -321,6 +322,21 @@ function addOrEditServer(server, conf) {
|
||||
} else {
|
||||
modal.find(".ui.hideforguest.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
if (server && server.EnableDDNS) {
|
||||
modal.find(".ui.enableddns.checkbox").checkbox("set checked");
|
||||
} else {
|
||||
modal.find(".ui.enableddns.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
if (server && server.EnableIPv4) {
|
||||
modal.find(".ui.enableipv4.checkbox").checkbox("set checked");
|
||||
} else {
|
||||
modal.find(".ui.enableipv4.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
if (server && server.EnableIpv6) {
|
||||
modal.find(".ui.enableipv6.checkbox").checkbox("set checked");
|
||||
} else {
|
||||
modal.find(".ui.enableipv6.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
showFormModal(".server.modal", "#serverForm", "/api/server");
|
||||
}
|
||||
|
||||
@@ -339,6 +355,11 @@ function addOrEditMonitor(monitor) {
|
||||
modal.find("select[name=Type]").val(monitor ? monitor.Type : 1);
|
||||
modal.find("select[name=Cover]").val(monitor ? monitor.Cover : 0);
|
||||
modal.find("input[name=NotificationTag]").val(monitor ? monitor.NotificationTag : null);
|
||||
if (monitor && monitor.EnableShowInService) {
|
||||
modal.find(".ui.nb-show-in-service.checkbox").checkbox("set checked")
|
||||
} else {
|
||||
modal.find(".ui.nb-show-in-service.checkbox").checkbox("set unchecked")
|
||||
}
|
||||
if (monitor && monitor.Notify) {
|
||||
modal.find(".ui.nb-notify.checkbox").checkbox("set checked");
|
||||
} else {
|
||||
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/* 屏幕适配 */
|
||||
@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) {
|
||||
.ui.card>.content>.header:not(.ui),.ui.cards>.card>.content>.header:not(.ui) {
|
||||
margin-top:0.4em !important;
|
||||
}
|
||||
.ui.menu .item>img:not(.ui){
|
||||
width: 2.2rem;
|
||||
}
|
||||
.ui.menu .item:before{
|
||||
width:0.5px;
|
||||
}
|
||||
.ui.menu .item{
|
||||
padding: 0.9rem 0.55rem;
|
||||
}
|
||||
.ui.large.menu{
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
i.icon {
|
||||
color:#000;
|
||||
width:1.2em !important;
|
||||
}
|
||||
i.fi {
|
||||
width:0.9em;
|
||||
margin:0px 6px 0px 2px;
|
||||
}
|
||||
|
||||
body {
|
||||
content:" " !important;
|
||||
background:fixed !important;
|
||||
z-index:-1 !important;
|
||||
top:0 !important;
|
||||
right:0 !important;
|
||||
bottom:0 !important;
|
||||
left:0 !important;
|
||||
}
|
||||
|
||||
td {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.nb-container {
|
||||
padding-top: 75px;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 65px;
|
||||
margin-bottom: -47px;
|
||||
}
|
||||
|
||||
#app .ui.fluid.accordion {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login.nb-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: unset;
|
||||
}
|
||||
|
||||
.login.nb-container > .grid {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.login.nb-container > .grid .column {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.ui.menu .item-right:before{
|
||||
width:0px;
|
||||
}
|
||||
|
||||
.status.cards .flag {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.status.cards .header > .info.icon {
|
||||
float: right;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ui.grid {
|
||||
margin-bottom:-0.5em
|
||||
}
|
||||
|
||||
.ui.card>.content>.header:not(.ui), .ui.cards>.card>.content>.header:not(.ui){
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.status.cards .wide.column {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
height:2.3rem !important;
|
||||
}
|
||||
|
||||
.status.cards .wide.column:nth-child(1) {
|
||||
margin-top:1.2rem !important;
|
||||
}
|
||||
|
||||
.status.cards .wide.column:nth-child(2) {
|
||||
margin-top:1.2rem !important;
|
||||
}
|
||||
|
||||
.status.cards .three.wide.column {
|
||||
text-align: center;
|
||||
width: 22%!important;
|
||||
}
|
||||
|
||||
.status.cards .thirteen.wide.column{
|
||||
width: 78%!important;
|
||||
padding-left:0;
|
||||
}
|
||||
|
||||
.status.cards .description {
|
||||
padding-bottom:0 !important;
|
||||
}
|
||||
|
||||
.status.cards .flag {
|
||||
margin-right:0.5rem !important;
|
||||
}
|
||||
|
||||
.status.cards .header > .info.icon {
|
||||
float: right;
|
||||
margin-right:0 !important;
|
||||
}
|
||||
|
||||
.ui.popup:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.closePopup{
|
||||
color:rgb(10, 148, 242) !important;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.ui.content {
|
||||
margin:0 !important;
|
||||
padding:1em !important;
|
||||
}
|
||||
|
||||
.status.cards .ui.content.popup {
|
||||
min-width:calc(100%)!important;
|
||||
line-height:2rem !important;
|
||||
border-radius:5px !important;
|
||||
border:1px solid transparent !important;
|
||||
font-family:Arial,Helvetica,sans-serif !important;
|
||||
}
|
||||
|
||||
.status.cards .outline.icon {
|
||||
margin-right:1px !important;
|
||||
}
|
||||
|
||||
.ui.progress .bar {
|
||||
min-width:1.8em !important;
|
||||
border-radius:5px !important;
|
||||
line-height:1.65em !important;
|
||||
text-align: right;
|
||||
padding-right: 0.4em;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 700;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.service-status .delay-today {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-status .delay-today > i {
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
border-radius: 0.6em;
|
||||
background-color: grey;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
.service-status .danger {
|
||||
background-color: crimson !important;
|
||||
}
|
||||
|
||||
.service-status .good {
|
||||
background-color: rgb(10, 148, 242) !important;
|
||||
}
|
||||
|
||||
.service-status .warning {
|
||||
background-color: orange !important;
|
||||
}
|
||||
|
||||
.nezha-primary-btn {
|
||||
background-color: #0338d6 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.nezha-primary-font {
|
||||
color: #0338d6 !important;
|
||||
}
|
||||
|
||||
.nezha-secondary-font {
|
||||
height: 1em;
|
||||
color: rgb(10, 148, 242) !important;
|
||||
}
|
||||
|
||||
.ui-alerts.top-center {
|
||||
z-index: 99999999;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
const mixinsVue = {
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
preferredTemplate: null,
|
||||
isMobile: false,
|
||||
adaptedTemplates: [
|
||||
{ key: 'default', name: 'Default', icon: 'th large' },
|
||||
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
||||
{ key: 'server-status', name: 'SeverStatus', icon: 'list' }
|
||||
]
|
||||
},
|
||||
created() {
|
||||
this.isMobile = this.checkIsMobile();
|
||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||
},
|
||||
methods: {
|
||||
toggleTemplate(template) {
|
||||
if( template != this.preferredTemplate){
|
||||
this.preferredTemplate = template;
|
||||
this.updateCookie("preferred_theme", template);
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
updateCookie(name, value) {
|
||||
document.cookie = name + "=" + value +"; path=/";
|
||||
},
|
||||
getCookie(name) {
|
||||
const cookies = document.cookie.split(';');
|
||||
let cookieValue = null;
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.startsWith(name + '=')) {
|
||||
cookieValue = cookie.substring(name.length + 1, cookie.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
},
|
||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
logOut(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/logout',
|
||||
data: JSON.stringify({ id: id }),
|
||||
contentType: 'application/json',
|
||||
success: function (resp) {
|
||||
if (resp.code == 200) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('注销失败(Error ' + resp.code + '): ' + resp.message);
|
||||
}
|
||||
},
|
||||
error: function (err) {
|
||||
alert('网络错误: ' + err.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+68
-66
File diff suppressed because one or more lines are too long
+57
-26
File diff suppressed because one or more lines are too long
+186
-33
@@ -6,58 +6,120 @@ body {
|
||||
/* 导航部分 开始*/
|
||||
.navbar {
|
||||
min-height: 40px !important;
|
||||
}
|
||||
|
||||
.navbar-inner{
|
||||
margin:0 auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pl-md-unset {
|
||||
.navbar .container{
|
||||
max-width: 95vw;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle {
|
||||
.navbar-inverse{
|
||||
background-image: none;
|
||||
background-color: #1C2127;
|
||||
box-shadow: 0 1px 40px -8px #00000080;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-toggle:focus,
|
||||
.navbar-inverse .navbar-toggle:hover {
|
||||
background-color: #1C2127;
|
||||
}
|
||||
|
||||
.navbar .navbar-collapse:not([aria-expanded]) .navbar-nav .dropdown-toggle {
|
||||
margin-top: 18px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
.navbar .navbar-toggle {
|
||||
margin-right:0
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
.navbar .navbar-brand {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
padding:12px 0 0 0;
|
||||
margin-right:30px;
|
||||
}
|
||||
|
||||
.node-cell-expand {
|
||||
max-width: 420px;
|
||||
.navbar .node-cell-expand {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.node-cell-expand-label {
|
||||
margin-right: 5px;
|
||||
.navbar .node-cell-expand-label {
|
||||
/*margin-right: 5px;*/
|
||||
}
|
||||
|
||||
.dropdown .dropdown-toggle {
|
||||
.navbar .dropdown .dropdown-toggle {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.navbar-inverse, .nav.navbar-nav {
|
||||
background-image: linear-gradient(rgb(60, 60, 60) 0px, rgb(34, 34, 34) 100%) !important;
|
||||
.navbar .navbar-nav {
|
||||
margin:0px -15px;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-nav>li>a {
|
||||
.navbar .navbar-nav>li>a {
|
||||
color:#f1f1f1;
|
||||
}
|
||||
|
||||
.navbar-inverse .navbar-brand {
|
||||
font-size: 20px;
|
||||
.navbar-nav li a span{
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.navbar .navbar-collapse{
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
/* 导航部分 结束 */
|
||||
|
||||
|
||||
/* toolbox 开始 */
|
||||
|
||||
.toolbox {
|
||||
position: fixed;
|
||||
bottom:20px;
|
||||
right: 12px;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.toolbox span{
|
||||
display: block;
|
||||
width: 2.75rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.toolbox i{
|
||||
display: block;
|
||||
color: rgba(241,241,241,1);
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
font-size: 1.5rem;
|
||||
height: 2.75rem;
|
||||
width: 2.75rem;
|
||||
line-height: 2.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toolbox .toggleView i.show-nogroup {
|
||||
font-size: 1.85rem;
|
||||
}
|
||||
|
||||
.toolbox .setTheme i.setTheme-dark {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.toolbox .setTheme i.setTheme-light {
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.toolbox .showGoTop i.goTop {
|
||||
font-size: 1.55rem;
|
||||
}
|
||||
|
||||
/* toolbox 结束 */
|
||||
|
||||
|
||||
/* 正文部分 开始 */
|
||||
.content {
|
||||
padding: 20px;
|
||||
@@ -75,15 +137,40 @@ body {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table>thead>tr>th{
|
||||
border:none;
|
||||
.table > tbody > tr > td,
|
||||
.table > tbody > tr > th,
|
||||
.table > tfoot > tr > td,
|
||||
.table > tfoot > tr > th,
|
||||
.table > thead > tr > td,
|
||||
.table > thead > tr > th {
|
||||
position: relative;
|
||||
border:none;
|
||||
line-height:20px;
|
||||
}
|
||||
|
||||
.table .node-group-tag {
|
||||
.table > tbody > tr > td:before,
|
||||
.table > tbody > tr > th:before,
|
||||
.table > tfoot > tr > td:before,
|
||||
.table > tfoot > tr > th:before,
|
||||
.table > thead > tr > td:before,
|
||||
.table > thead > tr > th:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0.7px;
|
||||
}
|
||||
|
||||
.table .node-group-tag th{
|
||||
font-size: 18px;
|
||||
padding-bottom:15px;
|
||||
}
|
||||
|
||||
.table .node-cell-os-text {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -93,7 +180,6 @@ body {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
|
||||
.expandRow > td {
|
||||
padding: 0 !important;
|
||||
border-top: 0 !important;
|
||||
@@ -131,25 +217,49 @@ body {
|
||||
}
|
||||
|
||||
.node-cell.network {
|
||||
min-width: 110px;
|
||||
max-width: 110px;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.node-cell.cpu, .node-cell.ram, .node-cell.hdd {
|
||||
min-width: 45px;
|
||||
max-width: 90px;
|
||||
.node-cell.traffic {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.node-cell.cpu, .node-cell.ram, .node-cell.hdd, .node-cell.memory {
|
||||
min-width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
/*正文结束*/
|
||||
|
||||
/* 服务页 正文*/
|
||||
.service-status {
|
||||
|
||||
}
|
||||
|
||||
.service-status .service-status-th{
|
||||
min-width:60px;
|
||||
}
|
||||
|
||||
.service-status .service-name-th{
|
||||
min-width:50px;
|
||||
}
|
||||
|
||||
.service-status .service-averagelatency-th{
|
||||
min-width:80px;
|
||||
}
|
||||
|
||||
.service-status .service-30daysonline-th{
|
||||
min-width:80px;
|
||||
}
|
||||
|
||||
.service-status .delay-today {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.service-status .delay-today > i {
|
||||
.service-status .delay-today i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
@@ -163,26 +273,29 @@ body {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: -3.25px;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.service-status {
|
||||
|
||||
.service-status .tooltip-inner {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 服务页 正文结束 */
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.accordian-body{
|
||||
margin: 5px 0px 5px 10px;
|
||||
}
|
||||
.table .node-group-tag {
|
||||
.table .node-group-tag th{
|
||||
font-size:16px;
|
||||
padding-bottom:6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 720px) {
|
||||
@media only screen and (max-width: 767px) {
|
||||
body {
|
||||
font-size: 10px !important;
|
||||
padding-top:60px !important;
|
||||
@@ -191,6 +304,23 @@ body {
|
||||
padding: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.navbar .navbar-nav .open .dropdown-menu>li>a {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
.navbar .navbar-nav .open .dropdown-menu {
|
||||
list-style-image: initial;
|
||||
background-color: #181a1b;
|
||||
border-color: rgba(140, 130, 115, 0.15);
|
||||
box-shadow: rgba(0, 0, 0, 0.18) 0px 6px 12px;
|
||||
}
|
||||
.table > tbody > tr > td:before,
|
||||
.table > tbody > tr > th:before,
|
||||
.table > tfoot > tr > td:before,
|
||||
.table > tfoot > tr > th:before,
|
||||
.table > thead > tr > td:before,
|
||||
.table > thead > tr > th:before {
|
||||
height: 0.5px;
|
||||
}
|
||||
.node-cell.os,
|
||||
.node-cell.uptime,
|
||||
.node-cell.traffic{
|
||||
@@ -208,10 +338,33 @@ body {
|
||||
.accordian-body{
|
||||
margin: 5px 0px 5px 10px;
|
||||
}
|
||||
.table .node-group-tag {
|
||||
font-size:12px;
|
||||
.table .node-group-tag th{
|
||||
font-size:16px;
|
||||
padding-bottom:6px;
|
||||
}
|
||||
.service-status .service-status-th{
|
||||
min-width:30px;
|
||||
}
|
||||
.service-status .delay-today{
|
||||
margin-top:4px;
|
||||
justify-content: center;
|
||||
}
|
||||
.service-status .delay-today i{
|
||||
margin-right:0px;
|
||||
}
|
||||
.service-status .delay-today-text{
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
.service-status .service-averagelatency-th{
|
||||
min-width:70px;
|
||||
}
|
||||
.service-status .service-30daysonline-th{
|
||||
min-width:75px;
|
||||
}
|
||||
.toolbox {
|
||||
right: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+107
-10
@@ -2,27 +2,79 @@ const mixinsVue = {
|
||||
data: {
|
||||
cache: [],
|
||||
theme: "light",
|
||||
isSystemTheme: false
|
||||
isSystemTheme: false,
|
||||
showGroup: false,
|
||||
showGoTop: false,
|
||||
preferredTemplate: null,
|
||||
isMobile: false,
|
||||
adaptedTemplates: [
|
||||
{ key: 'default', name: 'Default', icon: 'th large' },
|
||||
{ key: 'angel-kanade', name: 'AngelKanade', icon: 'square' },
|
||||
{ key: 'server-status', name: 'SeverStatus', icon: 'list' }
|
||||
]
|
||||
},
|
||||
created() {
|
||||
this.initTheme()
|
||||
this.isMobile = this.checkIsMobile();
|
||||
this.initTheme();
|
||||
this.storedShowGroup();
|
||||
this.preferredTemplate = this.getCookie('preferred_theme') ? this.getCookie('preferred_theme') : this.$root.defaultTemplate;
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
methods: {
|
||||
toggleView() {
|
||||
this.showGroup = !this.showGroup;
|
||||
localStorage.setItem("showGroup", JSON.stringify(this.showGroup));
|
||||
return this.showGroup;
|
||||
},
|
||||
storedShowGroup() {
|
||||
const storedShowGroup = localStorage.getItem("showGroup");
|
||||
if (storedShowGroup !== null) {
|
||||
this.showGroup = JSON.parse(storedShowGroup);
|
||||
}
|
||||
},
|
||||
toggleTemplate(template) {
|
||||
if( template != this.preferredTemplate){
|
||||
this.preferredTemplate = template;
|
||||
this.updateCookie("preferred_theme", template);
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
updateCookie(name, value) {
|
||||
document.cookie = name + "=" + value +"; path=/";
|
||||
},
|
||||
getCookie(name) {
|
||||
const cookies = document.cookie.split(';');
|
||||
let cookieValue = null;
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.startsWith(name + '=')) {
|
||||
cookieValue = cookie.substring(name.length + 1, cookie.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
},
|
||||
setTheme(title, store = false) {
|
||||
this.theme = title
|
||||
document.body.setAttribute("theme", title)
|
||||
this.theme = title;
|
||||
document.body.setAttribute("theme", title);
|
||||
if (store) {
|
||||
localStorage.setItem("theme", title)
|
||||
this.isSystemTheme = false
|
||||
localStorage.setItem("theme", title);
|
||||
this.isSystemTheme = false;
|
||||
if(this.$root.page == 'index') {
|
||||
this.$root.reloadCharts(); //重新载入echarts图表
|
||||
}
|
||||
}
|
||||
},
|
||||
setSystemTheme() {
|
||||
localStorage.removeItem("theme")
|
||||
this.initTheme()
|
||||
this.isSystemTheme = true
|
||||
localStorage.removeItem("theme");
|
||||
this.initTheme();
|
||||
this.isSystemTheme = true;
|
||||
},
|
||||
initTheme() {
|
||||
const storeTheme = localStorage.getItem("theme")
|
||||
const storeTheme = localStorage.getItem("theme");
|
||||
if (storeTheme === 'dark' || storeTheme === 'light') {
|
||||
this.setTheme(storeTheme, true);
|
||||
} else {
|
||||
@@ -45,5 +97,50 @@ const mixinsVue = {
|
||||
toFixed2(f) {
|
||||
return f.toFixed(2)
|
||||
},
|
||||
logOut(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/logout',
|
||||
data: JSON.stringify({ id: id }),
|
||||
contentType: 'application/json',
|
||||
success: function (resp) {
|
||||
if (resp.code == 200) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('注销失败(Error ' + resp.code + '): ' + resp.message);
|
||||
}
|
||||
},
|
||||
error: function (err) {
|
||||
alert('网络错误: ' + err.responseText);
|
||||
}
|
||||
});
|
||||
},
|
||||
goTop() {
|
||||
$('html, body').animate({ scrollTop: 0 }, 400);
|
||||
return false;
|
||||
},
|
||||
handleScroll() {
|
||||
this.showGoTop = window.scrollY >= 100;
|
||||
},
|
||||
groupingData(data, field) {
|
||||
let map = new Map();
|
||||
let dest = [];
|
||||
|
||||
data.forEach(item => {
|
||||
if (!map.has(item[field])) {
|
||||
dest.push({
|
||||
[field]: item[field],
|
||||
data: [item]
|
||||
});
|
||||
map.set(item[field], item);
|
||||
} else {
|
||||
dest.find(dItem => dItem[field] === item[field]).data.push(item);
|
||||
}
|
||||
});
|
||||
return dest;
|
||||
},
|
||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -10,7 +10,7 @@
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.js"></script>
|
||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
|
||||
<script src="/static/main.js?v20230614"></script>
|
||||
<script src="/static/main.js?v20240330"></script>
|
||||
<script>
|
||||
(function () {
|
||||
updateLang({{.LANG }});
|
||||
|
||||
Vendored
+2
-1
@@ -15,6 +15,7 @@
|
||||
{{else}}
|
||||
<a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
|
||||
<a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>{{tr "Services"}}</a>
|
||||
<a class='item{{if eq .MatchedPath "/network"}} active{{end}}' href="/network"><i class="server icon"></i>{{tr "NetworkSpiter"}}</a>
|
||||
{{end}}
|
||||
<div class="right menu">
|
||||
<div class="item">
|
||||
@@ -50,4 +51,4 @@
|
||||
</div>
|
||||
</div>
|
||||
{{template "component/confirm" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
+7
@@ -24,6 +24,12 @@
|
||||
<option value="3">TCP-Ping</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui nb-show-in-service checkbox">
|
||||
<input name="EnableShowInService" type="checkbox" tabindex="0" class="hidden" />
|
||||
<label>{{tr "EnableShowInService"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{tr "Duration"}}</label>
|
||||
<input type="number" name="Duration" placeholder="{{tr "Seconds"}}" />
|
||||
@@ -110,3 +116,4 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
||||
+22
@@ -26,6 +26,28 @@
|
||||
<label>{{tr "HideForGuest"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui enableddns checkbox">
|
||||
<input name="EnableDDNS" type="checkbox" tabindex="0" />
|
||||
<label>{{tr "EnableDDNS"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui enableipv4 checkbox">
|
||||
<input name="EnableIPv4" type="checkbox" tabindex="0" />
|
||||
<label>{{tr "EnableIPv4"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui enableipv6 checkbox">
|
||||
<input name="EnableIpv6" type="checkbox" tabindex="0" />
|
||||
<label>{{tr "EnableIpv6"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{tr "DDNSDomain"}}</label>
|
||||
<input type="text" name="DDNSDomain" placeholder="{{tr "DDNSDomain"}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{tr "Note"}}</label>
|
||||
<textarea name="Note"></textarea>
|
||||
|
||||
+1
-1
@@ -68,4 +68,4 @@
|
||||
<script>
|
||||
$(".checkbox").checkbox();
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
<th>IP</th>
|
||||
<th>{{tr "VersionNumber"}}</th>
|
||||
<th>{{tr "HideForGuest"}}</th>
|
||||
<th>{{tr "EnableDDNS"}}</th>
|
||||
<th>{{tr "EnableIPv4"}}</th>
|
||||
<th>{{tr "EnableIpv6"}}</th>
|
||||
<th>{{tr "DDNSDomain"}}</th>
|
||||
<th>{{tr "Secret"}}</th>
|
||||
<th>{{tr "OneKeyInstall"}}</th>
|
||||
<th>{{tr "Note"}}</th>
|
||||
@@ -45,6 +49,10 @@
|
||||
<td>{{$server.Host.IP}}</td>
|
||||
<td>{{$server.Host.Version}}</td>
|
||||
<td>{{$server.HideForGuest}}</td>
|
||||
<td>{{$server.EnableDDNS}}</td>
|
||||
<td>{{$server.EnableIPv4}}</td>
|
||||
<td>{{$server.EnableIpv6}}</td>
|
||||
<td>{{$server.DDNSDomain}}</td>
|
||||
<td>
|
||||
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
||||
<i class="copy icon"></i>
|
||||
|
||||
+11
-2
@@ -70,18 +70,24 @@
|
||||
<input type="text" name="IPChangeNotificationTag" placeholder="" value="{{.Conf.IPChangeNotificationTag}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui nf-ssl checkbox ip-change">
|
||||
<div class="ui checkbox ip-change">
|
||||
<input name="EnableIPChangeNotification" type="checkbox" tabindex="0" class="hidden">
|
||||
<label>{{tr "Enable"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui nf-ssl checkbox plain-ip">
|
||||
<div class="ui checkbox plain-ip">
|
||||
<input name="EnablePlainIPInNotification" type="checkbox" tabindex="0" class="hidden">
|
||||
<label>{{tr "NotificationMessagesDoNotHideIP"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox disable-switch-template">
|
||||
<input name="DisableSwitchTemplateInFrontend" type="checkbox" tabindex="0" class="hidden">
|
||||
<label>{{tr "DisableSwitchTemplateInFrontend"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button" type="submit">{{tr "Save"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -128,5 +134,8 @@
|
||||
{{if .Conf.EnablePlainIPInNotification}}
|
||||
$('.checkbox.plain-ip').checkbox('set checked')
|
||||
{{ end }}
|
||||
{{if .Conf.DisableSwitchTemplateInFrontend }}
|
||||
$('.checkbox.disable-switch-template').checkbox('set checked')
|
||||
{{ end }}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
{{define "theme-angel-kanade/footer"}}
|
||||
</div>
|
||||
<div class="ui inverted vertical footer segment">
|
||||
<div class="ui center aligned is-size-7 container">
|
||||
<b>© <a style="color: white;" href="/">{{.Conf.Site.Brand}}</a></b> |
|
||||
@@ -12,13 +13,13 @@
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.js"></script>
|
||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
|
||||
<script src="/static/main.js?v20230614"></script>
|
||||
<script src="/static/main.js?v20240330"></script>
|
||||
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
|
||||
<script>
|
||||
(function () {
|
||||
updateLang({{.LANG }});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{end}}
|
||||
+101
-100
@@ -3,118 +3,116 @@
|
||||
{{if ts .CustomCode}} {{.CustomCode|safe}} {{end}}
|
||||
{{template "theme-angel-kanade/menu" .}}
|
||||
<div class="nb-container">
|
||||
<div class="ui container">
|
||||
<div id="app">
|
||||
<div class="ui styled fluid accordion" v-for="group in groups">
|
||||
<div class="active title">
|
||||
<i class="dropdown icon"></i>
|
||||
@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@
|
||||
</div>
|
||||
<div class="active content">
|
||||
<div class="ui four stackable status cards">
|
||||
<div v-for="server in group.data" :id="server.ID" class="ui card">
|
||||
<div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
|
||||
<div class="header">
|
||||
<img v-if="server.Host.CountryCode" style="border-radius: 50%;box-shadow:-1px -1px 2px #eee, 1px 1px 2px #000;width:19px;" :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/1x1/'+server.Host.CountryCode + '.svg'" alt="国家"/> <i v-if='server.Host.Platform == "darwin"'
|
||||
class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)'
|
||||
class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i>
|
||||
@#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@
|
||||
<i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
|
||||
<div class="ui content popup" style="margin-bottom: 0">
|
||||
{{tr "Platform"}}: @#server.Host.Platform#@-@#server.Host.PlatformVersion#@
|
||||
[<span
|
||||
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
||||
CPU: @#server.Host.CPU#@<br />
|
||||
{{tr "DiskUsed"}}:
|
||||
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
||||
{{tr "MemUsed"}}:
|
||||
@#formatByteSize(server.State.MemUsed)#@/@#formatByteSize(server.Host.MemTotal)#@<br />
|
||||
{{tr "SwapUsed"}}:
|
||||
@#formatByteSize(server.State.SwapUsed)#@/@#formatByteSize(server.Host.SwapTotal)#@<br />
|
||||
{{tr "NetTransfer"}}: <i
|
||||
class="arrow alternate circle down outline icon"></i>@#formatByteSize(server.State.NetInTransfer)#@<i
|
||||
class="arrow alternate circle up outline icon"></i>@#formatByteSize(server.State.NetOutTransfer)#@<br />
|
||||
{{tr "Load"}}: @# toFixed2(server.State.Load1) #@/@# toFixed2(server.State.Load5) #@/@#
|
||||
toFixed2(server.State.Load15) #@<br />
|
||||
{{tr "ProcessCount"}}: @# server.State.ProcessCount #@<br />
|
||||
{{tr "ConnCount"}}: TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
|
||||
{{tr "BootTime"}}: @# formatTimestamp(server.Host.BootTime) #@<br />
|
||||
{{tr "LastActive"}}: @# new Date(server.LastActive).toLocaleString() #@<br />
|
||||
{{tr "Version"}}: @#server.Host.Version#@<br />
|
||||
</div>
|
||||
<div class="ui divider" style="margin-bottom: 5px"></div>
|
||||
<div class="ui container">
|
||||
<div class="ui styled fluid accordion" v-for="group in groups">
|
||||
<div class="active title">
|
||||
<i class="dropdown icon"></i>
|
||||
@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@
|
||||
</div>
|
||||
<div class="active content">
|
||||
<div class="ui four stackable status cards">
|
||||
<div v-for="server in group.data" :id="server.ID" class="ui card">
|
||||
<div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
|
||||
<div class="header">
|
||||
<img v-if="server.Host.CountryCode" style="border-radius: 50%;box-shadow:-1px -1px 2px #eee, 1px 1px 2px #000;width:19px;" :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/1x1/'+server.Host.CountryCode + '.svg'" alt="国家"/> <i v-if='server.Host.Platform == "darwin"'
|
||||
class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)'
|
||||
class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i>
|
||||
@#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@
|
||||
<i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
|
||||
<div class="ui content popup" style="margin-bottom: 0">
|
||||
{{tr "Platform"}}: @#server.Host.Platform#@-@#server.Host.PlatformVersion#@
|
||||
[<span
|
||||
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
||||
CPU: @#server.Host.CPU#@<br />
|
||||
{{tr "DiskUsed"}}:
|
||||
@#formatByteSize(server.State.DiskUsed)#@/@#formatByteSize(server.Host.DiskTotal)#@<br />
|
||||
{{tr "MemUsed"}}:
|
||||
@#formatByteSize(server.State.MemUsed)#@/@#formatByteSize(server.Host.MemTotal)#@<br />
|
||||
{{tr "SwapUsed"}}:
|
||||
@#formatByteSize(server.State.SwapUsed)#@/@#formatByteSize(server.Host.SwapTotal)#@<br />
|
||||
{{tr "NetTransfer"}}: <i
|
||||
class="arrow alternate circle down outline icon"></i>@#formatByteSize(server.State.NetInTransfer)#@<i
|
||||
class="arrow alternate circle up outline icon"></i>@#formatByteSize(server.State.NetOutTransfer)#@<br />
|
||||
{{tr "Load"}}: @# toFixed2(server.State.Load1) #@/@# toFixed2(server.State.Load5) #@/@#
|
||||
toFixed2(server.State.Load15) #@<br />
|
||||
{{tr "ProcessCount"}}: @# server.State.ProcessCount #@<br />
|
||||
{{tr "ConnCount"}}: TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
|
||||
{{tr "BootTime"}}: @# formatTimestamp(server.Host.BootTime) #@<br />
|
||||
{{tr "LastActive"}}: @# new Date(server.LastActive).toLocaleString() #@<br />
|
||||
{{tr "Version"}}: @#server.Host.Version#@<br />
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="ui grid">
|
||||
<div class="three wide column">CPU</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.CPU, 100).class">
|
||||
<div class="bar" :style="formatPercent(server.live,server.State.CPU, 100).style">
|
||||
<small>@#formatPercent(server.live,server.State.CPU,100).percent#@%</small>
|
||||
</div>
|
||||
<div class="ui divider" style="margin-bottom: 5px"></div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div class="ui grid">
|
||||
<div class="three wide column">CPU</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.CPU, 100).class">
|
||||
<div class="bar" :style="formatPercent(server.live,server.State.CPU, 100).style">
|
||||
<small>@#formatPercent(server.live,server.State.CPU,100).percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "MemUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "MemUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.MemUsed, server.Host.MemTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.MemUsed/server.Host.MemTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "SwapUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.SwapUsed/server.Host.SwapTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "SwapUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.SwapUsed, server.Host.SwapTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.SwapUsed/server.Host.SwapTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetSpeed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow alternate circle down outline icon"></i>
|
||||
@#formatByteSize(server.State.NetInSpeed)#@/s
|
||||
<i class="arrow alternate circle up outline icon"></i>
|
||||
@#formatByteSize(server.State.NetOutSpeed)#@/s
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetTransfer"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow circle down icon"></i>
|
||||
@#formatByteSize(server.State.NetInTransfer)#@
|
||||
|
||||
<i class="arrow circle up icon"></i>
|
||||
@#formatByteSize(server.State.NetOutTransfer)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "DiskUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.DiskUsed/server.Host.DiskTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetSpeed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow alternate circle down outline icon"></i>
|
||||
@#formatByteSize(server.State.NetInSpeed)#@/s
|
||||
<i class="arrow alternate circle up outline icon"></i>
|
||||
@#formatByteSize(server.State.NetOutSpeed)#@/s
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetTransfer"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow circle down icon"></i>
|
||||
@#formatByteSize(server.State.NetInTransfer)#@
|
||||
|
||||
<i class="arrow circle up icon"></i>
|
||||
@#formatByteSize(server.State.NetOutTransfer)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "DiskUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class">
|
||||
<div class="bar"
|
||||
:style="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).style">
|
||||
<small>@#parseInt(server.State?server.State.DiskUsed/server.Host.DiskTotal*100:0)#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Info"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="bi bi-cpu-fill" style="font-size: 1.1rem; color: #4a86e8;"></i> @#getCoreAndGHz(server.Host.CPU)#@
|
||||
|
||||
<i class="bi bi-memory" style="font-size: 1.1rem; color: #00ac0d;"></i> @#getByteToGB(server.Host.MemTotal)#@
|
||||
|
||||
<i class="bi bi-hdd-rack-fill" style="font-size: 1.1rem; color: #980000"></i> @#getByteToGB(server.Host.DiskTotal)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Uptime"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="clock icon"></i>@#secondToDate(server.State.Uptime)#@
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Info"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="bi bi-cpu-fill" style="font-size: 1.1rem; color: #4a86e8;"></i> @#getCoreAndGHz(server.Host.CPU)#@
|
||||
|
||||
<i class="bi bi-memory" style="font-size: 1.1rem; color: #00ac0d;"></i> @#getByteToGB(server.Host.MemTotal)#@
|
||||
|
||||
<i class="bi bi-hdd-rack-fill" style="font-size: 1.1rem; color: #980000"></i> @#getByteToGB(server.Host.DiskTotal)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Uptime"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="clock icon"></i>@#secondToDate(server.State.Uptime)#@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-else>
|
||||
<p>@#server.Name#@</p>
|
||||
<p>{{tr "ServerIsOffline"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-else>
|
||||
<p>@#server.Name#@</p>
|
||||
<p>{{tr "ServerIsOffline"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,10 +127,13 @@
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
data: initData,
|
||||
groups: [],
|
||||
cache: [],
|
||||
},
|
||||
mixins: [mixinsVue],
|
||||
created() {
|
||||
this.group()
|
||||
},
|
||||
|
||||
+12
@@ -1,4 +1,5 @@
|
||||
{{define "theme-angel-kanade/menu"}}
|
||||
<div id="app">
|
||||
<div class="ui large top fixed menu nb-menu">
|
||||
<div class="ui container">
|
||||
<a class="item" href="/">
|
||||
@@ -15,6 +16,17 @@
|
||||
{{else}}
|
||||
<a class='item{{if eq .MatchedPath "/"}} active{{end}}' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
|
||||
<a class='item{{if eq .MatchedPath "/service"}} active{{end}}' href="/service"><i class="rss icon"></i>{{tr "Services"}}</a>
|
||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||
<div class="item ui simple dropdown">
|
||||
<div class="text"><i class="bi bi-incognito icon" style="margin-right:3px;"></i>{{tr "Template" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
||||
<div class="menu">
|
||||
<a v-for="(item, index) in adaptedTemplates" :key="index" @click="toggleTemplate(item.key)" class="item">
|
||||
<i :class="item.icon + ' icon'"></i>@#item.name#@
|
||||
<i class="check icon" v-if="preferredTemplate === item.key"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{end}}
|
||||
<div class="right menu">
|
||||
<div class="item">
|
||||
|
||||
+11
-1
@@ -76,10 +76,20 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "theme-angel-kanade/footer" .}}
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}}
|
||||
},
|
||||
mixins: [mixinsVue]
|
||||
})
|
||||
</script>
|
||||
{{end}}
|
||||
+1
@@ -39,6 +39,7 @@
|
||||
<ul>
|
||||
<li><a href="/">{{tr "Home"}}</a></li>
|
||||
<li><a href="/service">{{tr "Services"}}</a></li>
|
||||
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
|
||||
{{if .Admin}}
|
||||
<li><a href="/server">{{tr "AdminPanel"}}</a></li>
|
||||
{{else}}
|
||||
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
{{define "theme-daynight/network"}}
|
||||
<!doctype html>
|
||||
<html lang="{{.Conf.Language}}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
|
||||
|
||||
<link rel="stylesheet" href="/static/theme-daynight/css/main.css?v202108042286">
|
||||
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
|
||||
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
</head>
|
||||
|
||||
<body data-theme="light" data-gridlist="grid">
|
||||
<header>
|
||||
<section class="nav-bar clearfix">
|
||||
<figure class="logo">
|
||||
<a href="/">
|
||||
<img src="/static/logo.svg?v20210804" alt='{{tr "NezhaMonitoring"}}' width="50" height="50">
|
||||
</a>
|
||||
<a href="/">{{.Conf.Site.Brand}}</a>
|
||||
</figure>
|
||||
<div class="icon-container">
|
||||
<div class="row cf">
|
||||
<div class="three col">
|
||||
<div class="hamburger" id="hamburger-icon"><span class="line"></span><span
|
||||
class="line"></span><span class="line"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="nav-menu">
|
||||
<ul>
|
||||
<li><a href="/">{{tr "Home"}}</a></li>
|
||||
<li><a href="/service">{{tr "Services"}}</a></li>
|
||||
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
|
||||
{{if .Admin}}
|
||||
<li><a href="/server">{{tr "AdminPanel"}}</a></li>
|
||||
{{else}}
|
||||
<li><a href="/login">{{tr "Login"}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div id="network">
|
||||
<div class="server-info-container" v-for='server in servers' :id="server.ID">
|
||||
<div class="info-body" @click="redirectNetwork(server.ID)">
|
||||
<ul class="server-info-body-container">
|
||||
<li>
|
||||
<h3>@#server.Name#@</h3>
|
||||
</li>
|
||||
<li><img :src="'/static/theme-daynight/img/flag/'+(server.Host&&server.Host.CountryCode?server.Host.CountryCode.toUpperCase():'CN')+'.png'"
|
||||
:title="server.Host.CountryCode.toUpperCase()" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="network-chart" style="height: 800px;overflow: hidden">
|
||||
<div id="monitor-info-container" style="height: 520px;max-width: 1400px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-container">
|
||||
<ul>
|
||||
<li><i class="fas fa-sun" title='{{tr "LightMode"}}'></i><span>{{tr "LightMode"}}</span></li>
|
||||
<li><i class="fas fa-moon" title='{{tr "DarkMode"}}'></i><span>{{tr "DarkMode"}}</span></li>
|
||||
<li><i class="fas fa-th" title='{{tr "GridLayout"}}'></i><span>{{tr "GridLayout"}}</span></li>
|
||||
<li><i class="fas fa-list-ul" title='{{tr "ListLayout"}}'></i><span>{{tr "ListLayout"}}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<section class="dark-light-toggle">
|
||||
<label class="switcher">
|
||||
<input type="checkbox" name="theme" id="dark-light" />
|
||||
<div>
|
||||
<i class="fas fa-adjust"></i>
|
||||
</div>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<!-- Back to top button -->
|
||||
<a id="back-to-top"></a>
|
||||
|
||||
<footer>
|
||||
<div class="footer-container">
|
||||
<div><a href="https://github.com/naiba/nezha" target="_blank">Powered by {{tr "NezhaMonitoring"}} ·
|
||||
{{.Version}}</a>
|
||||
<p>© <span id="copyright-date">
|
||||
<script>document.getElementById('copyright-date').appendChild(document.createTextNode(new Date().getFullYear()))</script>
|
||||
</span> · <a href="https://blog.jackiesung.com" target="_blank">Theme designed by Jackie Sung</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/theme-daynight/js/main.js?v202102012266"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
|
||||
<script
|
||||
src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/limonte-sweetalert2/11.4.4/sweetalert2.all.min.js"></script>
|
||||
|
||||
<script>
|
||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
||||
// 基于准备好的dom,初始化echarts实例
|
||||
const myChart = echarts.init(document.getElementById('monitor-info-container'));
|
||||
|
||||
// 使用刚指定的配置项和数据显示图表。
|
||||
var statusCards = new Vue({
|
||||
el: '#network',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
servers: initData,
|
||||
cache: [],
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none'
|
||||
},
|
||||
restore: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
start: 94,
|
||||
end: 100
|
||||
}
|
||||
],
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
boundaryGap: false
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: [0, '100%']
|
||||
},
|
||||
series: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.DarkMode();
|
||||
this.parseMonitorInfo(monitorInfo);
|
||||
window.addEventListener('resize', this.resizeHandle);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('resize', this.resizeHandle)
|
||||
},
|
||||
methods: {
|
||||
DarkMode() {
|
||||
const hour = new Date(Date.now()).getHours()
|
||||
if (hour > 17 || hour < 4) {
|
||||
document.querySelector("input[name=theme]").checked = true;
|
||||
document.getElementsByTagName("BODY")[0].setAttribute('data-theme', 'dark');
|
||||
document.getElementById("monitor-info-container").style.backgroundColor = "#1E1E1E";
|
||||
}
|
||||
},
|
||||
redirectNetwork(id) {
|
||||
this.getMonitorHistory(id)
|
||||
.then(function (monitorInfo) {
|
||||
var vm = network.__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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
||||
if (lossRate > 99) {
|
||||
datal = [];
|
||||
}
|
||||
legendName = monitorInfo.result[i].monitor_name + " " + lossRate + "%";
|
||||
tLegendData.push(legendName);
|
||||
tSeries.push({
|
||||
name: legendName,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
symbolSize: 0,
|
||||
data: datal
|
||||
},
|
||||
markPoint: {
|
||||
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 }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
this.option.title.text = monitorInfo.result[0].server_name;
|
||||
this.option.series = tSeries;
|
||||
this.option.legend.data = tLegendData;
|
||||
myChart.clear();
|
||||
myChart.setOption(this.option);
|
||||
},
|
||||
resizeHandle() {
|
||||
myChart.resize();
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
<style>
|
||||
#network {
|
||||
width: calc(100vw - 6em);
|
||||
max-width: 1400px;
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
#monitor-info-container {
|
||||
margin: 0em auto;
|
||||
align-content: start;
|
||||
background-color: #F1F1F2;
|
||||
}
|
||||
|
||||
.server-info-container {
|
||||
font-size: .6em;
|
||||
width: fit-content;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
</html>
|
||||
{{end}}
|
||||
+2
-1
@@ -43,6 +43,7 @@
|
||||
<ul>
|
||||
<li><a href="/">{{tr "Home"}}</a></li>
|
||||
<li><a href="/service">{{tr "Services"}}</a></li>
|
||||
<li><a href="/network">{{tr "NetworkSpiter"}}</a></li>
|
||||
{{if .Admin}}
|
||||
<li><a href="/server">{{tr "AdminPanel"}}</a></li>
|
||||
{{else}}
|
||||
@@ -80,7 +81,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</section>
|
||||
{{if .CycleTransferStats}}
|
||||
<h2 class="mdui-m-t-5 mdui-text-center">{{tr "CycleTransferStats"}}</h2>
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{{define "theme-default/footer"}}
|
||||
</div>
|
||||
<div class="ui inverted vertical footer segment">
|
||||
<div class="ui center aligned is-size-7 container">
|
||||
<b>© <a style="color: white;" href="/">{{.Conf.Site.Brand}}</a></b> | <small>Powered by <a
|
||||
href="https://github.com/naiba/nezha" style="color: white;" target="_blank">{{tr "NezhaMonitoring"}}</a>
|
||||
{{.Version}}</small>
|
||||
</div>
|
||||
</div>
|
||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||
<script>
|
||||
function showSwitchTemplate(list, currentBackendTheme) {
|
||||
console.log(list, currentBackendTheme);
|
||||
console.log("currentBackendTheme:",currentBackendTheme);
|
||||
}
|
||||
showSwitchTemplate({{ .Themes }}, {{ .Conf.Site.Theme }})
|
||||
</script>
|
||||
{{ end }}
|
||||
<script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{{define "theme-default/header"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{.Conf.Language}}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta content="telephone=no" name="format-detection">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/assets/font-logos.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/[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/theme-default/css/main.css?v20240226">
|
||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.js"></script>
|
||||
<script src="/static/semantic-ui-alerts.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
|
||||
<script src="/static/theme-default/js/mixin.js?v20240302"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
+240
-28
@@ -1,10 +1,10 @@
|
||||
{{define "theme-default/home"}}
|
||||
{{template "common/header" .}}
|
||||
{{template "theme-default/header" .}}
|
||||
{{if ts .CustomCode}} {{.CustomCode|safe}} {{end}}
|
||||
{{template "common/menu" .}}
|
||||
{{template "theme-default/menu" .}}
|
||||
<div class="nb-container">
|
||||
<div class="ui container">
|
||||
<div id="app">
|
||||
<div class="ui container">
|
||||
<template v-if="groups">
|
||||
<div class="ui styled fluid accordion" v-for="group in groups">
|
||||
<div class="active title">
|
||||
<i class="dropdown icon"></i>
|
||||
@@ -15,12 +15,13 @@
|
||||
<div v-for="server in group.data" :id="server.ID" class="ui card">
|
||||
<div class="content" v-if="server.Host" style="margin-top: 10px; padding-bottom: 5px">
|
||||
<div class="header">
|
||||
<i :class="server.Host.CountryCode + ' flag'"></i> <i v-if='server.Host.Platform == "darwin"'
|
||||
<i style="width: 22px;border-radius: 4px;" :class="'fi fi-' + server.Host.CountryCode"></i> <i v-if='server.Host.Platform == "darwin"'
|
||||
class="apple icon"></i><i v-else-if='isWindowsPlatform(server.Host.Platform)'
|
||||
class="windows icon"></i><i v-else :class="'fl-' + getFontLogoClass(server.Host.Platform)"></i>
|
||||
@#server.Name + (server.live?'':'[{{tr "Offline"}}]')#@
|
||||
<i class="nezha-secondary-font info circle icon" style="height: 28px"></i>
|
||||
<div class="ui content popup" style="margin-bottom: 0">
|
||||
<i @click="togglePopup($event, server.ID)" aria-expanded="false" class="nezha-secondary-font info circle icon"></i>
|
||||
<div class="ui content popup" :class="{ 'visible': isActive(server.ID) }" style="margin-bottom: 0;">
|
||||
<i class="closePopup window close icon" @click="closePopup(server.ID)"></i>
|
||||
{{tr "Platform"}}: @#server.Host.Platform#@-@#server.Host.PlatformVersion#@
|
||||
[<span
|
||||
v-if="server.Host.Virtualization">@#server.Host.Virtualization#@:</span>@#server.Host.Arch#@]<br />
|
||||
@@ -40,7 +41,8 @@
|
||||
{{tr "ConnCount"}}: TCP @# server.State.TcpConnCount #@ / UDP @# server.State.UdpConnCount #@<br />
|
||||
{{tr "BootTime"}}: @# formatTimestamp(server.Host.BootTime) #@<br />
|
||||
{{tr "LastActive"}}: @# new Date(server.LastActive).toLocaleString() #@<br />
|
||||
{{tr "Version"}}: @#server.Host.Version#@<br />
|
||||
{{tr "Version"}}: @#server.Host.Version#@
|
||||
<div class="chartbox" :key="server.ID" :ref="`chart${server.ID}`" style="width: 100%; height: auto; margin-bottom: 2px;"></div>
|
||||
</div>
|
||||
<div class="ui divider" style="margin-bottom: 5px"></div>
|
||||
</div>
|
||||
@@ -72,13 +74,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetSpeed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow alternate circle down outline icon"></i>
|
||||
@#formatByteSize(server.State.NetInSpeed)#@/s
|
||||
<i class="arrow alternate circle up outline icon"></i>
|
||||
@#formatByteSize(server.State.NetOutSpeed)#@/s
|
||||
</div>
|
||||
<div class="three wide column">{{tr "DiskUsed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<div :class="formatPercent(server.live,server.State.DiskUsed, server.Host.DiskTotal).class">
|
||||
@@ -88,6 +83,36 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetSpeed"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow alternate circle down outline icon"></i>
|
||||
@#formatByteSize(server.State.NetInSpeed)#@/s
|
||||
<i class="arrow alternate circle up outline icon"></i>
|
||||
@#formatByteSize(server.State.NetOutSpeed)#@/s
|
||||
</div>
|
||||
<div class="three wide column">{{tr "NetTransfer"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="arrow circle down icon"></i>
|
||||
@#formatByteSize(server.State.NetInTransfer)#@
|
||||
|
||||
<i class="arrow circle up icon"></i>
|
||||
@#formatByteSize(server.State.NetOutTransfer)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Stat"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="bi bi-cpu-fill" style="font-size: 1.1rem; color: #4a86e8;"></i> @#getCoreAndGHz(server.Host.CPU)#@
|
||||
|
||||
<i class="bi bi-memory" style="font-size: 1.1rem; color: #00ac0d;"></i> @#getK2Gb(server.Host.MemTotal)#@
|
||||
|
||||
<i class="bi bi-hdd" style="font-size: 1.1rem; color: #e41e10"></i> @#getK2Gb(server.Host.DiskTotal)#@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Load"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="bi bi-activity" style="font-size: 1.1rem; color: #e41e10;"></i>
|
||||
@# toFixed2(server.State.Load1) #@ |
|
||||
@# toFixed2(server.State.Load5) #@ |
|
||||
@# toFixed2(server.State.Load15) #@
|
||||
</div>
|
||||
<div class="three wide column">{{tr "Uptime"}}</div>
|
||||
<div class="thirteen wide column">
|
||||
<i class="clock icon"></i>@#secondToDate(server.State.Uptime)#@
|
||||
@@ -103,30 +128,177 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
{{template "common/footer" .}}
|
||||
{{template "theme-default/footer" .}}
|
||||
<script>
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
var statusCards = new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
data: initData,
|
||||
page: 'index',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
servers: [],
|
||||
groups: [],
|
||||
cache: [],
|
||||
chartDataList: [],
|
||||
activePopup: null,
|
||||
},
|
||||
mixins: [mixinsVue],
|
||||
created() {
|
||||
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||
this.group()
|
||||
},
|
||||
mounted() {
|
||||
$('.nezha-secondary-font.info.icon').popup({
|
||||
popup: '.ui.content.popup',
|
||||
exclusive: true,
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
togglePopup(event, id) {
|
||||
// 切换弹出层的激活状态
|
||||
this.activePopup = this.activePopup === id ? null : id;
|
||||
this.showCharts(id);
|
||||
},
|
||||
isActive(id) {
|
||||
// 检查弹出层是否处于激活状态
|
||||
return this.activePopup === id;
|
||||
},
|
||||
closePopup(id) {
|
||||
this.activePopup = null;
|
||||
},
|
||||
showCharts(id) {
|
||||
// 发起数据请求
|
||||
const url = `/api/v1/monitor/${id}`;
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
||||
this.chartDataList[id - 1] = data.result;
|
||||
this.$nextTick(() => {
|
||||
this.renderCharts(id);
|
||||
});
|
||||
} else {
|
||||
console.log('this agent (id:'+ id + ') has no monitor.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
});
|
||||
},
|
||||
renderCharts(id) {
|
||||
if (!this.chartDataList[id - 1]) return;
|
||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||
const isMobile = this.checkIsMobile();
|
||||
const fontSize = isMobile ? 10 : 9;
|
||||
const itemGap = isMobile ? 6 : 6;
|
||||
const itemWidth = isMobile ? 10 : 10;
|
||||
const itemHeight = isMobile ? 10 : 10;
|
||||
const gridLeft = 25;
|
||||
const gridRight = 12;
|
||||
const fontColor = "rgba(0, 0, 0, 0.68)";
|
||||
const backgroundColor = '';
|
||||
const borderColor = "#ffffff";
|
||||
const chartData = this.chartDataList[id - 1];
|
||||
const chartContainer = this.$refs[`chart${id}`][0];
|
||||
const chart = echarts.init(chartContainer, null, {
|
||||
renderer: 'canvas',
|
||||
useDirtyRect: false,
|
||||
width: 'auto',
|
||||
height: 120,
|
||||
});
|
||||
const xAxisData = chartData[0].created_at.map(time => new Date(time).toLocaleString());
|
||||
const seriesData = chartData.map(item => {
|
||||
let loss = 0;
|
||||
const data = item.avg_delay.map((avgDelay, index) => {
|
||||
if(avgDelay > 0 && avgDelay < MaxTCPPingValue){
|
||||
loss += avgDelay > 0.9 * MaxTCPPingValue ? 1 : 0;
|
||||
return [item.created_at[index], avgDelay];
|
||||
}else{
|
||||
loss += 1;
|
||||
}
|
||||
});
|
||||
const lossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||
item.monitor_name = item.monitor_name.includes("%") ? item.monitor_name : `${item.monitor_name} ${lossRate}%`;
|
||||
return {
|
||||
name: item.monitor_name,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
connectNulls: true
|
||||
};
|
||||
});
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
title: {
|
||||
show: false
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: 'rect',
|
||||
data: chartData.map(item => item.monitor_name),
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
},
|
||||
lineStyle: {
|
||||
cap: 'butt'
|
||||
},
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
itemGap: itemGap,
|
||||
itemWidth: itemWidth,
|
||||
itemHeight: itemHeight,
|
||||
padding: [5,0,5,0]
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
fontSize: fontSize
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
fontSize: fontSize
|
||||
}
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
show: false,
|
||||
type: 'slider',
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
],
|
||||
series: seriesData,
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
},
|
||||
grid: {
|
||||
top: '30',
|
||||
bottom: '20',
|
||||
left: gridLeft,
|
||||
right: gridRight
|
||||
}
|
||||
};
|
||||
chart.setOption(option);
|
||||
},
|
||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
toFixed2(f) {
|
||||
return f.toFixed(2)
|
||||
},
|
||||
@@ -191,7 +363,7 @@
|
||||
return '';
|
||||
},
|
||||
group() {
|
||||
this.groups = groupingData(this.data, "Tag")
|
||||
this.groups = groupingData(this.servers, "Tag")
|
||||
},
|
||||
formatPercent(live, used, total) {
|
||||
const percent = live ? (parseInt(used / total * 100) || 0) : -1
|
||||
@@ -227,7 +399,7 @@
|
||||
secondToDate(s) {
|
||||
var d = Math.floor(s / 3600 / 24);
|
||||
if (d > 0) {
|
||||
return d + ' {{tr "Day"}}'
|
||||
return d + " {{tr "Day"}}"
|
||||
}
|
||||
var h = Math.floor(s / 3600 % 24);
|
||||
var m = Math.floor(s / 60 % 60);
|
||||
@@ -238,8 +410,48 @@
|
||||
return new Date(t * 1000).toLocaleString()
|
||||
},
|
||||
formatByteSize(bs) {
|
||||
const x = readableBytes(bs)
|
||||
const x = this.readableBytes(bs)
|
||||
return x != "NaN undefined" ? x : '0B'
|
||||
},
|
||||
readableBytes(bytes) {
|
||||
if (!bytes) {
|
||||
return '0B'
|
||||
}
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024)),
|
||||
sizes = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
|
||||
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
|
||||
},
|
||||
getCoreAndGHz(str){
|
||||
if((str || []).hasOwnProperty(0) === false){
|
||||
return '';
|
||||
}
|
||||
str = str[0];
|
||||
let GHz = str.match(/(\d|\.)+GHz/g);
|
||||
let Core = str.match(/(\d|\.)+ Physical/g);
|
||||
GHz = GHz!==null?GHz.hasOwnProperty(0)===false?'':GHz[0]:''
|
||||
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
|
||||
if(Core === '?'){
|
||||
let Core = str.match(/(\d|\.)+ Virtual/g);
|
||||
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
|
||||
return Core.replace('Virtual','Core')
|
||||
}
|
||||
return Core.replace('Physical','Core');
|
||||
},
|
||||
getK2Gb(bs){
|
||||
bs = bs / 1024 /1024 /1024;
|
||||
if(bs>=1){
|
||||
return Math.ceil(bs.toFixed(2)) + 'GB';
|
||||
}else{
|
||||
bs = bs * 1024;
|
||||
return Math.ceil(bs.toFixed(2)) + 'MB';
|
||||
}
|
||||
},
|
||||
listTipsMouseenter(obj,strs,tipsNum=1){
|
||||
this.layerIndex = layer.tips(strs, '#'+obj,{tips: [tipsNum, 'rgb(0 0 0 / 85%)'],time:0});
|
||||
$('#'+obj).attr('layerIndex',this.layerIndex)
|
||||
},
|
||||
listTipsMouseleave(obj){
|
||||
layer.close(this.layerIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
{{define "theme-default/menu"}}
|
||||
<div id="app">
|
||||
<div class="ui large top fixed menu nb-menu" style="z-index:9999999;">
|
||||
<div class="ui container">
|
||||
<a class="item" href="/">
|
||||
<img src="/static/logo.svg?v20210804">
|
||||
</a>
|
||||
<a class='item' href="/"><i class="home icon"></i>{{tr "Home"}}</a>
|
||||
<template v-if="isMobile">
|
||||
<div class="item ui simple dropdown">
|
||||
<div class="text"><i class="bi bi-gear-wide-connected icon" style="margin-right:3px;"></i>{{tr "Feature" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
||||
<div class="menu">
|
||||
<a href="/service" class="item"><i class="rss icon"></i>{{tr "Services" }}</a>
|
||||
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a href="/service" class='item'><i class="rss icon"></i>{{tr "Services" }}</a>
|
||||
<a href="/network" class="item"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a>
|
||||
</template>
|
||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||
<div class="item ui simple dropdown">
|
||||
<div class="text"><i class="bi bi-incognito icon" style="margin-right:3px;"></i>{{tr "Template" }}<i class="dropdown icon" style="margin-right:0px;"></i></div>
|
||||
<div class="menu">
|
||||
<a v-for="(item, index) in adaptedTemplates" :key="index" @click="toggleTemplate(item.key)" class="item">
|
||||
<i :class="item.icon + ' icon'"></i>@#item.name#@
|
||||
<i class="check icon" v-if="preferredTemplate === item.key"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{if .Admin}}
|
||||
<div class="item right item-right ui simple dropdown">
|
||||
<div class="text">
|
||||
<i class="user icon" style="margin-right:3px"></i>{{.Admin.Name}}
|
||||
<i class="dropdown icon"></i>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<a class="item" href="/server"><i class="terminal icon"></i>{{tr "AdminPanel"}}</a>
|
||||
<a class="item" @click="logOut({{.Admin.ID}})"><i class="logout icon"></i>{{tr "Logout"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<a href="/login" class="item right item-right" style="padding-right:1.2rem"><i class="sign-in icon"></i>{{tr "Login"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "component/confirm" .}}
|
||||
{{end}}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{{define "theme-default/network"}}
|
||||
{{template "theme-default/header" .}}
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
{{template "theme-default/menu" .}}
|
||||
<div class="nb-container">
|
||||
<div class="ui container">
|
||||
<div class="service-status">
|
||||
<table class="ui celled table">
|
||||
<button class="ui nezha-primary-btn button"
|
||||
v-for="server in servers"
|
||||
style="margin-top: 3px"
|
||||
@click="redirectNetwork(server.ID)">
|
||||
@#server.Name#@ <i :class="server.Host.CountryCode + ' flag'"></i>
|
||||
</button>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui container">
|
||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "theme-default/footer" .}}
|
||||
|
||||
<script>
|
||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
page: 'network',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
servers: initData,
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '8%',
|
||||
right: '8%',
|
||||
},
|
||||
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],
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
this.parseMonitorInfo(monitorInfo);
|
||||
window.addEventListener('resize', this.resizeHandle);
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeHandle)
|
||||
},
|
||||
methods: {
|
||||
getFontLogoClass(str) {
|
||||
if (["almalinux",
|
||||
"alpine",
|
||||
"aosc",
|
||||
"apple",
|
||||
"archlinux",
|
||||
"archlabs",
|
||||
"artix",
|
||||
"budgie",
|
||||
"centos",
|
||||
"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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
||||
if (lossRate > 99) {
|
||||
datal = [];
|
||||
}
|
||||
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
|
||||
tLegendData.push(legendName);
|
||||
tSeries.push({
|
||||
name: legendName,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
symbolSize :0,
|
||||
data: datal
|
||||
},
|
||||
markPoint: {
|
||||
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 }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
},
|
||||
resizeHandle () {
|
||||
this.myChart.resize();
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{{end}}
|
||||
+15
-4
@@ -1,9 +1,9 @@
|
||||
{{define "theme-default/service"}}
|
||||
{{template "common/header" .}}
|
||||
{{template "theme-default/header" .}}
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
{{template "common/menu" .}}
|
||||
{{template "theme-default/menu" .}}
|
||||
<div class="nb-container">
|
||||
<div class="ui container">
|
||||
<div class="service-status">
|
||||
@@ -76,10 +76,21 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "common/footer" .}}
|
||||
{{template "theme-default/footer" .}}
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
page: 'service',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}}
|
||||
},
|
||||
mixins: [mixinsVue]
|
||||
})
|
||||
</script>
|
||||
{{end}}
|
||||
+1
@@ -12,6 +12,7 @@
|
||||
{{else}}
|
||||
<a href="/" class='mdui-ripple mdui-ripple-white mdui-hoverable{{if eq .MatchedPath "/"}} mdui-tab-active{{end}}'><i class="mdui-icon material-icons">home</i>{{tr "Home"}}</a>
|
||||
<a href="/service" class='mdui-ripple mdui-ripple-white mdui-hoverable{{if eq .MatchedPath "/service"}} mdui-tab-active{{end}}'><i class="mdui-icon material-icons">accessibility</i>{{tr "Services"}}</a>
|
||||
<a href="/network" class='mdui-ripple mdui-ripple-white mdui-hoverable{{if eq .MatchedPath "/network"}} mdui-tab-active{{end}}'><i class="mdui-icon material-icons">network_check</i>{{tr "NetworkSpiter"}}</a>
|
||||
{{end}}
|
||||
<div class="mdui-toolbar-spacer"></div>
|
||||
{{if .Admin}}
|
||||
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
{{define "theme-mdui/network"}}
|
||||
<!doctype html>
|
||||
<html lang="{{.Conf.Language}}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
|
||||
|
||||
<!-- MDUI CSS -->
|
||||
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/css/mdui.min.css"/>
|
||||
<link rel="stylesheet" href="/static/theme-mdui/mdui.css" type="text/css">
|
||||
<style>
|
||||
.mdui-table td, .mdui-table th{padding: 6px;}
|
||||
.progress{width: 10%;min-width: 75px;}
|
||||
.progress-text{font-size: 16px;font-weight: 800;position: relative;top: 4px;left: 6px;}
|
||||
.offline st,.offline at,.offline gt,.offline .progress-text{color: grey;}
|
||||
a{text-decoration:none;color:#333;}.mdui-theme-layout-dark a{color:#fff;}
|
||||
</style>
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "theme-mdui/menu" .}}
|
||||
<div class="nb-container" id="app">
|
||||
<div class="ui container">
|
||||
<div class="service-status">
|
||||
<table class="ui celled table">
|
||||
<button
|
||||
v-for="server in servers"
|
||||
@click="redirectNetwork(server.ID)"
|
||||
class="mdui-btn mdui-btn-raised mdui-color-theme mdui-color-indigo mdui-text-color-white"
|
||||
style="margin-top: 6px;margin-left:5px">
|
||||
<img :src="'https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/flag-icon-css/4.1.5/flags/4x3/' + (server.Host.CountryCode?server.Host.CountryCode:'cn') + '.svg'" alt="Flag Icon" style="vertical-align: middle;height:14px"> <span>@#server.Name#@ </span></button>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui container">
|
||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "theme-mdui/footer" .}}
|
||||
<script src="/static/theme-mdui/mdui.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/mdui/1.0.2/js/mdui.min.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
|
||||
|
||||
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/echarts/5.3.0-rc.1/echarts.min.js"></script>
|
||||
|
||||
<script>
|
||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
servers: initData,
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '8%',
|
||||
right: '8%'
|
||||
},
|
||||
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,
|
||||
},
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
this.parseMonitorInfo(monitorInfo);
|
||||
window.addEventListener('resize', this.resizeHandle);
|
||||
},
|
||||
resizeHandle () {
|
||||
this.myChart.resize();
|
||||
},
|
||||
methods: {
|
||||
getFontLogoClass(str) {
|
||||
if (["almalinux",
|
||||
"alpine",
|
||||
"aosc",
|
||||
"apple",
|
||||
"archlinux",
|
||||
"archlabs",
|
||||
"artix",
|
||||
"budgie",
|
||||
"centos",
|
||||
"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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
||||
if (lossRate > 99) {
|
||||
datal = [];
|
||||
}
|
||||
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
|
||||
tLegendData.push(legendName);
|
||||
tSeries.push({
|
||||
name: legendName,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
symbolSize :0,
|
||||
data: datal
|
||||
},
|
||||
markPoint: {
|
||||
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 }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
},
|
||||
resizeHandle () {
|
||||
this.myChart.resize();
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.myChart.dispose();
|
||||
this.myChart = null;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
+14
-1
@@ -1,7 +1,20 @@
|
||||
{{define "theme-server-status/content-footer"}}
|
||||
<footer class="container" style="padding-bottom: 2rem;">
|
||||
<p style="text-align: center; font-size: 10px;">
|
||||
{{ .Conf.Site.Brand }} | Theme <a target="_blank" href="https://github.com/cppla/ServerStatus">ServerStatus</a> | Powered by <a target="_blank" href="https://github.com/naiba/nezha">{{tr "NezhaMonitoring"}}</a> {{.Version}}
|
||||
{{ .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 class="toggleView">
|
||||
<i v-if="showGroup" @click="toggleView" class="show-nogroup bi bi-justify"></i>
|
||||
<i v-else @click="toggleView" class="show-group bi bi-view-stacked"></i>
|
||||
</span>
|
||||
<span class="setTheme">
|
||||
<i v-if="theme === 'light'" @click="setTheme('dark', true)" class="setTheme-dark bi bi-moon-fill"></i>
|
||||
<i v-else @click="setTheme('light', true)" 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}}
|
||||
+55
-39
@@ -1,47 +1,63 @@
|
||||
{{define "theme-server-status/content-nav"}}
|
||||
<div role="navigation" class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container pl-md-unset">
|
||||
<div class="navbar-header">
|
||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="/" class="navbar-brand pl-md-unset">
|
||||
<img src="/static/logo.svg?v20210804" style="height: 2rem;display: inline-block;">
|
||||
{{.Conf.Site.Brand}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/">{{tr "Home" }}</a></li>
|
||||
<li><a href="/service">{{tr "Services" }}</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<header role="navigation" class="navbar navbar-inverse navbar-fixed-top" style="z-index:99999999;">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="/" class="navbar-brand">
|
||||
<img src="/static/logo.svg" style="height: 2rem;display: inline-block;">
|
||||
{{.Conf.Site.Brand}}
|
||||
</a>
|
||||
</div>
|
||||
<nav id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
||||
<template v-if="isMobile">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown" href="#">{{tr "Menu" }}<b class="caret"></b></a>
|
||||
<a data-toggle="dropdown"><i class="bi bi-gear-wide-connected" style="position:relative;top:1px;margin-right:3px;font-size:1.1rem;"></i>{{tr "Feature" }}<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu" style="min-width:100px;">
|
||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||
</template>
|
||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
{{if .Admin}}
|
||||
<li><a href="/server">{{tr "AdminPanel" }} ({{.Admin.Name}})</a></li>
|
||||
{{else}}
|
||||
<li><a href="/login">{{tr "Login" }}</a></li>
|
||||
{{end}}
|
||||
<li><a href="#" @click="setSystemTheme">{{tr "FollowSystem" }}
|
||||
<span style="color: #fff" v-if="isSystemTheme"> ✔️</span></a>
|
||||
</li>
|
||||
<li><a href="#" @click="setTheme('dark', true)">{{tr "DarkMode" }}
|
||||
<span v-if="theme === 'dark' && !isSystemTheme"> ✔️</span></a>
|
||||
</li>
|
||||
<li><a href="#" @click="setTheme('light', true)">{{tr "LightMode" }}
|
||||
<span v-if="theme === 'light' && !isSystemTheme"> ✔️</span></a>
|
||||
<li v-for="(item, index) in adaptedTemplates" :key="index">
|
||||
<a @click="toggleTemplate(item.key)">
|
||||
<i :class="item.icon + ' icon'" style="font-size:1em"></i>@#item.name#@
|
||||
<i class="check icon" v-if="preferredTemplate === item.key"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{{if .Admin}}
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown"><i class="user icon"></i>{{.Admin.Name}}<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu" style="margin-bottom:20px;">
|
||||
<li><a href="/server"><i class="terminal icon"></i>{{tr "AdminPanel" }}</a></li>
|
||||
<li><a @click="logOut({{.Admin.ID}})"><i class="logout icon"></i>{{tr "Logout"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{{else}}
|
||||
<li><a href="/login"><i class="sign-in icon"></i>{{tr "Login" }}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{{end}}
|
||||
|
||||
|
||||
+10
-9
@@ -6,16 +6,16 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20231207">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240225">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/font-logos/0.17/font-logos.min.css">
|
||||
<link rel="stylesheet" href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/semantic-ui/2.4.1/semantic.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-logos@0.17/assets/font-logos.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/[email protected]/css/flag-icons.min.css">
|
||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg?v20210804" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css">
|
||||
<link rel="shortcut icon" type="image/png" href="/static/logo.svg" />
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="/static/theme-server-status/js/html5shiv.js"></script>
|
||||
@@ -24,10 +24,11 @@
|
||||
{{if ts .CustomCode}}
|
||||
{{.CustomCode|safe}}
|
||||
{{end}}
|
||||
<script src="/static/theme-server-status/js/jquery.min.js"></script>
|
||||
<script src="/static/theme-server-status/js/bootstrap.min.js"></script>
|
||||
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/vue/2.6.14/vue.min.js"></script>
|
||||
<script src="/static/theme-server-status/js/mixin.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
|
||||
<script src="/static/theme-server-status/js/mixin.js?v20240302"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{end}}
|
||||
@@ -0,0 +1,134 @@
|
||||
{{define "theme-server-status/home-group-false"}}
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-cell status center">{{tr "Status"}}</th>
|
||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||
<th class="node-cell traffic center">{{tr "NetTransfer"}}↓|↑</th>
|
||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="(node,index) in nodesNoTag">
|
||||
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle" :class="index % 2 === 0 ? 'odd': 'even'"
|
||||
aria-expanded="false" @click="showCharts($event, node.ID)">
|
||||
<td class="node-cell status center">
|
||||
<div class="status-container">
|
||||
<div v-if="node.online" class="status-icon online"></div>
|
||||
<div v-else class="status-icon offline"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell name center">@#node.name#@</td>
|
||||
<td class="node-cell os center">
|
||||
<i v-if='node.os == "darwin"' class="apple icon"></i>
|
||||
<i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
|
||||
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
|
||||
<span class="node-cell-os-text">@#node.os#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell location">
|
||||
<i :class="'fi fi-' + node.location"></i>
|
||||
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
<td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td>
|
||||
<td class="node-cell cpu">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell memory">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.memory.style" :class="node.memory.class">
|
||||
<small>@#node.memory.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell hdd">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
<div class="accordian-body collapse" :id="'rt'+node.ID">
|
||||
<div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Platform"}}:</span>
|
||||
@#node.host.Platform#@-@#node.host.PlatformVersion#@
|
||||
[<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@]
|
||||
</span>
|
||||
<span class="node-cell-expand" v-if="node.host.CPU">
|
||||
<span class="node-cell-expand-label">CPU:</span>
|
||||
@#node.host.CPU.join(",")#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
|
||||
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "MemUsed"}}:</span>
|
||||
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%)
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span>
|
||||
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@
|
||||
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
|
||||
<i class="arrow alternate circle down outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetInTransfer)#@
|
||||
<i class="arrow alternate circle up outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@
|
||||
</span>
|
||||
<span class="node-cell-expand load">
|
||||
<span class="node-cell-expand-label">{{tr "Load"}}:</span>
|
||||
@#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ / @#toFixed2(node.state.Load15)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span>
|
||||
@#node.state.ProcessCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ConnCount"}}:</span>
|
||||
TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
|
||||
@#formatTimestamp(node.host.BootTime)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
|
||||
@#new Date(node.lastActive).toLocaleString()#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Uptime"}}:</span>
|
||||
@#node.uptime#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
|
||||
@#node.host.Version#@
|
||||
</span>
|
||||
<span class="node-echarts-expand">
|
||||
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
@@ -0,0 +1,137 @@
|
||||
{{define "theme-server-status/home-group-true"}}
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr class="node-group-tag">
|
||||
<th colspan="16" style="border:none;">@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@</th>
|
||||
</tr>
|
||||
<tr class="node-group-cell">
|
||||
<th class="node-cell status center">{{tr "Status"}}</th>
|
||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||
<th class="node-cell traffic center">{{tr "NetTransfer"}}↓|↑</th>
|
||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="(node,index) in group.data">
|
||||
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
|
||||
:class="index % 2 === 0 ? 'odd': 'even'" aria-expanded="false" @click="showCharts($event, node.ID)">
|
||||
<td class="node-cell status center">
|
||||
<div class="status-container">
|
||||
<div v-if="node.online" class="status-icon online"></div>
|
||||
<div v-else class="status-icon offline"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell name center">@#node.name#@</td>
|
||||
<td class="node-cell os center">
|
||||
<i v-if='node.os == "darwin"' class="apple icon"></i>
|
||||
<i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
|
||||
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
|
||||
<span class="node-cell-os-text">@#node.os#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell location">
|
||||
<i :class="'fi fi-' + node.location"></i>
|
||||
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
<td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td>
|
||||
<td class="node-cell cpu">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell memory">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.memory.style" :class="node.memory.class">
|
||||
<small>@#node.memory.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell hdd">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
<div class="accordian-body collapse" :id="'rt'+node.ID">
|
||||
<div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Platform"}}:</span>
|
||||
@#node.host.Platform#@-@#node.host.PlatformVersion#@
|
||||
[<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@]
|
||||
</span>
|
||||
<span class="node-cell-expand" v-if="node.host.CPU">
|
||||
<span class="node-cell-expand-label">CPU:</span>
|
||||
@#node.host.CPU.join(",")#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
|
||||
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "MemUsed"}}:</span>
|
||||
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%)
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span>
|
||||
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@
|
||||
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
|
||||
<i class="arrow alternate circle down outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetInTransfer)#@
|
||||
<i class="arrow alternate circle up outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@
|
||||
</span>
|
||||
<span class="node-cell-expand load">
|
||||
<span class="node-cell-expand-label">{{tr "Load"}}:</span>
|
||||
@#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ / @#toFixed2(node.state.Load15)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span>
|
||||
@#node.state.ProcessCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ConnCount"}}:</span>
|
||||
TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
|
||||
@#formatTimestamp(node.host.BootTime)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
|
||||
@#new Date(node.lastActive).toLocaleString()#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Uptime"}}:</span>
|
||||
@#node.uptime#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
|
||||
@#node.host.Version#@
|
||||
</span>
|
||||
<span class="node-echarts-expand">
|
||||
<div class="chartbox" chartbox-show="0" :key="node.ID" :ref="`chart${node.ID}`" style="width: 100%; height: auto;"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
+225
-173
@@ -1,156 +1,58 @@
|
||||
{{define "theme-server-status/home"}}
|
||||
{{template "theme-server-status/header" .}}
|
||||
<div id="app">
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<div class="container table-responsive content" style="max-width: 95vw" v-for="group in nodes">
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-group-tag" colspan="16" style="border:none;">@#(group.Tag!==''?group.Tag:'{{tr "Default"}}')#@</th>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="node-cell status center">{{tr "Status"}}</th>
|
||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||
<th class="node-cell traffic center">{{tr "NetTransfer"}}↓|↑</th>
|
||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell ram center">{{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="(node,index) in group.data">
|
||||
<tr :id="'r'+node.ID" data-toggle="collapse" :data-target="'#rt'+node.ID" class="accordion-toggle"
|
||||
:class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td class="node-cell status center">
|
||||
<div class="status-container">
|
||||
<div v-if="node.online" class="status-icon online"></div>
|
||||
<div v-else class="status-icon offline"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell name center">@#node.name#@</td>
|
||||
<td class="node-cell os center">
|
||||
<i v-if='node.os == "darwin"' class="apple icon"></i>
|
||||
<i v-else-if='isWindowsPlatform(node.host.Platform)' class="windows icon"></i>
|
||||
<i v-else :class="'fl-' + getFontLogoClass(node.host.Platform)"></i>
|
||||
<span class="node-cell-os-text">@#node.os#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell location">
|
||||
<i :class="'fi fi-' + node.location"></i>
|
||||
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
<td style="text-align: center;" class="node-cell traffic">@#node.traffic#@</td>
|
||||
<td class="node-cell cpu">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.cpu.style" :class="node.cpu.class"><small>@#node.cpu.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell memory">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.memory.style" :class="node.memory.class">
|
||||
<small>@#node.memory.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell hdd">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<div :style="node.hdd.style" :class="node.hdd.class"><small>@#node.hdd.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
<div class="accordian-body collapse" :id="'rt'+node.ID">
|
||||
<div style="display: flex;left-items: center;justify-content: center;flex-direction: column; max-width: 89vw">
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Platform"}}:</span>
|
||||
@#node.host.Platform#@-@#node.host.PlatformVersion#@
|
||||
[<span v-if="node.host.Virtualization">@#node.host.Virtualization#@:</span>@#node.host.Arch#@]
|
||||
</span>
|
||||
<span class="node-cell-expand" v-if="node.host.CPU">
|
||||
<span class="node-cell-expand-label">CPU:</span>
|
||||
@#node.host.CPU.join(",")#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "DiskUsed"}}:</span>
|
||||
@#formatByteSize(node.state.DiskUsed)#@ / @#formatByteSize(node.host.DiskTotal)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "MemUsed"}}:</span>
|
||||
@#formatByteSize(node.state.MemUsed)#@ / @#formatByteSize(node.host.MemTotal)#@(@#toFixed2(node.state.MemUsed / node.host.MemTotal * 100)#@%)
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "SwapUsed"}}:</span>
|
||||
@#formatByteSize(node.state.SwapUsed)#@ / @#formatByteSize(node.host.SwapTotal)#@
|
||||
<span v-if="node.host.SwapTotal">(@#toFixed2(node.state.SwapUsed / node.host.SwapTotal * 100)#@%)</span>
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "NetTransfer"}}:</span>
|
||||
<i class="arrow alternate circle down outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetInTransfer)#@
|
||||
<i class="arrow alternate circle up outline icon"
|
||||
style="margin: 0"></i>@#formatByteSize(node.state.NetOutTransfer)#@
|
||||
</span>
|
||||
<span class="node-cell-expand load">
|
||||
<span class="node-cell-expand-label">{{tr "Load"}}:</span>
|
||||
@#toFixed2(node.state.Load1)#@ / @#toFixed2(node.state.Load5)#@ / @#toFixed2(node.state.Load15)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ProcessCount"}}:</span>
|
||||
@#node.state.ProcessCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "ConnCount"}}:</span>
|
||||
TCP @#node.state.TcpConnCount#@ / UDP @#node.state.UdpConnCount#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "BootTime"}}:</span>
|
||||
@#formatTimestamp(node.host.BootTime)#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "LastActive"}}:</span>
|
||||
@#new Date(node.lastActive).toLocaleString()#@
|
||||
</span>
|
||||
<span class="node-cell-expand">
|
||||
<span class="node-cell-expand-label">{{tr "Version"}}:</span>
|
||||
@#node.host.Version#@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<!-- showGroup true -->
|
||||
<template v-if="showGroup">
|
||||
<section class="container table-responsive content" style="max-width: 95vw" v-for="group in nodesTag">
|
||||
{{template "theme-server-status/home-group-true" .}}
|
||||
</section>
|
||||
</template>
|
||||
<!-- showGroup false -->
|
||||
<template v-else>
|
||||
<section class="container table-responsive content" style="max-width: 95vw">
|
||||
{{template "theme-server-status/home-group-false" .}}
|
||||
</section>
|
||||
</template>
|
||||
{{template "theme-server-status/content-footer" .}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
nodes: [],
|
||||
page: 'index',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
cache: [],
|
||||
servers: [],
|
||||
nodesTag: [],
|
||||
nodesNoTag: [],
|
||||
chartDataList: [],
|
||||
ws: null
|
||||
},
|
||||
mixins: [mixinsVue],
|
||||
created() {
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
this.nodes = groupingData(this.handleNodes(initData),"Tag");
|
||||
this.initTheme()
|
||||
this.servers = JSON.parse('{{.Servers}}').servers;
|
||||
if(this.showGroup) {
|
||||
this.nodesTag = this.groupingData(this.handleNodes(this.servers),"Tag");
|
||||
} else {
|
||||
this.nodesNoTag = this.handleNodes(this.servers);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化时建立WebSocket连接
|
||||
this.connect();
|
||||
// 监听页面可见性变化
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
setTimeout(() => {
|
||||
if (document.hasFocus()) {
|
||||
this.connect();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
isWindowsPlatform(str) {
|
||||
@@ -231,13 +133,18 @@
|
||||
return x !== "NaN undefined" ? x : '0B'
|
||||
},
|
||||
formatPercent(live, used, total) {
|
||||
const percent = live ? (this.toFixed2(used / total * 100) || 0) : 0
|
||||
return this.formatPercents(percent)
|
||||
//const percent = live ? (this.toFixed2(used / total * 100) || 0) : 0
|
||||
const percent = (this.toFixed2(used / total * 100) || 0)
|
||||
return this.formatPercents(live,percent)
|
||||
},
|
||||
formatPercents(percent) {
|
||||
formatPercents(live,percent) {
|
||||
//if(!live) { percent = 0; }
|
||||
if (percent <= 0) {
|
||||
percent = 0;
|
||||
}
|
||||
if (percent >= 100) {
|
||||
percent = 100;
|
||||
}
|
||||
if (!this.cache[percent]) {
|
||||
this.cache[percent] = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
@@ -263,12 +170,18 @@
|
||||
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
|
||||
},
|
||||
connect() {
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws"
|
||||
const ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
|
||||
ws.onopen = function () {
|
||||
// 如果已经存在 WebSocket 连接并且处于开启状态,则不重复建立连接
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
console.log('Closing old WebSocket connection...');
|
||||
this.ws.close();
|
||||
}
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
this.ws = new WebSocket(wsProtocol + '://' + window.location.host + '/ws');
|
||||
|
||||
this.ws.onopen = function () {
|
||||
console.log("Connection open ...")
|
||||
}
|
||||
ws.onmessage = (evt) => {
|
||||
this.ws.onmessage = (evt) => {
|
||||
let jsonData = evt.data
|
||||
const data = JSON.parse(jsonData)
|
||||
for (let i = 0; i < data.servers?.length; i++) {
|
||||
@@ -279,16 +192,20 @@
|
||||
const lastActive = new Date(ns.LastActive).getTime()
|
||||
data.servers[i].live = data.now - lastActive <= 10 * 1000;
|
||||
}
|
||||
}
|
||||
this.nodes = groupingData(this.handleNodes(data.servers),"Tag");
|
||||
}
|
||||
if(this.showGroup) {
|
||||
this.nodesTag = this.groupingData(this.handleNodes(data.servers),"Tag");
|
||||
} else {
|
||||
this.nodesNoTag = this.handleNodes(data.servers);
|
||||
}
|
||||
}
|
||||
ws.onclose = () => {
|
||||
setTimeout(function () {
|
||||
this.connect()
|
||||
this.ws.onclose = () => {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 5000);
|
||||
}
|
||||
ws.onerror = function () {
|
||||
ws.close()
|
||||
};
|
||||
this.ws.onerror = () => {
|
||||
this.ws.close()
|
||||
}
|
||||
},
|
||||
handleNodes(servers) {
|
||||
@@ -309,7 +226,7 @@
|
||||
load: this.toFixed2(server.State.Load1),
|
||||
network: this.getNetworkSpeed(server.State.NetInSpeed, server.State.NetOutSpeed),
|
||||
traffic: this.formatByteSize(server.State.NetInTransfer) + ' | ' + this.formatByteSize(server.State.NetOutTransfer),
|
||||
cpu: this.formatPercents(this.toFixed2(server.State.CPU)),
|
||||
cpu: this.formatPercents(server.live, this.toFixed2(server.State.CPU)),
|
||||
memory: this.formatPercent(server.live, server.State.MemUsed, server.Host.MemTotal),
|
||||
hdd: this.formatPercent(server.live, server.State.DiskUsed, server.Host.DiskTotal),
|
||||
online: server.live,
|
||||
@@ -324,29 +241,164 @@
|
||||
},
|
||||
getNetworkSpeed(netInSpeed, netOutSpeed) {
|
||||
return this.formatByteSize(netInSpeed) + ' | ' + this.formatByteSize(netOutSpeed)
|
||||
},
|
||||
showCharts(event, id) {
|
||||
const chartContainer = this.$refs[`chart${id}`][0];
|
||||
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
||||
chartContainer.setAttribute('chartbox-show', chartboxShow === '0' ? '1' : '0');
|
||||
const isAriaExpandedFalse = event.currentTarget.getAttribute('aria-expanded') === 'false';
|
||||
if (!isAriaExpandedFalse) return;
|
||||
// 发起数据请求
|
||||
const url = `/api/v1/monitor/${id}`;
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.result) { // 数据请求成功,更新数据并渲染图表
|
||||
this.chartDataList[id - 1] = data.result;
|
||||
this.$nextTick(() => {
|
||||
this.renderCharts(id);
|
||||
});
|
||||
} else {
|
||||
console.log('this agent (id:'+ id + ') has no monitor.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
});
|
||||
},
|
||||
renderCharts(id, reload = false) {
|
||||
if (!this.chartDataList[id - 1]) return;
|
||||
const chartData = this.chartDataList[id - 1];
|
||||
const chartContainer = this.$refs[`chart${id}`][0];
|
||||
if (reload) { //点击切换亮色/暗色风格模式时,重新载入echarts图表的逻辑,
|
||||
// 第一步,查找已经渲染出的图表容器,并销毁它
|
||||
const existingChart = echarts.getInstanceByDom(chartContainer);
|
||||
if (existingChart) existingChart.dispose();
|
||||
// 第二步,如果图表容器处于不可见状态chartboxShow=0,不重新渲染出新的图表,
|
||||
// 如果图表容器处于可见状态chartboxShow=1,重新渲染出新的图表
|
||||
const chartboxShow = chartContainer.getAttribute('chartbox-show');
|
||||
if ( chartboxShow === '0' ) return;
|
||||
}
|
||||
// 定义图表参数值
|
||||
const MaxTCPPingValue = {{.Conf.MaxTCPPingValue}} ? {{.Conf.MaxTCPPingValue}} : 300;
|
||||
const isMobile = this.checkIsMobile();
|
||||
const fontSize = isMobile ? 10 : 14;
|
||||
const gridLeft = isMobile ? 25 : 36;
|
||||
const gridRight = isMobile ? 5 : 20;
|
||||
const legendLeft = isMobile ? 'center' : 'center';
|
||||
const legendTop = isMobile ? 5 : 5;
|
||||
const legendPadding= isMobile ? [5,0,5,0] : [5,0,5,0];
|
||||
const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
const theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : systemDarkMode;
|
||||
const chartTheme = theme == "dark" ? "dark" : "default";
|
||||
const fontColor = theme == "dark" ? "#f1f1f1" : "#000000";
|
||||
const backgroundColor = theme == "dark" ? "#1C1D26" : '';
|
||||
const tooltipBackgroundColor = theme == "dark" ? "#1C1D26" : '#ffffff';
|
||||
const tooltipBorderColor = theme == "dark" ? "#31363B" : "#ffffff";
|
||||
// 渲染图表
|
||||
const chart = echarts.init(chartContainer, chartTheme, {
|
||||
renderer: 'canvas',
|
||||
useDirtyRect: false,
|
||||
width: 'auto',
|
||||
height: 300,
|
||||
});
|
||||
const xAxisData = chartData[0].created_at.map(time => new Date(time).toLocaleString());
|
||||
const seriesData = chartData.map(item => {
|
||||
let loss = 0;
|
||||
const data = item.avg_delay.map((avgDelay, index) => {
|
||||
if(avgDelay > 0 && avgDelay < MaxTCPPingValue){
|
||||
loss += avgDelay > 0.9 * MaxTCPPingValue ? 1 : 0;
|
||||
return [item.created_at[index], avgDelay];
|
||||
}else{
|
||||
loss += 1;
|
||||
}
|
||||
});
|
||||
const lossRate = ((loss / item.created_at.length) * 100).toFixed(1);
|
||||
item.monitor_name = item.monitor_name.includes("%") ? item.monitor_name : `${item.monitor_name} ${lossRate}%`;
|
||||
return {
|
||||
name: item.monitor_name,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
connectNulls: true
|
||||
};
|
||||
});
|
||||
const option = {
|
||||
backgroundColor: backgroundColor,
|
||||
title: {
|
||||
show: false
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: tooltipBackgroundColor,
|
||||
borderColor: tooltipBorderColor,
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: chartData.map(item => item.monitor_name),
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
},
|
||||
top: legendTop,
|
||||
bottom: 0,
|
||||
left: legendLeft,
|
||||
padding: legendPadding
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
data: xAxisData,
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
fontSize: fontSize
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
fontSize: fontSize
|
||||
}
|
||||
}
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'slider',
|
||||
start: 0,
|
||||
end: 100
|
||||
}
|
||||
],
|
||||
series: seriesData,
|
||||
textStyle: {
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
},
|
||||
grid: {
|
||||
top: '40',
|
||||
left: gridLeft,
|
||||
right: gridRight
|
||||
}
|
||||
};
|
||||
chart.setOption(option);
|
||||
},
|
||||
reloadCharts() { // 重新加载所有图表
|
||||
this.servers.forEach(node => {
|
||||
const id = node.ID;
|
||||
const chartData = this.chartDataList[id - 1];
|
||||
if (chartData) {
|
||||
this.renderCharts(id,true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
function groupingData(data, field) {
|
||||
let map = new Map();
|
||||
let dest = [];
|
||||
|
||||
data.forEach(item => {
|
||||
if (!map.has(item[field])) {
|
||||
dest.push({
|
||||
[field]: item[field],
|
||||
data: [item]
|
||||
});
|
||||
map.set(item[field], item);
|
||||
} else {
|
||||
dest.find(dItem => dItem[field] === item[field]).data.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return dest;
|
||||
}
|
||||
</script>
|
||||
{{template "theme-server-status/footer" .}}
|
||||
{{end}}
|
||||
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
{{define "theme-server-status/network"}}
|
||||
{{template "theme-server-status/header" .}}
|
||||
<div id="app">
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<div class="container table-responsive content" style="max-width: 95vw">
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<button class="ui nezha-primary-btn button"
|
||||
v-for="server in servers"
|
||||
style="margin-top: 3px"
|
||||
@click="redirectNetwork(server.ID)">
|
||||
@#server.Name#@ <i :class="'fi fi-' + server.Host.CountryCode"></i><span class="node-cell-location-text text-uppercase"> @#server.Host.CountryCode#@</span>
|
||||
</button>
|
||||
</table>
|
||||
</div>
|
||||
<div class="container table-responsive content" style="max-width: 95vw">
|
||||
<div ref="chartDom" style="border-radius: 28px; margin-top: 15px;height: 520px;max-width: 1400px;overflow: hidden"></div>
|
||||
</div>
|
||||
{{template "theme-server-status/content-footer" .}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const monitorInfo = JSON.parse('{{.MonitorInfos}}');
|
||||
const initData = JSON.parse('{{.Servers}}').servers;
|
||||
let MaxTCPPingValue = {{.Conf.MaxTCPPingValue}};
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
page: 'network',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
servers: initData,
|
||||
option: {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
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],
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
this.parseMonitorInfo(monitorInfo);
|
||||
},
|
||||
methods: {
|
||||
getFontLogoClass(str) {
|
||||
if (["almalinux",
|
||||
"alpine",
|
||||
"aosc",
|
||||
"apple",
|
||||
"archlinux",
|
||||
"archlabs",
|
||||
"artix",
|
||||
"budgie",
|
||||
"centos",
|
||||
"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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
lossRate = ((loss / monitorInfo.result[i].created_at.length) * 100).toFixed(1);
|
||||
if (lossRate > 99) {
|
||||
datal = [];
|
||||
}
|
||||
legendName = monitorInfo.result[i].monitor_name +" "+ lossRate + "%";
|
||||
tLegendData.push(legendName);
|
||||
tSeries.push({
|
||||
name: legendName,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
data: data,
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
symbolSize :0,
|
||||
data: datal
|
||||
},
|
||||
markPoint: {
|
||||
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 }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
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>
|
||||
{{template "theme-server-status/footer" .}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,46 @@
|
||||
{{define "theme-server-status/service-group-false"}}
|
||||
<table class="table table-striped table-condensed table-hover service-status">
|
||||
<thead>
|
||||
<tr class="node-group-tag">
|
||||
<th colspan="16" style="border:none;">
|
||||
{{tr "ServicesManagement"}}
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="node-group-cell">
|
||||
<th class="node-cell center service-status-th">{{tr "Status"}}</th>
|
||||
<th class="node-cell center service-name-th">{{tr "Name"}}</th>
|
||||
<th class="node-cell center service-details-th">{{tr "Details"}}</th>
|
||||
<th class="node-cell center service-averagelatency-th">{{tr "AverageLatency"}}</th>
|
||||
<th class="node-cell center service-30daysonline-th">{{tr "30DaysOnline"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="service in servicesNoTag">
|
||||
<tr>
|
||||
<td class="node-cell center">
|
||||
<div class="delay-today">
|
||||
<i class="delay-today-icon" :class="service.health.className"></i>
|
||||
<span class="delay-today-text">@#service.health.text#@</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.name#@</td>
|
||||
<td class="node-cell center service-details-td">
|
||||
<template v-for="(item,index) in service.dayDetail">
|
||||
<div data-toggle="tooltip" data-placement="top" class="service-day-status-icon" :class="item.className"
|
||||
:title="item.text">
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.avgDelay#@</td>
|
||||
<td class="node-cell center">
|
||||
<div class="progress">
|
||||
<div :style="service.totalUpTime.style" :class="service.totalUpTime.className">
|
||||
<small>@#service.totalUpTime.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
@@ -0,0 +1,51 @@
|
||||
{{define "theme-server-status/service-group-true"}}
|
||||
<table class="table table-striped table-condensed table-hover service-status">
|
||||
<thead>
|
||||
<tr class="node-group-tag">
|
||||
<th colspan="16" style="border:none;">
|
||||
<span v-if="group.type == 1">HTTP-GET</span>
|
||||
<span v-if="group.type == 2">ICMP-Ping</span>
|
||||
<span v-if="group.type == 3">TCP-Ping</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="node-group-cell">
|
||||
<th class="node-cell center service-status-th">{{tr "Status"}}</th>
|
||||
<th class="node-cell center service-name-th">{{tr "Name"}}</th>
|
||||
<th class="node-cell center service-details-th">{{tr "Details"}}</th>
|
||||
<th class="node-cell center service-averagelatency-th">{{tr "AverageLatency"}}</th>
|
||||
<th class="node-cell center service-30daysonline-th">{{tr "30DaysOnline"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="service in group.data">
|
||||
<tr>
|
||||
<td class="node-cell center">
|
||||
<div class="delay-today">
|
||||
<i class="delay-today-icon" :class="service.health.className"></i>
|
||||
<span class="delay-today-text">@#service.health.text#@</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.name#@</td>
|
||||
<td class="node-cell center service-details-td">
|
||||
<template v-for="(item,index) in service.dayDetail">
|
||||
<div data-toggle="tooltip" data-placement="top" class="service-day-status-icon" :class="item.className"
|
||||
:title="item.text">
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.avgDelay#@</td>
|
||||
<td class="node-cell center">
|
||||
<div class="progress">
|
||||
<div :style="service.totalUpTime.style" :class="service.totalUpTime.className">
|
||||
<small>@#service.totalUpTime.percent#@%</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
|
||||
|
||||
|
||||
+63
-82
@@ -2,110 +2,89 @@
|
||||
{{template "theme-server-status/header" .}}
|
||||
<div id="app">
|
||||
{{template "theme-server-status/content-nav" .}}
|
||||
<div class="container content" style="max-width: 95vw">
|
||||
<table class="table table-striped table-condensed service-status">
|
||||
<!-- showGroup true -->
|
||||
<template v-if="showGroup">
|
||||
<section 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" .}}
|
||||
</section>
|
||||
</template>
|
||||
<!-- showGroup false -->
|
||||
<template v-else>
|
||||
<section class="container content" style="max-width: 95vw; min-height: .01%;overflow-x: auto;">
|
||||
{{template "theme-server-status/service-group-false" .}}
|
||||
</section>
|
||||
</template>
|
||||
<section class="container content table-responsive" style="max-width: 95vw">
|
||||
{{if .CycleTransferStats}}
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-cell center" style="min-width:60px">{{tr "Status"}}</th>
|
||||
<th class="node-cell center" style="min-width:50px">{{tr "Name"}}</th>
|
||||
<th class="node-cell center">{{tr "Details"}}</th>
|
||||
<th class="node-cell center" style="min-width:80px">{{tr "AverageLatency"}}</th>
|
||||
<th class="node-cell center" style="min-width:80px">{{tr "30DaysOnline"}}</th>
|
||||
</tr>
|
||||
<tr class="node-group-tag">
|
||||
<th colspan="16" style="border:none;">
|
||||
{{tr "CycleTransferStats"}}
|
||||
</th>
|
||||
</tr>
|
||||
<tr class="node-group-cell">
|
||||
<th class="node-cell center">ID</th>
|
||||
<th class="node-cell center">{{tr "Rules"}}</th>
|
||||
<th class="node-cell center">{{tr "Server"}}</th>
|
||||
<th class="node-cell center">{{tr "From"}}</th>
|
||||
<th class="node-cell center">{{tr "To"}}</th>
|
||||
<th class="node-cell center">MAX</th>
|
||||
<th class="node-cell center">MIN</th>
|
||||
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
||||
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
||||
<th class="node-cell center">{{tr "Transleft"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<template v-for="service in services">
|
||||
<tbody>
|
||||
{{range $id, $stats := .CycleTransferStats}}
|
||||
{{range $innerId, $transfer := $stats.Transfer}}
|
||||
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
||||
<tr>
|
||||
<td class="node-cell center">
|
||||
<div class="delay-today">
|
||||
<i class="delay-today" :class="service.health.className"></i>
|
||||
@#service.health.text#@
|
||||
</div>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.name#@</td>
|
||||
<td class="node-cell center">
|
||||
<template v-for="(item,index) in service.dayDetail">
|
||||
<div class="service-day-status-icon" :class="item.className"
|
||||
:data-tooltip="item.text">
|
||||
</div>
|
||||
</template>
|
||||
</td>
|
||||
<td class="node-cell center">@#service.avgDelay#@</td>
|
||||
<td class="node-cell center">{{$id}}</td>
|
||||
<td class="node-cell center">{{$stats.Name}}</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.To|tf}}</td>
|
||||
<td class="node-cell center">{{$stats.Max|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">{{$transfer|bf}}</td>
|
||||
<td class="node-cell center">
|
||||
<div class="progress">
|
||||
<div :style="service.totalUpTime.style" :class="service.totalUpTime.className">
|
||||
<small>@#service.totalUpTime.percent#@%</small>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="container" style="padding:unset;max-width: 95vw">
|
||||
{{if .CycleTransferStats}}
|
||||
<h4 style="text-align: center;">{{tr "CycleTransferStats"}}</h4>
|
||||
<div class="table-responsive content">
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="node-cell center">ID</th>
|
||||
<th class="node-cell center">{{tr "Rules"}}</th>
|
||||
<th class="node-cell center">{{tr "Server"}}</th>
|
||||
<th class="node-cell center">{{tr "From"}}</th>
|
||||
<th class="node-cell center">{{tr "To"}}</th>
|
||||
<th class="node-cell center">MAX</th>
|
||||
<th class="node-cell center">MIN</th>
|
||||
<th class="node-cell center">{{tr "NextCheck"}}</th>
|
||||
<th class="node-cell center">{{tr "CurrentUsage"}}</th>
|
||||
<th class="node-cell center">{{tr "Transleft"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $id, $stats := .CycleTransferStats}}
|
||||
{{range $innerId, $transfer := $stats.Transfer}}
|
||||
{{$TransLeftPercent := TransLeftPercent (UintToFloat $transfer) (UintToFloat $stats.Max)}}
|
||||
<tr>
|
||||
<td class="node-cell center">{{$id}}</td>
|
||||
<td class="node-cell center">{{$stats.Name}}</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.To|tf}}</td>
|
||||
<td class="node-cell center">{{$stats.Max|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">{{$transfer|bf}}</td>
|
||||
<td class="node-cell center">
|
||||
<div class="progress">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{template "theme-server-status/content-footer" .}}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
// 初始化 Tooltip
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ['@#', '#@'],
|
||||
data: {
|
||||
services: []
|
||||
page: 'service',
|
||||
defaultTemplate: {{.Conf.Site.Theme}},
|
||||
templates: {{.Themes}},
|
||||
servicesTag: [],
|
||||
servicesNoTag: [],
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
this.initData();
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
@@ -129,6 +108,7 @@
|
||||
const services = []
|
||||
{{range $service := .Services}}
|
||||
services.push({
|
||||
type: '{{$service.Monitor.Type}}',
|
||||
name: '{{$service.Monitor.Name}}',
|
||||
currentUp: parseInt('{{$service.CurrentUp}}'),
|
||||
currentDown: parseInt('{{$service.CurrentDown}}'),
|
||||
@@ -147,7 +127,8 @@
|
||||
service.dayDetail = this.getDayTails(service)
|
||||
service.totalUpTime = this.getProgressInfo(this.getPercent(service.totalUp, service.totalDown))
|
||||
}
|
||||
this.services = services
|
||||
this.servicesTag = this.groupingData(services,"type");
|
||||
this.servicesNoTag = services;
|
||||
},
|
||||
getPercent(up, down) {
|
||||
if (!up) {
|
||||
|
||||
@@ -12,3 +12,13 @@ site:
|
||||
brand: "nz_site_title"
|
||||
cookiename: "nezha-dashboard" #浏览器 Cookie 字段名,可不改
|
||||
theme: "default"
|
||||
ddns:
|
||||
enable: false
|
||||
provider: "webhook"
|
||||
accessid: ""
|
||||
accesssecret: ""
|
||||
webhookmethod: ""
|
||||
webhookurl: ""
|
||||
webhookrequestbody: ""
|
||||
webhookheaders: ""
|
||||
maxretries: 3
|
||||
|
||||
+324
-550
File diff suppressed because it is too large
Load Diff
+321
-547
File diff suppressed because it is too large
Load Diff
+8
-48
@@ -1,59 +1,19 @@
|
||||
#!/sbin/openrc-run
|
||||
supervisor=supervise-daemon
|
||||
name=nezha-agent
|
||||
output_log=/var/log/${name}.log
|
||||
error_log=/var/log/${name}.err
|
||||
SERVER="nz_grpc_host:nz_grpc_port" #Dashboard 地址 ip:port
|
||||
SECRET="nz_client_secret" #SECRET
|
||||
NZ_BASE_PATH="/opt/nezha"
|
||||
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
|
||||
pidfile="/run/${RC_SVCNAME}.pid"
|
||||
command="/opt/nezha/agent/nezha-agent"
|
||||
command_args="-s ${SERVER} -p ${SECRET}"
|
||||
command_background=true
|
||||
|
||||
depend() {
|
||||
need net
|
||||
}
|
||||
checkconfig() {
|
||||
GITHUB_URL="github.com"
|
||||
if [ ! -f "${NZ_AGENT_PATH}/nezha-agent" ]; then
|
||||
if [[ $(uname -m | grep 'x86_64') != "" ]]; then
|
||||
os_arch="amd64"
|
||||
elif [[ $(uname -m | grep 'i386\|i686') != "" ]]; then
|
||||
os_arch="386"
|
||||
elif [[ $(uname -m | grep 'aarch64\|armv8b\|armv8l') != "" ]]; then
|
||||
os_arch="arm64"
|
||||
elif [[ $(uname -m | grep 'arm') != "" ]]; then
|
||||
os_arch="arm"
|
||||
elif [[ $(uname -m | grep 's390x') != "" ]]; then
|
||||
os_arch="s390x"
|
||||
elif [[ $(uname -m | grep 'riscv64') != "" ]]; then
|
||||
os_arch="riscv64"
|
||||
fi
|
||||
local version=$(curl -m 10 -sL "https://api.github.com/repos/nezhahq/agent/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
if [ ! -n "$version" ]; then
|
||||
version=$(curl -m 10 -sL "https://fastly.jsdelivr.net/gh/nezhahq/agent/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/nezhahq\/agent@/v/g')
|
||||
fi
|
||||
if [ ! -n "$version" ]; then
|
||||
version=$(curl -m 10 -sL "https://gcore.jsdelivr.net/gh/nezhahq/agent/" | grep "option\.value" | awk -F "'" '{print $2}' | sed 's/nezhahq\/agent@/v/g')
|
||||
fi
|
||||
if [ ! -n "$version" ]; then
|
||||
echo -e "获取版本号失败,请检查本机能否链接 https://api.github.com/repos/nezhahq/agent/releases/latest"
|
||||
return 0
|
||||
else
|
||||
echo -e "当前最新版本为: ${version}"
|
||||
fi
|
||||
wget -t 2 -T 10 -O nezha-agent_linux_${os_arch}.zip https://${GITHUB_URL}/nezhahq/agent/releases/download/${version}/nezha-agent_linux_${os_arch}.zip >/dev/null 2>&1
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "Release 下载失败,请检查本机能否连接 ${GITHUB_URL}${plain}"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p $NZ_AGENT_PATH
|
||||
chmod 755 -R $NZ_AGENT_PATH
|
||||
unzip -qo nezha-agent_linux_${os_arch}.zip && mv nezha-agent $NZ_AGENT_PATH && rm -rf nezha-agent_linux_${os_arch}.zip README.md
|
||||
fi
|
||||
if [ ! -x "${NZ_AGENT_PATH}/nezha-agent" ]; then
|
||||
chmod +x ${NZ_AGENT_PATH}/nezha-agent
|
||||
fi
|
||||
}
|
||||
|
||||
start_pre() {
|
||||
if [ "${RC_CMD}" != "restart" ]; then
|
||||
checkconfig || return $?
|
||||
fi
|
||||
}
|
||||
checkpath -f -m 0644 -o root:root "/var/log/${name}.log"
|
||||
}
|
||||
|
||||
Regular → Executable
+7
-5
@@ -1,4 +1,8 @@
|
||||
#!/sbin/openrc-run
|
||||
supervisor=supervise-daemon
|
||||
name=nezha-dashboard
|
||||
output_log=/var/log/${name}.log
|
||||
error_log=/var/log/${name}.err
|
||||
pidfile="/run/${RC_SVCNAME}.pid"
|
||||
command="/opt/nezha/dashboard/app"
|
||||
command_args=""
|
||||
@@ -6,11 +10,9 @@ command_background=true
|
||||
directory="/opt/nezha/dashboard"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
need net
|
||||
}
|
||||
|
||||
reload() {
|
||||
ebegin "Reloading ${RC_SVCNAME}"
|
||||
start-stop-daemon --signal HUP --pidfile "${pidfile}"
|
||||
eend $?
|
||||
start_pre() {
|
||||
checkpath -f -m 0644 -o root:root "/var/log/${name}.log"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package rpc
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/naiba/nezha/pkg/ddns"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
@@ -110,6 +113,36 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
||||
host := model.PB2Host(r)
|
||||
singleton.ServerLock.RLock()
|
||||
defer singleton.ServerLock.RUnlock()
|
||||
|
||||
// 检查并更新DDNS
|
||||
if singleton.Conf.DDNS.Enable &&
|
||||
singleton.ServerList[clientID].EnableDDNS &&
|
||||
singleton.ServerList[clientID].Host != nil &&
|
||||
host.IP != "" &&
|
||||
singleton.ServerList[clientID].Host.IP != host.IP {
|
||||
|
||||
serverDomain := singleton.ServerList[clientID].DDNSDomain
|
||||
provider, err := singleton.GetDDNSProviderFromString(singleton.Conf.DDNS.Provider)
|
||||
if err == nil && serverDomain != "" {
|
||||
ipv4, ipv6, _ := utils.SplitIPAddr(host.IP)
|
||||
maxRetries := int(singleton.Conf.DDNS.MaxRetries)
|
||||
config := &ddns.DomainConfig{
|
||||
EnableIPv4: singleton.ServerList[clientID].EnableIPv4,
|
||||
EnableIpv6: singleton.ServerList[clientID].EnableIpv6,
|
||||
FullDomain: serverDomain,
|
||||
Ipv4Addr: ipv4,
|
||||
Ipv6Addr: ipv6,
|
||||
}
|
||||
go singleton.RetryableUpdateDomain(provider, config, maxRetries)
|
||||
|
||||
} else {
|
||||
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
||||
log.Printf("NEZHA>> 未找到对应的DDNS提供者(%s), 请前往config.yml检查你的设置\n", singleton.Conf.DDNS.Provider)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 发送IP变动通知
|
||||
if singleton.Conf.EnableIPChangeNotification &&
|
||||
((singleton.Conf.Cover == model.ConfigCoverAll && !singleton.Conf.IgnoredIPNotificationServerIDs[clientID]) ||
|
||||
(singleton.Conf.Cover == model.ConfigCoverIgnoreAll && singleton.Conf.IgnoredIPNotificationServerIDs[clientID])) &&
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -11,7 +13,10 @@ var (
|
||||
UserIDToApiTokenList = make(map[uint64][]string)
|
||||
ApiLock sync.RWMutex
|
||||
|
||||
ServerAPI = &ServerAPIService{}
|
||||
ServerAPI = &ServerAPIService{}
|
||||
MonitorAPI = &MonitorAPIService{}
|
||||
|
||||
once = &sync.Once{}
|
||||
)
|
||||
|
||||
type ServerAPIService struct{}
|
||||
@@ -51,6 +56,23 @@ type ServerInfoResponse struct {
|
||||
Result []*CommonServerInfo `json:"result"`
|
||||
}
|
||||
|
||||
type MonitorAPIService struct {
|
||||
}
|
||||
|
||||
type MonitorInfoResponse struct {
|
||||
CommonResponse
|
||||
Result []*MonitorInfo `json:"result"`
|
||||
}
|
||||
|
||||
type MonitorInfo struct {
|
||||
MonitorID uint64 `json:"monitor_id"`
|
||||
ServerID uint64 `json:"server_id"`
|
||||
MonitorName string `json:"monitor_name"`
|
||||
ServerName string `json:"server_name"`
|
||||
CreatedAt []int64 `json:"created_at"`
|
||||
AvgDelay []float32 `json:"avg_delay"`
|
||||
}
|
||||
|
||||
func InitAPI() {
|
||||
ApiTokenList = make(map[string]*model.ApiToken)
|
||||
UserIDToApiTokenList = make(map[uint64][]string)
|
||||
@@ -203,3 +225,45 @@ func (s *ServerAPIService) GetAllList() *ServerInfoResponse {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *MonitorAPIService) GetMonitorHistories(query map[string]any) *MonitorInfoResponse {
|
||||
var (
|
||||
resultMap = make(map[uint64]*MonitorInfo)
|
||||
monitorHistories []*model.MonitorHistory
|
||||
sortedMonitorIDs []uint64
|
||||
)
|
||||
res := &MonitorInfoResponse{
|
||||
CommonResponse: CommonResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
},
|
||||
}
|
||||
if err := DB.Model(&model.MonitorHistory{}).Select("monitor_id, created_at, server_id, avg_delay").
|
||||
Where(query).Where("created_at >= ?", time.Now().Add(-24*time.Hour)).Order("monitor_id, created_at").
|
||||
Scan(&monitorHistories).Error; err != nil {
|
||||
res.CommonResponse = CommonResponse{
|
||||
Code: 500,
|
||||
Message: err.Error(),
|
||||
}
|
||||
} else {
|
||||
for _, history := range monitorHistories {
|
||||
infos, ok := resultMap[history.MonitorID]
|
||||
if !ok {
|
||||
infos = &MonitorInfo{
|
||||
MonitorID: history.MonitorID,
|
||||
ServerID: history.ServerID,
|
||||
MonitorName: ServiceSentinelShared.monitors[history.MonitorID].Name,
|
||||
ServerName: ServerList[history.ServerID].Name,
|
||||
}
|
||||
resultMap[history.MonitorID] = infos
|
||||
sortedMonitorIDs = append(sortedMonitorIDs, history.MonitorID)
|
||||
}
|
||||
infos.CreatedAt = append(infos.CreatedAt, history.CreatedAt.Truncate(time.Minute).Unix()*1000)
|
||||
infos.AvgDelay = append(infos.AvgDelay, history.AvgDelay)
|
||||
}
|
||||
for _, monitorID := range sortedMonitorIDs {
|
||||
res.Result = append(res.Result, resultMap[monitorID])
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
||||
"log"
|
||||
)
|
||||
|
||||
func RetryableUpdateDomain(provider ddns2.Provider, config *ddns2.DomainConfig, maxRetries int) bool {
|
||||
if nil == config {
|
||||
return false
|
||||
}
|
||||
for retries := 0; retries < maxRetries; retries++ {
|
||||
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)\n", config.FullDomain, retries+1, maxRetries)
|
||||
if provider.UpdateDomain(config) {
|
||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功\n", config.FullDomain)
|
||||
return true
|
||||
}
|
||||
}
|
||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败\n", config.FullDomain)
|
||||
return false
|
||||
}
|
||||
|
||||
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
||||
switch provider {
|
||||
case "webhook":
|
||||
return ddns2.ProviderWebHook{
|
||||
URL: Conf.DDNS.WebhookURL,
|
||||
RequestMethod: Conf.DDNS.WebhookMethod,
|
||||
RequestBody: Conf.DDNS.WebhookRequestBody,
|
||||
RequestHeader: Conf.DDNS.WebhookHeaders,
|
||||
}, nil
|
||||
case "dummy":
|
||||
return ddns2.ProviderDummy{}, nil
|
||||
case "cloudflare":
|
||||
return ddns2.ProviderCloudflare{
|
||||
Secret: Conf.DDNS.AccessSecret,
|
||||
}, nil
|
||||
}
|
||||
return ddns2.ProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider))
|
||||
}
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
pb "github.com/naiba/nezha/proto"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,12 +37,13 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
|
||||
ServiceSentinelShared = &ServiceSentinel{
|
||||
serviceReportChannel: make(chan ReportData, 200),
|
||||
serviceStatusToday: make(map[uint64]*_TodayStatsOfMonitor),
|
||||
serviceCurrentStatusIndex: make(map[uint64]int),
|
||||
serviceCurrentStatusIndex: make(map[uint64]*indexStore),
|
||||
serviceCurrentStatusData: make(map[uint64][]*pb.TaskResult),
|
||||
lastStatus: make(map[uint64]int),
|
||||
serviceResponseDataStoreCurrentUp: make(map[uint64]uint64),
|
||||
serviceResponseDataStoreCurrentDown: make(map[uint64]uint64),
|
||||
serviceResponseDataStoreCurrentAvgDelay: make(map[uint64]float32),
|
||||
serviceResponsePing: make(map[uint64]map[uint64]*pingStore),
|
||||
monitors: make(map[uint64]*model.Monitor),
|
||||
sslCertCache: make(map[uint64]string),
|
||||
// 30天数据缓存
|
||||
@@ -95,11 +97,12 @@ type ServiceSentinel struct {
|
||||
|
||||
serviceResponseDataStoreLock sync.RWMutex
|
||||
serviceStatusToday map[uint64]*_TodayStatsOfMonitor // [monitor_id] -> _TodayStatsOfMonitor
|
||||
serviceCurrentStatusIndex map[uint64]int // [monitor_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标
|
||||
serviceCurrentStatusIndex map[uint64]*indexStore // [monitor_id] -> 该监控ID对应的 serviceCurrentStatusData 的最新索引下标
|
||||
serviceCurrentStatusData map[uint64][]*pb.TaskResult // [monitor_id] -> []model.MonitorHistory
|
||||
serviceResponseDataStoreCurrentUp map[uint64]uint64 // [monitor_id] -> 当前服务在线计数
|
||||
serviceResponseDataStoreCurrentDown map[uint64]uint64 // [monitor_id] -> 当前服务离线计数
|
||||
serviceResponseDataStoreCurrentAvgDelay map[uint64]float32 // [monitor_id] -> 当前服务离线计数
|
||||
serviceResponsePing map[uint64]map[uint64]*pingStore // [monitor_id] -> ClientID -> delay
|
||||
lastStatus map[uint64]int
|
||||
sslCertCache map[uint64]string
|
||||
|
||||
@@ -111,6 +114,16 @@ type ServiceSentinel struct {
|
||||
monthlyStatus map[uint64]*model.ServiceItemResponse // [monitor_id] -> model.ServiceItemResponse
|
||||
}
|
||||
|
||||
type indexStore struct {
|
||||
index int
|
||||
t time.Time
|
||||
}
|
||||
|
||||
type pingStore struct {
|
||||
count int
|
||||
ping float32
|
||||
}
|
||||
|
||||
func (ss *ServiceSentinel) refreshMonthlyServiceStatus() {
|
||||
// 刷新数据防止无人访问
|
||||
ss.LoadStats()
|
||||
@@ -326,6 +339,34 @@ func (ss *ServiceSentinel) worker() {
|
||||
continue
|
||||
}
|
||||
mh := r.Data
|
||||
if mh.Type == model.TaskTypeTCPPing || mh.Type == model.TaskTypeICMPPing {
|
||||
monitorTcpMap, ok := ss.serviceResponsePing[mh.GetId()]
|
||||
if !ok {
|
||||
monitorTcpMap = make(map[uint64]*pingStore)
|
||||
ss.serviceResponsePing[mh.GetId()] = monitorTcpMap
|
||||
}
|
||||
ts, ok := monitorTcpMap[r.Reporter]
|
||||
if !ok {
|
||||
ts = &pingStore{}
|
||||
}
|
||||
ts.count++
|
||||
ts.ping = (ts.ping*float32(ts.count-1) + mh.Delay) / float32(ts.count)
|
||||
if ts.count == Conf.AvgPingCount {
|
||||
if ts.ping > float32(Conf.MaxTCPPingValue) {
|
||||
ts.ping = float32(Conf.MaxTCPPingValue)
|
||||
}
|
||||
ts.count = 0
|
||||
if err := DB.Create(&model.MonitorHistory{
|
||||
MonitorID: mh.GetId(),
|
||||
AvgDelay: ts.ping,
|
||||
Data: mh.Data,
|
||||
ServerID: r.Reporter,
|
||||
}).Error; err != nil {
|
||||
log.Println("NEZHA>> 服务监控数据持久化失败:", err)
|
||||
}
|
||||
}
|
||||
monitorTcpMap[r.Reporter] = ts
|
||||
}
|
||||
ss.serviceResponseDataStoreLock.Lock()
|
||||
// 写入当天状态
|
||||
if mh.Successful {
|
||||
@@ -336,9 +377,20 @@ func (ss *ServiceSentinel) worker() {
|
||||
} else {
|
||||
ss.serviceStatusToday[mh.GetId()].Down++
|
||||
}
|
||||
|
||||
currentTime := time.Now()
|
||||
if ss.serviceCurrentStatusIndex[mh.GetId()] == nil {
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()] = &indexStore{
|
||||
t: currentTime,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
// 写入当前数据
|
||||
ss.serviceCurrentStatusData[mh.GetId()][ss.serviceCurrentStatusIndex[mh.GetId()]] = mh
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()]++
|
||||
if ss.serviceCurrentStatusIndex[mh.GetId()].t.Before(currentTime) {
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()].t = currentTime.Add(30 * time.Second)
|
||||
ss.serviceCurrentStatusData[mh.GetId()][ss.serviceCurrentStatusIndex[mh.GetId()].index] = mh
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()].index++
|
||||
}
|
||||
|
||||
// 更新当前状态
|
||||
ss.serviceResponseDataStoreCurrentUp[mh.GetId()] = 0
|
||||
@@ -365,8 +417,11 @@ func (ss *ServiceSentinel) worker() {
|
||||
stateCode := GetStatusCode(upPercent)
|
||||
|
||||
// 数据持久化
|
||||
if ss.serviceCurrentStatusIndex[mh.GetId()] == _CurrentStatusSize {
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()] = 0
|
||||
if ss.serviceCurrentStatusIndex[mh.GetId()].index == _CurrentStatusSize {
|
||||
ss.serviceCurrentStatusIndex[mh.GetId()] = &indexStore{
|
||||
index: 0,
|
||||
t: currentTime,
|
||||
}
|
||||
if err := DB.Create(&model.MonitorHistory{
|
||||
MonitorID: mh.GetId(),
|
||||
AvgDelay: ss.serviceResponseDataStoreCurrentAvgDelay[mh.GetId()],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -46,6 +47,21 @@ func InitConfigFromPath(path string) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ValidateConfig()
|
||||
}
|
||||
|
||||
// ValidateConfig 验证配置文件有效性
|
||||
func ValidateConfig() {
|
||||
// 如果DDNS启用则检查Provider是否存在, 不存在直接退出
|
||||
if Conf.DDNS.Enable {
|
||||
_, err := GetDDNSProviderFromString(Conf.DDNS.Provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if Conf.DDNS.MaxRetries < 1 || Conf.DDNS.MaxRetries > 10 {
|
||||
panic(fmt.Errorf("DDNS.MaxRetries值域为[1, 10]的整数, 当前为 %d", Conf.DDNS.MaxRetries))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitDBFromPath 从给出的文件路径中加载数据库
|
||||
@@ -99,6 +115,10 @@ func RecordTransferHourlyUsage() {
|
||||
func CleanMonitorHistory() {
|
||||
// 清理已被删除的服务器的监控记录与流量记录
|
||||
DB.Unscoped().Delete(&model.MonitorHistory{}, "created_at < ? OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -30))
|
||||
// 由于网络监控记录的数据较多,并且前端仅使用了 1 天的数据
|
||||
// 考虑到 sqlite 数据量问题,仅保留一天数据,
|
||||
// server_id = 0 的数据会用于/service页面的可用性展示
|
||||
DB.Unscoped().Delete(&model.MonitorHistory{}, "(created_at < ? AND server_id != 0) OR monitor_id NOT IN (SELECT `id` FROM monitors)", time.Now().AddDate(0, 0, -1))
|
||||
DB.Unscoped().Delete(&model.Transfer{}, "server_id NOT IN (SELECT `id` FROM servers)")
|
||||
// 计算可清理流量记录的时长
|
||||
var allServerKeep time.Time
|
||||
|
||||
Reference in New Issue
Block a user